Skip to content

Let a logger be taken back off - #153

Open
Bilb wants to merge 2 commits into
session-foundation:clientfrom
Bilb:logger-handles
Open

Let a logger be taken back off#153
Bilb wants to merge 2 commits into
session-foundation:clientfrom
Bilb:logger-handles

Conversation

@Bilb

@Bilb Bilb commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Depends on session-foundation/liblogging#1, which adds the oxen::log::remove_sink this calls.

The problem

add_logger builds a formatted_callback_sink around the caller's callback, hands it to
add_sink, and throws away the only reference to it:

void add_logger(std::function<void(std::string_view msg)> cb) {
    log::add_sink(std::make_shared<log::formatted_callback_sink>(std::move(cb)));
}

So the callback can never be retired. clear_loggers is the only way out and it drops every logger
in the process, including ones this caller does not own.

A consumer whose callback captures anything shorter-lived than the process is then left with a
registered callback pointing at freed memory. That is concrete rather than hypothetical: session-app
has a bridge that can be closed and reopened, and libsession logs from network threads its teardown
cannot join. It ended up holding the bridge through a weak_ptr purely so that a log line arriving
after close finds a dead pointer it can check rather than freed memory it cannot.

The change

LoggerHandle add_logger(std::function<void(std::string_view msg)> cb);
void remove_logger(const LoggerHandle& logger);
  • LoggerHandle is std::shared_ptr<oxen::log::formatted_callback_sink> with the sink only
    forward-declared: the type has to be complete to dereference it, not to hold it. So spdlog stays
    out of the public header - which today keeps it out with a forward declaration of
    spdlog::level::level_enum - and the handle still names one type, so an unrelated shared_ptr
    cannot be handed to remove_logger.
  • Removal is serialised against logging by the master sink, so once remove_logger returns the
    callback is neither running nor reachable and what it captured can be destroyed. That is the
    property a caller needs, and the reason this is worth having over "stop calling us".

ABI

add_logger's return type changes from void. Source-compatible - callers that ignore it still
compile - but the mangled symbol is unchanged while the calling convention is not, so a consumer
linking a prebuilt library against an old header would be wrong in a way the linker will not catch.
Everything I know of vendors libsession as a submodule and builds it with the application, so this
should have no victim, but it is the reviewer's call and I am happy to add a separately named
function instead.

The C API is untouched. It never exposed removal, and adding it needs an opaque handle of its own.

Verified

Built against liblogging's remove-sink branch: 245/246 targets, no warnings from these files, and
testLogging passes. The one target that does not link, static-bundle-test, does not link before
this change either - its link line is missing SQLiteCpp.

Targeted client rather than dev because client is 434 commits ahead and dev has nothing it
lacks.

add_logger built a sink around the caller's callback and discarded the only
reference to it, so the callback could never be retired: clear_loggers drops
every logger in the process, including ones this caller does not own.

A consumer whose callback captures something shorter-lived than the process is
then left with a registered callback pointing at freed memory. That is what
session-app hit - a bridge that can be closed and reopened, logging from
threads its teardown cannot join - and it had to hold the bridge weakly to work
around it.

add_logger now returns a handle and remove_logger takes it. The handle is a
shared_ptr to a forward-declared formatted_callback_sink: spdlog stays out of
the public header, and the handle still names a type, so an unrelated
shared_ptr cannot be passed as one. Logging is serialised against removal, so
once remove_logger returns the callback is neither running nor reachable.

The C API is unchanged; it never exposed removal and adding it needs an opaque
handle of its own.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants