Files
OpenRA3/third_party/libenderlog
EnderTheCoder 70d4beca4f v0.6.0: WebAssembly build with a WebGL backend (playable in the browser)
- ra3.webgl: a Web peer of Vulkan/Direct3D (SDL3 canvas + GLES3/WebGL2), with
  2D blit and the GPU terrain raymarch; GLSL ES shaders; null fallback off
  Emscripten. backend::webgl + default_backend() (webgl under Emscripten).
- Emscripten toolchain + Dockerfile.wasm + scripts/build-wasm.sh; import std,
  global -fexceptions, and Asyncify so the blocking frame loop yields to the
  browser (display::sleep_frame calls emscripten_sleep).
- apps/web/shell.html + apps/web/serve.py (Range-capable dev server).
- Assets: browsers forbid synchronous on-demand reads on the main thread (and
  FS.createLazyFile / the WasmFS fetch backend are worker-only; SDL3's Emscripten
  backend is main-thread DOM only, so the engine cannot run in a worker). The
  wasm build therefore preloads a compact per-map set via OPENRA3_WEB_ASSETS.
- New openra3 textures --map ID lists the loose terrain TGAs a map resolves to
  (terrain::resolve_texture_files, shared with load_textures_from_dir).
- Fixes: webgl heightmap used GL_R16 = 0x8229 (that is R8) -> upload rejected,
  terrain flattened to water; now 0x822A. Logger uses a stdout console sink on
  the web (stderr maps to console.error); a GL error check logs bad uploads.
- CI: wasm image + build jobs.
2026-09-28 22:57:19 +08:00
..

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 <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:

[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.log.<timestamp>, 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.<YYYYmmdd-HHMMSS> 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:

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.