Skip to content

Sinks and Threading

Hugo edited this page Oct 15, 2026 · 2 revisions

Sinks and Threading

Sinks

A sink receives the formatted output:

using SinkFn = void (*)(const char *data, size_t size);

By default, output goes to stderr. On POSIX, writes are retried on EINTR and on partial writes. set_sink(fn) redirects it, and reset_sink() or set_sink(nullptr) restores stderr.

SinkFn is a plain function pointer, so a lambda passed to it cannot capture anything. Keep sink state in variables with static storage duration:

static FILE *log_file = std::fopen("app.log", "w");

coretrace::set_sink([](const char *data, size_t size) {
  std::fwrite(data, 1, size, log_file);
});
// ...
coretrace::reset_sink();
std::fclose(log_file); // Safe: see "Changing the sink" below.

Each log() line reaches the sink in one call containing the whole line: prefix, message and your trailing \n. The sink is chosen once per line, so a line never ends up split between two sinks.

Thread-safe mode

Thread-safe mode is on by default (set_thread_safe(true)). In this mode:

  • Lines are serialized: the sink is never called for two lines at once, so a sink can append to a std::string or a FILE* without its own lock.
  • The format-error fallback (coretrace: log format error) is serialized like any other line.
  • The output lock is reentrant. A sink may call log(), for example to report its own error, and may call set_sink(); the new sink applies to the next line.

set_thread_safe(false) removes the lock, for single-threaded hot paths. The sink can then be called from several threads at once, and the guarantees below no longer hold.

fork()

On POSIX, fork() waits for a line another thread is writing, and for the logger's initialization. The child therefore inherits neither half done, and can log right away, even when threads of the parent were logging at the time of the fork. Its lines carry its own PID.

  • A sink that waits for the thread calling fork() deadlocks it.
  • A sink may call fork(), and the child can log.
  • Fork handlers registered with pthread_atfork(), before or after the logger's, may log.
  • fork() keeps working while the program exits, from an atexit() handler or a static destructor.

Changing the sink

With thread safety enabled, set_sink() and reset_sink() wait for a line that is still being written. Once they return, log() no longer calls the previous sink, so its resources (file, socket, buffer) can be released.

Low-level writes

write_raw(), write_str(), write_dec(), write_hex() and write_prefix() write straight to the current sink. Unlike log(), they are not serialized with other lines, even in thread-safe mode. Use them from one thread, or accept that their output may interleave with other lines.

Colors and custom sinks

Whether lines include ANSI colors depends on stderr, not on the sink (see Configuration). A program started from a terminal therefore writes escape sequences into a file sink. Set NO_COLOR=1 to avoid that.

Clone this wiki locally