Logging
The Logging module provides an async/sync logger with printf-style formatting, multiple log levels, console and rotating file sinks, per-module log tags, and a system watchdog for resource monitoring.
Overview
xtils logging is designed for embedded and server applications where performance matters. It uses an async ring buffer by default (zero allocation on the hot path) and supports multiple sinks simultaneously.
#define LOG_TAG_STRING "my_module"
#include <xtils/logging/logger.h>
LogI("Server started on port %d", 8080);
LogW("Connection pool running low: %d/%d", available, total);
LogE("Failed to connect: %s", error.c_str());Header
#include "xtils/logging/logger.h"Log Macros (Primary Interface)
Default Logger
LogT(fmt, ...) // Trace (compiled out unless ENABLE_TRACE_LOGGING defined)
LogD(fmt, ...) // Debug
LogI(fmt, ...) // Info
LogW(fmt, ...) // Warning
LogE(fmt, ...) // ErrorCustom Logger Instance
auto* logger = xtils::logger::DefaultLogger();
XTILS_LOG_T(logger, fmt, ...)
XTILS_LOG_D(logger, fmt, ...)
XTILS_LOG_I(logger, fmt, ...)
XTILS_LOG_W(logger, fmt, ...)
XTILS_LOG_E(logger, fmt, ...)Opt-in short macros
If you prefer TRACE / DEBUG / INFO / WARN / ERROR, define before including:
#define XTILS_LOG_SHORT_MACROS
#include <xtils/logging/logger.h>
INFO(logger, "info message");
DEBUG(logger, "debug message");WARNING
Short macros may collide with other libraries (e.g. Windows ERROR). Disabled by default.
Assertions and Fatal
XTILS_CHECK(expr) // assert + abort if false
XTILS_DCHECK(expr) // debug-only assert (still active in release if compiled)
XTILS_FATAL(fmt, ...) // log error + abortWith XTILS_LOG_SHORT_MACROS defined the short forms CHECK/DCHECK/FATAL are also available.
Log Levels
trace (0) → debug (1) → info (2) → warn (3) → error (4)Messages below the configured level are dropped. In production, set level to info or warn for minimal overhead.
Log Tag
Set a per-file log tag to identify which module produced a message:
// At the top of your .cc file, BEFORE including logger.h
#define LOG_TAG_STRING "network"
#include "xtils/logging/logger.h"
// Output: [I][network] Connection established
LogI("Connection established");If LOG_TAG_STRING is not defined, the tag defaults to the filename.
Structured logging (LogBuilder)
In addition to printf-style macros, v2.0 ships a chained-field API ideal for adding rich context that downstream tooling can parse.
#include "xtils/logging/log_builder.h"
LOGI().Field("req_id", id)
.Field("status", code)
.Msg("done");
LOGW().Field("path", path)
.Field("latency_ms", latency)
.Msg("slow request");
// printf-style body still works
LOGE().Field("errno", errno).Msg("open(%s) failed", path.c_str());Example output (PlainFormatter):
2026-06-17 10:30:00 I default: done req_id=abc123 status=200Macros
| Macro | Level |
|---|---|
LOGI() | info |
LOGW() | warn |
LOGE() | error |
LOGD() | debug |
LOGT() | trace (a no-op when ENABLE_TRACE_LOGGING is not defined) |
Terminal methods
Msg(const std::string& body)/Msg()— dispatch one log entryMsg(const char* fmt, ...)— printf-style bodyRenderForTesting()— returns the rendered fields without dispatching (tests only)
If Msg(...) is never called, the destructor flushes with an empty body so a forgotten .Msg() does not silently swallow the entry.
MDC (Mapped Diagnostic Context)
MDC is thread-local key/value context that automatically attaches to any LogBuilder call on the same thread.
#include "xtils/logging/mdc.h"
using xtils::logger::Mdc;
void HandleRequest(const Request& r) {
Mdc::Put("req_id", r.id);
Mdc::Put("user", r.user);
LOGI().Msg("request received"); // appends req_id=... user=...
DoWork();
LOGI().Msg("request done");
Mdc::Clear();
}RAII Scope
The usual pattern is Mdc::Scope, which restores the previous value (or removes the key) on destruction:
void HandleRequest(const Request& r) {
Mdc::Scope s1("req_id", r.id);
Mdc::Scope s2("user", r.user);
LOGI().Msg("request received");
DoWork();
LOGI().Msg("request done");
// req_id / user reset here.
}API
class Mdc {
public:
static void Put (std::string key, std::string value);
static void Erase(const std::string& key);
static const std::string& Get(const std::string& key); // empty string if absent
static void Clear();
static std::vector<std::pair<std::string,std::string>> Snapshot();
class Scope { Scope(std::string key, std::string value); };
};TIP
MDC is thread-local. When jumping tasks across ThreadTaskRunner boundaries you must propagate context manually if you want it to ride along.
Logger API
namespace xtils::logger {
// Get the global default logger
Logger* DefaultLogger();
// Logger class
class Logger {
public:
void SetLevel(log_level level);
log_level Level() const;
void AddSink(std::unique_ptr<Sink> sink);
void Flush();
void Shutdown();
size_t GetDroppedCount() const; // Messages dropped due to full buffer
};
// Convenience
void SetLevel(Logger* logger, log_level level);
} // namespace xtils::loggerLog Levels Enum
enum class log_level {
trace = 0,
debug = 1,
info = 2,
warn = 3,
error = 4,
};Sinks
Sinks control where log output goes. Multiple sinks can be active simultaneously.
#include "xtils/logging/sink.h"
// Base interface
struct Sink {
virtual void write(const char* buf, size_t start, size_t len) = 0;
virtual void flush() = 0;
};ConsoleSink
Writes colored output to stdout:
auto* logger = xtils::logger::DefaultLogger();
// ConsoleSink is added by defaultFileSink (Rotating)
Writes to rotating log files with configurable size and count:
#include "xtils/logging/sink.h"
// Parameters: path, max_bytes_per_file, max_file_count
auto sink = std::make_unique<FileSink>("logs/app.log", 10 * 1024 * 1024, 5);
// Creates: app.log, app.1.log, app.2.log, ... app.4.log
logger->AddSink(std::move(sink));Watchdog
The Watchdog monitors system resources and can trigger actions when limits are exceeded:
#include "xtils/logging/watchdog.h"
auto* wd = Watchdog::GetInstance();
wd->Start();
// Alert if memory exceeds 512MB for 5 seconds
wd->SetMemoryLimit(512 * 1024 * 1024, 5000);
// Alert if CPU exceeds 90% for 10 seconds
wd->SetCpuLimit(90, 10000);Fatal Timers
Create a watchdog timer that aborts the process if not cancelled (deadlock detection):
// Create a 30-second fatal timer
auto timer = wd->CreateFatalTimer(30000, WatchdogCrashReason::Timeout);
// Do work that should complete within 30 seconds...
DoWork();
// Timer is automatically cancelled when it goes out of scope (RAII)Guard Helper
// Run a task with automatic watchdog guard
RunTaskWithWatchdogGuard([]() {
// If this takes too long, watchdog triggers
ProcessData();
});Configuration
Compile-Time Options
| Define | Effect |
|---|---|
ENABLE_TRACE_LOGGING | Enable LogT() / trace level (off by default for performance) |
LOG_TAG_STRING | Set per-file module tag |
Runtime Configuration
// Set level at runtime
auto* logger = xtils::logger::DefaultLogger();
logger->SetLevel(log_level::debug);
// Disable file logging (enabled by default in app framework)
// Configure via config.json: {"logging": {"file": false}}Best Practices
Performance
- Use
LogTfor high-frequency debug messages — they compile away in release builds - Avoid string formatting in hot paths when log level would filter it out; the macros short-circuit
- File sinks use async writes — they don't block your thread
Thread Safety
The default logger is thread-safe. Custom loggers created separately are also thread-safe. Sink implementations should be thread-safe as they may receive writes from multiple threads.
Complete Example
#define LOG_TAG_STRING "app"
#include <xtils/logging/logger.h>
#include <xtils/logging/sink.h>
#include <xtils/logging/watchdog.h>
using namespace xtils::logger;
int main() {
auto* logger = DefaultLogger();
// Add rotating file sink (10MB per file, keep 3 files)
logger->AddSink(std::make_unique<FileSink>("logs/app.log", 10*1024*1024, 3));
// Set level based on environment
#ifdef NDEBUG
logger->SetLevel(log_level::info);
#else
logger->SetLevel(log_level::debug);
#endif
// Start watchdog
auto* wd = Watchdog::GetInstance();
wd->Start();
wd->SetMemoryLimit(256 * 1024 * 1024, 5000);
LogI("Application starting (PID=%d)", getpid());
LogD("Debug logging enabled");
// Application logic...
for (int i = 0; i < 100; ++i) {
LogT("Processing item %d", i); // Only if ENABLE_TRACE_LOGGING
ProcessItem(i);
}
LogI("Application shutting down");
logger->Flush();
return 0;
}