Files
OpenRA3/third_party/libenderlog/README.md
T
EnderTheCoder 2f6ff74670 chore(vendor): update vendored libenderlog to v0.0.3
Dated record timestamps, per-sink timestamp format, and archive names
that keep the extension last (<stem>.<timestamp>.<ext>).
2026-10-01 01:05:21 +08:00

6.4 KiB

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, bound how many archives are kept, and format both the record timestamp and the archive names.
  • No stacktrace? No problem. Where <stacktrace> 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

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 <unknown>.

Using the library

add_subdirectory

add_subdirectory(libenderlog)
target_link_libraries(my_app PRIVATE enderlog::enderlog)

Installed package

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

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:

[2026-10-01 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  <unknown>
    #3  __libc_start_main
    #4  _start

Configuration

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

namespace log = ender::log;

// Archive any existing enderlog.log to enderlog.<timestamp>.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.<timestamp>.log before the new file is created, so every run gets its own file and the previous run's log is preserved. The timestamp is inserted before the extension, which stays last (.log when the active file has none). 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.
  • file_options::timestamp_format (chrono syntax, default %Y-%m-%d %H:%M:%S) controls the timestamp on each record's header line; file_options::archive_time_format (chrono syntax, default %Y%m%d-%H%M%S) controls the timestamp inserted into archive names. A chrono format string must begin with % (e.g. %Y-%m-%d_%H%M%S).
  • 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:

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.