# libenderlog A standalone C++26 logging library, built as a C++20/26 **module** (`import ender.log;`) with **`std::stacktrace`** call-stack capture on severe records. It was extracted from the `ender-physics` engine, where it started life as `ender.log`, and turned into a library that has no other dependency — not on the engine, not on a logging framework. ## Features - **Leveled records.** `trace`, `debug`, `info`, `warn`, `error`, `critical`, filtered by an atomic `minimum` check that is cheap enough to guard expensive message construction: `if (log::enabled(log::level::debug)) { ... }`. - **Source location.** Every record carries the file, line and function of the caller, taken from `std::source_location` at the call site — exact even in a stripped release binary, because it is a compile-time constant. - **Call stacks.** Records at or above `options::stacktrace_from` carry a formatted `std::stacktrace`. The frames belonging to the library itself are stripped by symbol, so the first reported frame is the caller regardless of the optimisation level (the level wrappers get inlined away under `-O`). - **Pluggable sinks.** A `console_sink` (stderr by default) and a `memory_sink` (for tests and in-game consoles) ship; `sink` is a small interface. - **File output with archiving.** `file_sink` writes to a file and, on open, moves an existing log aside to a timestamped archive, so a run never appends onto a previous run's log. It can also rotate by size and bound how many archives are kept. - **No stacktrace? No problem.** Where `` is missing (libc++, and therefore every cross target), the module still compiles and records still carry their call site — they simply have no stack. ## Requirements C++26 modules and `import std;` need a recent toolchain: | Requirement | Version | |---|---| | Compiler | **GCC 15+** (or Clang with a standard library that provides the `std` module) | | CMake | **3.30+** (for `CMAKE_EXPERIMENTAL_CXX_IMPORT_STD`) | | Standard library | libstdc++ for `std::stacktrace` | Ubuntu 26.04's default `g++` (GCC 15) and CMake 4 satisfy this, and that is the release the CI targets and the `.deb` is built for. Ubuntu 24.04 ships GCC 13 and CMake 3.28 and cannot build `import std;` without extra toolchains, so it is not supported. ## Building ```sh cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release cmake --build build -j ctest --test-dir build --output-on-failure ``` Options: | Option | Default | Description | |---|---|---| | `ENDERLOG_WERROR` | `OFF` | Treat warnings as errors | | `ENDERLOG_BACKTRACE_SYMBOLS` | `ON` | Link executables with `-rdynamic` so traces can name frames | | `ENDERLOG_BUILD_TESTS` | `ON` | Build the test suite | | `ENDERLOG_BUILD_EXAMPLES` | `ON` | Build the example program | Build Debug or RelWithDebInfo when you need to read a trace: frame names come from debug information, and a release build often reports application frames as ``. ## Using the library ### `add_subdirectory` ```cmake add_subdirectory(libenderlog) target_link_libraries(my_app PRIVATE enderlog::enderlog) ``` ### Installed package ```cmake find_package(enderlog REQUIRED) target_link_libraries(my_app PRIVATE enderlog::enderlog) ``` The static archive is installed together with the module interface source (`ender.log.cppm`) because a module's BMI is compiler-version-specific — the consumer rebuilds it from the source. ### In code ```cpp import std; import ender.log; namespace log = ender::log; auto main() -> int { log::configure({.minimum = log::level::debug, .stacktrace_from = log::level::warn}); log::info(std::format("body {} moved to {:.2f}", 7, 12.35)); // formatted by the caller log::error("a body left the world"); // carries a stack trace } ``` Example output: ``` [11:32:18] ERROR example: a body left the world (examples/main.cpp:8) #0 simulate_one_step (examples/main.cpp:8) #1 main (examples/main.cpp:20) #2 #3 __libc_start_main #4 _start ``` ### Configuration ```cpp log::configure({ .minimum = log::level::debug, // drop everything below this .stacktrace_from = log::level::error, // capture a stack at/above this .stacktrace_depth = 16, // max frames kept .stacktrace_skip = 2, // frames dropped before the caller is found }); ``` `log::current_options()` reads it back, `log::add_sink(...)` adds a destination, and `log::set_sinks({...})` replaces them. ### Writing to a file ```cpp namespace log = ender::log; // Archive any existing enderlog.log to enderlog.log., then start a // fresh file for this run. Rotate at 64 KiB and keep the last 5 archives. auto sink = log::add_file_sink("enderlog.log", {.max_file_size = 64 * 1024, .max_archives = 5}); ``` - **No appending onto a previous run.** On open, an existing non-empty `enderlog.log` is renamed to `enderlog.log.` before the new file is created, so every run gets its own file and the previous run's log is preserved. A leftover empty file is simply replaced. - `file_options::max_file_size` (0 disables) rotates the active file mid-run the same way, and never archives an empty file. `file_options::max_archives` (0 keeps all) deletes the oldest archives beyond the limit. - `file_options::flush_each_record` (on by default) flushes after every record so a crash keeps the tail. - `add_file_sink` adds the sink to the global logger and returns it; `path()` and `archives()` expose what it wrote. The `file_sink` class can also be used directly and installed with `set_sinks`. ## Packaging `cpack` produces a Debian package: ```sh cmake -S . -B build -DENDERLOG_DISTRO=ubuntu26.04 cmake --build build -j cd build cpack # -> libenderlog-dev_0.0.1_amd64_ubuntu26.04.deb ``` The package installs the static archive, the module interface source and the CMake package config. CI builds it for Ubuntu 26.04 and publishes it as a job artifact. ## License MIT — see [LICENSE](LICENSE).