chlog is a lightweight, modern, header-only C++20 logging library: simple at the call site, configurable for throughput (optional async), and extensible via sinks.
- C++20: uses
std::formatandstd::source_location - Platforms: Windows / Linux / macOS (
localtime_son Windows,localtime_relsewhere)
中文一句话:
chlog主打“高性能、调用端轻、吞吐高、可选异步、可插拔 sink、 线程安全可选”,并且尽量把开销留在需要的时候。
- Sync / async: toggle via
logger_config::async.enabled - Fast async wakeups: bounded MPSC ring buffer +
std::counting_semaphore - Single-threaded ultra-fast mode:
logger_config::single_threaded(not thread-safe; forces async + parallel_sinks off) - Bounded queue + priority dropping: prefers dropping
trace/debug/infowhile keepingwarn+ - Optional {fmt} backend: define
CHLOG_USE_FMTto use{fmt}for formatting (default:std::format) - Built-in sinks:
console_sink,rotating_file_sink,daily_file_sink,json_sink - Optional parallel sinks:
logger_config::parallel_sinks(sync mode only) - Call-site info:
std::source_location→{file}{line}{func}(pattern / JSON) - Robustness: formatting failures / sink exceptions are swallowed (logging should not crash your app)
- chlog
| Case | chlog | spdlog |
|---|---|---|
| async_mt | 5.026e+06 | 4.130e+06 |
| filtered_out | 4.510e+09 | 4.395e+08 |
| sync_mt | 1.737e+07 | 1.695e+07 |
| sync_st | 2.836e+07 | 2.288e+07 |
Full details (including per-case tables, CPU/memory, and library versions) are in docs/logbench_results.md.
#include <chlog/chlog.hpp>
int main() {
using namespace chlog;
logger_config cfg;
cfg.name = "app";
cfg.level = level::debug;
cfg.pattern = "[{date} {time}.{ms}][{lvl}][{name}][{file}:{line}] {msg}";
cfg.async.enabled = true;
cfg.async.queue_capacity = 1u << 16;
cfg.async.batch_max = 256;
cfg.async.flush_every = std::chrono::milliseconds(200);
auto lg = std::make_shared<logger>(cfg);
lg->add_sink(std::make_shared<console_sink>(console_sink::style::plain));
lg->add_sink(std::make_shared<rotating_file_sink>("logs/app.log", 32 * 1024 * 1024, 5));
lg->info("hello {}", 123);
lg->warn("disk {}%", 95);
lg->shutdown();
}If you only need the formatted message (no timestamp / thread id / logger name / source location), set:
cfg.pattern = "{msg}";
In this mode, chlog automatically disables metadata capture (capture_timestamp, capture_thread_id, capture_logger_name, capture_source_location) to minimize per-call overhead. This is especially useful for hot loops (e.g., game loops, trading strategies, telemetry) where you want high throughput and you don’t need rich metadata.
#include <chlog/chlog.hpp>
#include <chrono>
#include <memory>
int main() {
using namespace chlog;
logger_config cfg;
cfg.name = "fast";
cfg.level = level::info;
// Feature: message-only mode disables metadata capture automatically.
cfg.pattern = "{msg}";
cfg.async.enabled = true;
cfg.async.queue_capacity = 1u << 16;
cfg.async.batch_max = 512;
cfg.async.flush_every = std::chrono::milliseconds(200);
auto lg = std::make_shared<logger>(cfg);
lg->add_sink(std::make_shared<rotating_file_sink>("logs/fast.log", 8 * 1024 * 1024, 3));
for (int i = 0; i < 100000; ++i) {
lg->info("tick {}", i);
}
lg->shutdown();
}Notes:
- If you need any metadata, use a pattern token (e.g.
[{lvl}] {msg}or[{date} {time}.{ms}] {msg}) and keep the correspondingcapture_*flags enabled. - If you want structured output, prefer
{json}(see “Pattern”).
If your application logs from exactly one thread (typical in some game loops, embedded, or single-threaded tools), you can enable:
cfg.single_threaded = true;
Behavior:
- No thread-safety guarantees: calling the same logger/sinks from multiple threads is undefined.
- For maximum throughput, chlog forces
async.enabled = falseandparallel_sinks = falsein this mode (no internal worker threads; no cross-thread sink writes). - Built-in sinks also skip their internal mutexes when single-threaded.
parallel_sinks controls whether sync-mode logging fans out a single log event to multiple sinks in parallel.
- When
cfg.async.enabled == falseandcfg.parallel_sinks == true,logger::add_sink()lazily creates an internal thread pool and each log event will enqueue one task per sink. - When
cfg.async.enabled == true, chlog intentionally keeps sink writes on the single async worker thread for best throughput and lower overhead;parallel_sinksdoes not change async behavior.
logger_config::sink_pool_size controls the number of worker threads used for parallel sinks:
0(default): usessinks.size()at the time the pool is created.> 0: uses that fixed size.
Trade-offs (important):
- Ordering: with
parallel_sinksenabled, strict ordering across sinks (and even within the same sink under contention) is not guaranteed. - Flush semantics: in sync mode with
parallel_sinks,flush_on_levelandlogger::flush()are best-effort because sink writes are happening on background pool threads.
Recommendation:
- Use
parallel_sinks = trueif you have multiple slow sinks (e.g. file + network) and you prefer higher throughput over strict ordering/flush guarantees. - Use
parallel_sinks = falseif you require strict ordering and synchronous flush behavior.
Default pattern:
[{date} {time}.{ms}][{lvl}][tid={tid}][{name}] {msg}
Available tokens:
{ts}{date}{time}{ms}{lvl}{tid}{name}{msg}{file}{line}{func}
Special pattern:
{json}: outputs a single-line JSON record (includes file/line/func).
If you want to force capturing call-site info without changing your function signatures, you can use:
CHLOG_INFO(*lg, "msg {}", x)CHLOG_ERROR(*lg, "oops {}", err)- etc.
cmake -S . -B build
cmake --build build --config ReleaseOnce the chlog port is available in your vcpkg registry (or you use this repo's overlay port under ports/chlog), install with:
vcpkg install chlogConsume from CMake:
find_package(chlog CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE chlog::chlog)Example (enabled by default):
chlog_stress(built fromexamples/chlog_stress.cpp)chlog_single_thread_bench(built fromexamples/single_thread_bench.cpp)
This repo includes a simple benchmark executable that compares chlog vs spdlog in a tight loop, using an in-memory counting sink (no I/O) to focus on call-site + formatting + dispatch overhead.
Note: for a fairer comparison, the benchmark enables CHLOG_USE_FMT for chlog when spdlog is available,
so both libraries use the same formatting backend.
Benchmark executable:
chlog_bench_loggers
Install spdlog:
vcpkg install spdlogExample using Ninja + clang on Windows:
$env:VCPKG_ROOT = "C:\\path\\to\\vcpkg"
cmake -S . -B build-ninja-clang -G Ninja `
-DCMAKE_BUILD_TYPE=Release `
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT\\scripts\\buildsystems\\vcpkg.cmake" `
-DCMAKE_CXX_COMPILER=clang
cmake --build build-ninja-clangIterations can be set via --iters or CHLOG_BENCH_ITERS:
$env:CHLOG_BENCH_ITERS = "1000000"
./build-ninja-clang/chlog_bench_loggers
./build-ninja-clang/chlog_bench_loggers --iters 2000000The program prints machine-parsable lines:
RESULT runner=... case=... calls=... seconds=... cps=... processed=... dropped=...
python ./tools/logbench_report.py --build-dir build-ninja-clang --out docs/logbench_results.md --iters 1000000The report includes:
- CPU + total memory info
- vcpkg versions for
spdlog(andfmtif present)
Note:
- results may differ between platforms
python ./tools/logbench_report.py --build-dir build-ninja-clang --out docs/logbench_results.md --iters 2000000
python ./tools/logbench_plot.py --in docs/logbench_results.md --out docs/logbench_summary.svg