- ra3.dx: D3D11 and D3D12 presentation backends (runtime HLSL via d3dcompiler); 2D image blit and the GPU heightfield terrain raymarch, with the corner minimap/FPS overlays. Non-Windows builds link a null fallback. - ra3.display: preferred backend plus ordered fallback (Vulkan/D3D11/D3D12/SDL); menu gains a Renderer option and the CLI gains --dx11/--dx12/--sdl, which the render command now honours too. - vendor libenderlog (MIT): every run writes openra3.log next to the exe and archives the previous run's log; records at warn and above carry a call stack (native fallback, since libc++ has no <stacktrace>). - Windows crash reporter writes openra3_crash.log (faulting module + backtrace); D3D/DXGI diagnostics are routed through the logger.
171 lines
5.9 KiB
Markdown
171 lines
5.9 KiB
Markdown
# 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
|
|
|
|
```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
|
|
`<unknown>`.
|
|
|
|
## 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 <unknown>
|
|
#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.<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:
|
|
|
|
```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).
|