Build types, debug logging, and crash handling in libpromeki.
This page explains the tools libpromeki provides for diagnosing problems at development time and in production. It covers:
PROMEKI_DEBUGlibpromeki defines three CMake build types. The build type controls optimisation level, debug symbols, and whether per-module debug logging (promekiDebug()) is compiled in.
| Build type | Optimisation | Debug symbols | promekiDebug() | NDEBUG | Typical use |
|---|---|---|---|---|---|
| Debug | -O0 | Yes | Compiled in | Not set | Step-through debugging, sanitizers |
| DevRelease (default) | -O3 | Yes (-g) | Compiled in | Set | Day-to-day development, CI, profiling |
| Release | -O3 | No | Compiled out | Set | Distribution / final packaging |
When no CMAKE_BUILD_TYPE is specified, the build defaults to DevRelease. This gives you release-level performance (-O3) plus two features that are invaluable during development and production troubleshooting:
-g) — so crash reports, core dumps, and profilers show meaningful function names and line numbers.promekiDebug() compiled in** — the per-module debug logging system is available and can be activated at runtime via the PROMEKI_DEBUG environment variable without recompiling.NDEBUG is still set, so assert() macros are disabled and the library runs at full speed.
The Debug build type disables optimisation entirely and enables debug symbols. Use it when you need to step through code in a debugger (GDB, LLDB) or run sanitizers (ASan, TSan, UBSan).
The Release build type enables full optimisation with no debug symbols and compiles out all promekiDebug() calls. Use it for final distribution builds where binary size and performance are paramount and debug logging overhead must be zero.
All logging in libpromeki goes through the Logger class. The logger is thread-safe and asynchronous — log messages are enqueued and written by a dedicated background thread so the calling thread is never blocked on I/O.
In practice you will almost always use the convenience macros rather than calling Logger methods directly:
| Macro | Level | When to use |
|---|---|---|
promekiDebug(fmt, ...) | Debug | Per-module diagnostic output (see Per-Module Debug Logging) |
promekiInfo(fmt, ...) | Info | Normal operational messages |
promekiWarn(fmt, ...) | Warn | Recoverable problems worth investigating |
promekiErr(fmt, ...) | Err | Errors that affect correctness |
All macros accept printf-style format strings:
Every log line automatically includes a timestamp, source file and line number, log level, and the name of the calling thread.
The default logger writes to the console (stderr). You can also direct output to a file and adjust the minimum log level:
You can also install custom formatters for file and console output:
Because logging is asynchronous, the log queue may contain unwritten messages when your program exits or when you need to inspect a log file mid-run. Use sync() to block until the queue is drained:
The promekiDebug() macro provides fine-grained, per-module debug logging that can be compiled in but only activated at runtime for the modules you care about. This lets you instrument the library heavily without any runtime cost until you need it.
Each source file that wants to emit debug output places the PROMEKI_DEBUG macro near the top, inside the promeki namespace:
The PROMEKI_DEBUG(Name) macro registers the module name with the debug database. At startup, the library checks the PROMEKI_DEBUG environment variable and enables logging for any module whose name appears in the comma-separated list.
Set the PROMEKI_DEBUG environment variable to a comma-separated list of module names:
When enabled, promekiDebug() messages appear alongside normal log output at the Debug level. When disabled, the module's promekiDebug() calls skip the logging call entirely — just a branch on a local bool.
In a Release build, PROMEKI_DEBUG_ENABLE is not defined, so the promekiDebug() macro expands to nothing. The compiler eliminates all debug logging calls entirely — zero overhead.
If you set the PROMEKI_DEBUG environment variable in a Release build, the library will print a warning:
Two companion macros let you time sections of code, gated on the same per-module enable flag:
Like promekiDebug(), these are compiled out in Release builds and only run when the module's debug flag is enabled.
The CrashHandler class installs signal handlers for five fatal POSIX signals: SIGSEGV, SIGABRT, SIGBUS, SIGFPE, and SIGILL. When a crash occurs, it writes a detailed report to stderr and to a log file, then re-raises the signal so the OS can generate a core dump.
SIGSEGV)/proc/self/maps on Linux)MemSpace allocation countersThe crash log is written to:
The directory can be overridden via LibraryOptions::CrashLogDir or the PROMEKI_OPT_CrashLogDir environment variable.
If your program uses the Application class, crash handlers are installed automatically. No extra code is needed:
The Application constructor checks LibraryOptions::CrashHandler (default true). To disable it, set the option before constructing Application or via the environment:
If your program does not use Application (e.g. a library consumer that has its own main loop), you can install the crash handler directly:
install() is safe to call multiple times — subsequent calls refresh the snapshotted process state (hostname, thread list, MemSpace entries, etc.) without stacking signal handlers.
The crash handler snapshots process state at install() time so it can write the report from inside a signal handler without calling non-signal-safe functions. If your application registers new MemSpace entries or changes the app name after startup, call Application::refreshCrashHandler() (or just call CrashHandler::install() again) to update the snapshot:
You can produce the same report a crash would generate — without actually crashing — using CrashHandler::writeTrace():
Each call writes to a unique file:
This is useful for capturing the state of a long-running process without stopping it.
By default the crash handler does not modify the core dump resource limit. To enable core dumps, set LibraryOptions::CoreDumps to true:
When enabled, install() raises RLIMIT_CORE to the hard limit so the kernel generates a core file on crash. Combined with the -g debug symbols from DevRelease builds, you get a core dump that GDB/LLDB can make full use of.
All crash-related options can be set via code or environment variables:
| Option | Env variable | Type | Default | Description |
|---|---|---|---|---|
LibraryOptions::CrashHandler | PROMEKI_OPT_CrashHandler | bool | true | Install crash signal handlers |
LibraryOptions::CoreDumps | PROMEKI_OPT_CoreDumps | bool | false | Raise RLIMIT_CORE for core dumps |
LibraryOptions::CrashLogDir | PROMEKI_OPT_CrashLogDir | String | (empty = temp dir) | Directory for crash/trace log files |
LibraryOptions::CaptureEnvironment | PROMEKI_OPT_CaptureEnvironment | bool | true | Include env vars in crash reports |
When PROMEKI_ENABLE_HTTP is on (the default), Application can embed a lightweight HTTP debug server that exposes diagnostic information over a browser-friendly REST API and a baked-in web UI.
Set PROMEKI_DEBUG_SERVER to a host:port spec before launching your application. The constructor picks it up automatically — no code changes required:
Parse or bind failures are logged at warn level and are never fatal; the application continues without a debug server.
Then open http://localhost:8085/ in a browser — it redirects to the built-in debug UI at /promeki/debug/.
startDebugServer() returns Error::AlreadyOpen if the server is already listening. Call stopDebugServer() first to rebind.
Applications that own their own HttpServer can skip Application integration and cherry-pick individual module installers from <promeki/debugmodules.h>:
Or use the full convenience wrapper on a dedicated server:
All endpoints are mounted under /promeki/debug/api by default.
| Endpoint | Method | Description |
|---|---|---|
/promeki/debug/api/build | GET | Build metadata, features, platform info |
/promeki/debug/api/env | GET | Full process environment as a JSON object |
/promeki/debug/api/options/_schema | GET | LibraryOptions key schema (read-only) |
/promeki/debug/api/memory | GET | Live MemSpace allocation counters |
/promeki/debug/api/logger | GET | Logger state: level, console, debug channels |
/promeki/debug/api/logger/level | PUT | Set log level — body {"level": <0-4>} |
/promeki/debug/api/logger/debug/{name} | PUT | Toggle a debug channel — body {"enabled": <bool>} |
/promeki/debug/api/logger/stream | WS | Stream log entries as JSON frames |
The debug server's WebSocket log stream is built on a general-purpose listener API exposed by Logger. Applications can install their own listeners to consume log entries programmatically:
removeListener() blocks until the worker thread has acknowledged removal, so it is safe to destroy state captured by the lambda immediately after it returns.
The history ring size (default 1024) controls how many entries are available for replay:
| Variable | Purpose | Example |
|---|---|---|
PROMEKI_DEBUG | Enable per-module debug logging | PROMEKI_DEBUG=ThreadPool,MediaIO |
PROMEKI_DEBUG_SERVER | Start the debug HTTP server | PROMEKI_DEBUG_SERVER=:8085 |
PROMEKI_OPT_CrashHandler | Enable/disable crash handlers | PROMEKI_OPT_CrashHandler=false |
PROMEKI_OPT_CoreDumps | Enable core dumps | PROMEKI_OPT_CoreDumps=true |
PROMEKI_OPT_CrashLogDir | Override crash log directory | PROMEKI_OPT_CrashLogDir=/var/log/myapp |
PROMEKI_OPT_CaptureEnvironment | Include env in crash reports | PROMEKI_OPT_CaptureEnvironment=false |