A small, header-only C++20 logger - the lite version of spdlog, simpler, smaller, fewer features.
Use spdlite if you want a tiny, fast, capable logger.
Copy the include/spdlite/ folder into your build tree — header-only, no build step.
Or via CMake (find_package, FetchContent, or add_subdirectory), link spdlite::spdlite.
#include "spdlite/logger.h"
#include "spdlite/sinks/console_sink.h"
int main() {
using namespace spdlite;
logger<console_sink> log("app", console_sink{});
log.info("Hello {}", "world");
log.info("Value: {}", 42);
log.warn("Something happened");
log.error("Failed with code {}", -1);
}Output:
[2026-04-11 10:30:45.123] [app] [INF] Hello world
[2026-04-11 10:30:45.123] [app] [INF] Value: 42
[2026-04-11 10:30:45.123] [app] [WRN] Something happened
[2026-04-11 10:30:45.123] [app] [ERR] Failed with code -1
See include/spdlite/logger.h for the full API and include/spdlite/sinks/ for the available sinks.
logger is thread-safe by default — it uses a std::mutex to serialize format and dispatch
per call, so multiple threads can write through the same instance safely.
If you don't require thread safety, you can use logger_st which skips the lock entirely:
spdlite::logger<console_sink> loogger("app", console_sink{}); // std::mutex
spdlite::logger_st<console_sink> logger("app", console_sink{}); // no lockingBoth share the same API; only the mutex type differs.
The default header is [YYYY-MM-DD HH:MM:SS.mmm] [name] [LVL] payload. Reconfigure
via format_options:
log.format_options({.utc = true});
log.format_options({.precision = time_precision::ns});
log.format_options({.show_date = false, .precision = time_precision::none});See the table below for all available fields:
| Field | Default | Effect |
|---|---|---|
utc |
false |
Use gmtime instead of localtime. |
show_date |
true |
Include the YYYY-MM-DD prefix. |
show_thread_id |
false |
Include a 6-digit thread id after the timestamp. |
precision |
time_precision::ms |
Fractional digits: none, ms (3), us (6), or ns (9). |
Strip log calls below a chosen severity from the binary entirely — no format string,
no argument evaluation, no symbol — via the SPDLITE_* macros:
#define SPDLITE_ACTIVE_LEVEL SPDLITE_LEVEL_INFO // before the include
#include "spdlite/logger.h"
void hot_path(spdlite::logger<spdlite::console_sink>& log) {
SPDLITE_DEBUG(log, "value={}", expensive_to_compute()); // gone — args not evaluated
SPDLITE_INFO(log, "did the thing"); // stays
}Macros: SPDLITE_TRACE, SPDLITE_DEBUG, SPDLITE_INFO, SPDLITE_WARN, SPDLITE_ERROR,
SPDLITE_CRITICAL. Levels: SPDLITE_LEVEL_TRACE (0) ... SPDLITE_LEVEL_OFF (6). Per-TU
setting; default is SPDLITE_LEVEL_TRACE (no elision). The compile-time gate is independent
of the runtime set_log_level() — a call emits only when both gates pass.
Most of a logging TU's compile cost is the bundled fmt headers. To cut it:
- Gate out cold log calls — see Compile-time level gating above; elided calls cost nothing, at compile time and runtime.
- Compile fmt once —
-DSPDLITE_COMPILED_FMT=ONbuilds fmt's implementation into a small static lib instead of header-only, so logging TUs compile much faster (~60% per TU) with no runtime cost and identical output. Worth it from ~2 logging TUs up; gives up pure header-only (consumers link the lib — automatic viaspdlite::spdlite). - Use
std::format—-DSPDLITE_USE_STD_FORMAT=ONdrops the bundled fmt headers and compiles ~20% faster per TU, but it is a compile-time-for-runtime trade: libstdc++'sstd::formatis ~1.3–2× slower than bundled fmt on typical messages, so fmt stays the default. Needs a modern stdlib (libstdc++ 13+, libc++ 17+, MSVC 19.29+).
CMake isn't required to use spdlite — copy the headers and you're done. The flags below
only apply if you build the bundled example, tests, or benchmarks with the provided
CMakeLists.txt.
| Option | Default | Effect |
|---|---|---|
SPDLITE_BUILD_EXAMPLE |
ON |
Build the example executable. |
SPDLITE_BUILD_TESTS |
OFF |
Build the doctest-based unit tests. |
SPDLITE_BUILD_BENCH |
OFF |
Build the benchmarks (fetches Google Benchmark automatically). |
SPDLITE_USE_STD_FORMAT |
OFF |
CMake option: use <format> instead of bundled fmt — drop fmt/ from the install. Pass via -DSPDLITE_USE_STD_FORMAT=ON. |
SPDLITE_COMPILED_FMT |
OFF |
Compile bundled fmt once into a static lib (not header-only) — faster builds, links spdlite_fmt. Mutually exclusive with SPDLITE_USE_STD_FORMAT. |
Build and run:
$ cmake -B build -DSPDLITE_BUILD_BENCH=ON .
$ cmake --build build
$ ./build/latency # quick set
$ ./build/latency full # adds multi-threaded + file I/OMIT