Skip to content

Threading Model

Eduard Mishkurov edited this page Aug 18, 2026 · 4 revisions

Threading Model

logme is designed for concurrent use, but the thread model is more specific than “thread-safe everywhere”.

What is thread-safe

The implementation uses a mix of mutexes and atomics for normal multi-threaded logging:

  • logger channel map changes are protected by Logger::DataLock
  • channel routing/backends are protected by Channel::DataLock
  • level/enabled/active state use atomics for fast checks
  • BufferBackend uses its own mutex
  • FileBackend uses an internal queue plus a shared management thread
  • the directory-size watchdog runs in its own worker thread

This makes ordinary logging from multiple threads a supported scenario.

Thread-local context

Several features use actual thread-local state:

  • default channel override (SetThreadChannel, LogmeThreadChannel)
  • default subsystem override (SetThreadSubsystem, LogmeThreadSubsystem)
  • default output override (SetThreadOverride, LogmeThreadOverride)
  • structured thread fields (SetThreadField, SetThreadFields, LogmeThreadFields)
  • re-entry guard state (DisplayReentryGuard)

The scoped LogmeThread... helpers save the previous value and restore it when the scope ends. LogmeThreadFields(fields) temporarily replaces the current field set; use SetThreadField() / RemoveThreadField() when only individual fields need to change. Structured thread fields are emitted by JSON/XML output and are ignored by normal text output.

LogmeThreadName(...) is also thread-specific from the caller's point of view, but it is stored by the channel under the current thread id rather than in the same TLS state described above.

Thread-local context does not automatically propagate to another worker thread. If a task moves between threads, establish the required channel, subsystem, fields, or override in the thread that continues the work.

Channel recursion is also tracked per thread, not globally across all threads.

Re-entry behavior

The channel-level re-entry guard blocks delivery back into the same channel when that channel is already on the current thread’s routing stack. It also prevents infinite link cycles within the same thread.

Logging to a different channel from inside a backend is allowed, provided that routing does not re-enter an already active channel.

Runtime reconfiguration

Live changes such as enabling/disabling channels, changing levels/flags, or adding/removing backends are synchronized internally.

Still, users should treat runtime reconfiguration as an operational feature, not as a lock-free control plane: changes become visible under normal mutex/atomic rules, not as a transactional graph swap for in-flight records.

What is not guaranteed

A few things should not be assumed:

  • signal-handler safety is not provided
  • lock-free delivery is not a goal of the current implementation
  • normal Display() delivery from a backend's owner channel is serialized by Channel::DataLock; backend state shared with worker threads or lifecycle methods still needs its own synchronization

The signal-safety point is especially important: the implementation uses mutexes, condition variables, dynamic allocation, file I/O, and formatting machinery, so it is not suitable for async-signal-safe logging.

Getting Started

Practical Runbooks

Architecture

Output & Formatting

Backends

Runtime Control

Tools

Reference

Examples

Clone this wiki locally