Repository navigation
Architecture
| Path | Role |
|---|---|
include/coretrace/logger.hpp |
Public API, including the log() templates |
src/logger.cpp |
State, filtering, line formatting, output |
src/logger_platform.hpp |
Platform interface: color support, stderr writes, process and thread IDs, UTC time, fork handlers |
src/logger_posix.cpp |
POSIX backend (Linux, macOS) |
src/logger_windows.cpp |
Windows backend |
example/main.cpp |
Demo program, also run as a test |
tests/ |
Test suite, see Testing |
cmake/coretrace-loggerConfig.cmake.in |
Package configuration for find_package()
|
CMake compiles logger.cpp and one backend, chosen with WIN32, into the static library coretrace_logger, exported as coretrace::logger.
All state is process-wide, in src/logger.cpp:
- Atomics: enabled flag, minimum level, sink pointer, thread-safe, timestamp and source-location flags.
- A state mutex guards the prefix (64-byte buffer) and the module table (32 entries of 32 bytes). Neither allocates.
- An output mutex serializes lines in thread-safe mode.
- Both mutexes are reentrant: a
std::mutexwith a thread-local count of the guards held, locked when the count goes from 0 to 1. Notstd::recursive_mutex, which a child offork()cannot unlock: it checks the ID of its owner, and the child's thread has another one. They are never destroyed, sofork()still works from anatexit()handler or a static destructor that runs after the logger's.
init_once() runs std::call_once on the first log() call, getter, filter setter, or fork(). It reads CT_LOG_LEVEL and CT_DEBUG, unless the level or the modules were already set through the API. That is how explicit calls take precedence over the environment.
The color decision (NO_COLOR, then whether stderr is a terminal) is made once, on first use. It is a lock-free std::atomic, not a function-local static, whose initialization guard a child of fork() could inherit as taken. When two threads decide at once, the first decision stored holds for both. The PID is not cached, so a child of fork() logs its own.
- The
log()template runsinit_once(), then checks the enabled flag, the level and, for tagged messages, the module filter. - It formats the message with
std::vformat. A format error writes the fallback line throughwrite_format_error(), and an empty message stops here. -
write_log_line()copies the prefix under the state mutex, then builds the complete line in astd::string: timestamp, PID, prefix, level, location, module, message, with color sequences when enabled. - It takes the output mutex, when thread-safe, and passes the line to
write_raw(). That reads the sink once and calls it, or falls back toplatform::write_stderr().
Building the line before locking keeps the critical section short. Delivering it in one call guarantees that a line is never split or interleaved.
A child of fork() has only the thread that forked: a lock or an initialization another thread of the parent was holding stays held forever. The logger registers fork handlers through platform::at_fork():
- Before the fork, the forking thread runs
init_once(), then takes the output mutex and the state mutex, in the order of a sink that logs. - Fork handlers registered before the logger's run in between, in the same thread. They can call the logger, since the locks count as held by that thread.
- After the fork, the parent and the child release both mutexes.
| Function | POSIX | Windows |
|---|---|---|
stderr_supports_color() |
isatty(2) |
_isatty(), then enables virtual terminal processing on the console |
write_stderr() |
write(2), retried on EINTR and on partial writes |
_write() in chunks |
process_id() |
getpid() |
GetCurrentProcessId() |
current_thread_id() |
gettid (Linux), pthread_threadid_np (macOS), otherwise pthread_self
|
GetCurrentThreadId() |
utc_timestamp() |
clock_gettime + gmtime_r
|
system_clock + gmtime_s
|
at_fork() |
pthread_atfork() |
does nothing |
State is global and initialization runs once, so a scenario that depends on startup (environment variables, colors, logging disabled by default) needs its own process. See Testing.
Using the library
Working on the library