Logging and Diagnostics for C++
Structured Logging • Assertions • Runtime Instrumentation
Vigil is a C++ logging and diagnostics library built around structured logging, assertions, stack traces, and runtime instrumentation.
It provides a consistent interface for recording application events, detecting failures early, and capturing rich context when things go wrong. Vigil supports C++17 and later on Linux, Windows, and macOS.
- Structured logging with
fmt-style formatting and compile-time level filtering. - Main and named loggers for subsystem-oriented diagnostics.
- Configurable log levels, console sinks, and rotating file sinks.
- One-shot and TTL-based rate limiting to prevent log spam.
- Assertion macros with source-location capture, formatted messages, and stack traces.
- Cross-platform stack trace capture and symbolication (DWARF on POSIX, PDB on Windows).
- RAII scope instrumentation with automatic entry/exit logging and elapsed time.
- Lifecycle hooks for observing log events without modifying the pipeline.
Choose the option that best fits your project:
add_subdirectory(vendor/vigil)
target_link_libraries(my_app PRIVATE vigil::vigil)Option 2: FetchContent (recommended for external dependencies)
include(FetchContent)
FetchContent_Declare(
vigil
GIT_REPOSITORY https://github.com/DMsuDev/vigil.git
GIT_TAG v0.5.0
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(vigil)
target_link_libraries(my_app PRIVATE vigil::vigil)find_package(Vigil CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE vigil::vigil)If CMake cannot locate VigilConfig.cmake:
cmake -S . -B build -DCMAKE_PREFIX_PATH="/path/to/vigil/install"| Option | Default | Description |
|---|---|---|
VIGIL_BUILD_EXAMPLES |
OFF |
Build example targets. |
VIGIL_BUILD_TESTS |
OFF |
Build test suite. |
VIGIL_INSTALL |
OFF |
Generate install targets and package metadata. |
VIGIL_USE_SYSTEM_FMT |
OFF |
Prefer system-installed fmt when available. |
VIGIL_BUILD_SHARED |
OFF |
Build Vigil as a shared library. |
VIGIL_ENABLE_STACK_TRACE |
ON |
Enable stack trace capture and symbolication. |
VIGIL_ENABLE_ASSERTS |
ON (Debug) |
Enable assertion macros with logging and stack traces. |
VIGIL_ENABLE_SCOPED_LOG |
OFF |
Enable RAII scope instrumentation macros. |
Example configure command:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DVIGIL_BUILD_TESTS=OFF \
-DVIGIL_BUILD_EXAMPLES=OFFLogging
Initialize the logging system and emit messages using the logging API or convenience macros with fmt style formatting.
#include <vigil/vigil.h>
int main()
{
vigil::LogSystem::Init({
.Name = "MyApp",
.LogDir = "logs",
.ConsoleLevel = vigil::LogLevel::Info,
});
vigil::Info("Application started: version {}.{}", 1, 0);
vigil::Warn("Config file not found, using defaults.");
vigil::Error("Failed to connect to {}:{}", "127.0.0.1", 5432);
VIGIL_DEBUG("Debug detail: x = {}", 42);
VIGIL_INFO("Build number: {:06}", 1337);
vigil::LogSystem::Shutdown();
return 0;
}| Level | Macro | Typical use |
|---|---|---|
Trace |
VIGIL_TRACE(...) |
Verbose internal flow. |
Debug |
VIGIL_DEBUG(...) |
Developer diagnostics. |
Info |
VIGIL_INFO(...) |
Normal operational events. |
Warn |
VIGIL_WARN(...) |
Recoverable anomalies. |
Error |
VIGIL_ERROR(...) |
Operation failures. |
Critical |
VIGIL_CRITICAL(...) |
System-level failures. |
[!IMPORTANT] Calling any logging function before
vigil::LogSystem::Init()triggers aVIGIL_ASSERTin debug builds and performs a silent no-op in release builds — arguments are never evaluated.
Named Loggers
Create per-subsystem loggers that share the main file sink or write to a dedicated file.
#include <vigil/vigil.h>
int main()
{
vigil::LogSystem::Init({
.Name = "Engine",
.LogDir = "logs",
});
// Simple named logger — shares the main file sink.
auto& net = vigil::LogSystem::Create("Network");
net.Info("Connected to server.");
net.Warn("Packet loss detected: {}%", 12);
// Named logger with a dedicated file and custom level.
vigil::LogSystem::Create({
.Name = "Physics",
.LogDir = "logs/physics",
.LogFile = "physics.log",
.FileMode = vigil::FileOpenMode::Truncate,
.FileLevel = vigil::LogLevel::Debug,
});
vigil::LogSystem::Get("Physics").Debug("Simulation step complete.");
// Log through a named logger via macro.
VIGIL_LOG_NAMED("Network", vigil::LogLevel::Error, "Disconnected from server.");
// Find() returns nullptr instead of throwing when a logger does not exist.
if (auto* audio = vigil::LogSystem::Find("Audio"))
audio->Info("Audio subsystem ready.");
vigil::LogSystem::Shutdown();
}| Function | Description |
|---|---|
LogSystem::Create(name) |
Creates or retrieves a named logger sharing the main file sink. |
LogSystem::Create(LogConfig) |
Creates or retrieves a named logger with a dedicated configuration. |
LogSystem::Get(name) |
Returns a named logger; throws if not found. |
LogSystem::Find(name) |
Returns a pointer to a named logger, or nullptr if not found. |
LogSystem::Remove(name) |
Removes a named logger and releases its sinks. |
LogSystem::SetMain(name) |
Promotes a named logger to replace the main logger. |
Rate-limited logging
Prevent log spam in high-frequency paths without manual state management.
#include <vigil/vigil.h>
#include <chrono>
#include <thread>
int main()
{
vigil::LogSystem::Init({ .Name = "App" });
for (int i = 0; i < 20; ++i)
{
// Once: emitted only once for this key.
vigil::LogOncePolicy::LogOnce(
"startup-notice",
vigil::LogLevel::Warn,
"Running without a config file.");
// TTL: emitted at most once every 500 ms.
vigil::LogTTLPolicy::LogTTL(
"heartbeat",
0.5,
vigil::LogLevel::Info,
"Service heartbeat OK.");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
vigil::LogSystem::Shutdown();
return 0;
}| Policy | Key | Behavior |
|---|---|---|
LogOncePolicy::LogOnce |
string key | Logs exactly once per key per process lifetime. |
LogTTLPolicy::LogTTL |
string key + TTL (seconds) | Logs at most once per TTL window. |
Assertions
Assertion macros evaluate conditions and, on failure, log the expression, source location, formatted message, and full stack trace before aborting the process.
#include <vigil/vigil.h>
int main()
{
vigil::LogSystem::Init({ .Name = "App" });
int value = 42;
int* ptr = &value;
VIGIL_ASSERT(value > 0);
VIGIL_ASSERT_MSG(value == 42, "Expected 42, got {}", value);
VIGIL_ASSERT_NOT_NULL(ptr);
VIGIL_ASSERT_IN_RANGE(value, 0, 100);
vigil::LogSystem::Shutdown();
return 0;
}A failing assertion produces output like:
[App] Assertion failed
Expression : value == 0
Location : src/main.cpp:12
Function : main()
Message : Expected 0, got 42.
Stack trace (3 frames):
#0 0x00005807BEF5FF26 in main() at src/main.cpp:12
#1 0x000076962942A601 in __libc_start_call_main() at libc_start_call_main.h:58
#2 0x000076962942A718 in __libc_start_main_impl() at libc-start.c:347
| Macro | Custom message | Release behavior | Use case |
|---|---|---|---|
VIGIL_ASSERT(check) |
— | Stripped out | Preconditions with no side-effects. |
VIGIL_ASSERT_MSG(check, ...) |
✓ | Stripped out | Preconditions requiring runtime context. |
VIGIL_ASSERT_NOT_NULL(ptr) |
— | Stripped out | Null pointer guards. |
VIGIL_ASSERT_IN_RANGE(val, min, max) |
— | Stripped out | Bounds validation. |
VIGIL_VERIFY(check) |
— | Condition evaluated | Side-effect expressions (e.g. file.close()). |
VIGIL_VERIFY_MSG(check, ...) |
✓ | Condition evaluated | Side-effect checks needing failure context. |
VIGIL_UNREACHABLE_ASSERT() |
— | UB hint | Unreachable switch defaults or code paths. |
[!NOTE]
VIGIL_ASSERT*macros are active whenVIGIL_ENABLE_ASSERTSis defined and are completely stripped in release builds.VIGIL_VERIFY*macros always evaluate their condition regardless of build type.
Scoped logging
Enable scoped logging with -DVIGIL_ENABLE_SCOPED_LOG=ON to instrument functions and code blocks with automatic entry/exit logging and elapsed time.
#include <vigil/vigil.h>
static void LoadAssets()
{
VIGIL_SCOPED_LOG_FUNCTION(); // instruments the entire function
{
VIGIL_SCOPED_LOG("Parsing manifest");
// ...
}
{
VIGIL_SCOPED_LOG("Uploading textures");
// ...
}
}Output:
[trace] >> void LoadAssets()
[trace] >> Parsing manifest
[trace] << Parsing manifest (15 ms)
[trace] >> Uploading textures
[trace] << Uploading textures (20 ms)
[trace] << void LoadAssets() (37 ms)
| Macro | Description |
|---|---|
VIGIL_SCOPED_LOG(name) |
RAII scope at Trace level. |
VIGIL_SCOPED_LOG_LEVEL(name, level) |
RAII scope at an explicit level. |
VIGIL_SCOPED_LOG_FUNCTION() |
RAII scope using the compiler function signature at Trace. |
VIGIL_SCOPED_LOG_FUNCTION_LEVEL(level) |
RAII scope using the compiler function signature at an explicit level. |
VIGIL_SCOPE_BEGIN(name) |
Opens a manual block scope at Trace level. |
VIGIL_SCOPE_BEGIN_LEVEL(name, level) |
Opens a manual block scope at an explicit level. |
VIGIL_SCOPE_END() |
Closes a manual block scope. |
[!NOTE] When
VIGIL_ENABLE_SCOPED_LOGis not defined, all macros expand to((void)0)and incur zero runtime overhead.VIGIL_SCOPE_BEGINandVIGIL_SCOPE_ENDexpand to bare{and}to preserve block structure.
Lifecycle Hooks
Observe logging events without modifying the pipeline — useful for in-app consoles, telemetry, or test assertions.
#include <vigil/vigil.h>
int main()
{
vigil::LogSystem::Init({ .Name = "App" });
vigil::LogSystem::SetHooks({
.OnMessage = [](const vigil::LogMessageEvent& e) {
// Mirror every message to an in-app console, telemetry sink, etc.
},
.OnLevelChange = [](const vigil::LevelChangeEvent& e) {
// React when any logger's severity level changes.
},
.OnFlush = [](const vigil::FlushEvent& e) {
// Notified after any flush operation.
},
.OnShutdown = [] {
// Called at the start of LogSystem::Shutdown().
},
});
vigil::Info("This message is observed by the hook.");
vigil::LogSystem::ClearHooks();
vigil::Info("This message is not.");
vigil::LogSystem::Shutdown();
return 0;
}| Function | Description |
|---|---|
LogSystem::SetHooks(LogHooks) |
Registers all hooks at once, replacing any previously set. |
LogSystem::SetOnMessage(cb) |
Registers a callback for every emitted message. |
LogSystem::SetOnLevelChange(cb) |
Registers a callback for logger level changes. |
LogSystem::SetOnFlush(cb) |
Registers a callback after any flush. |
LogSystem::SetOnShutdown(cb) |
Registers a callback at the start of shutdown. |
LogSystem::ClearHooks() |
Removes all registered hooks. |
Fully worked examples covering every feature are available under examples/:
| Example | Source | Covers |
|---|---|---|
| Logging | examples/logger_example.cpp |
Basic logging, named loggers, rate limiting, level control, hooks, flush. |
| Assertions | examples/assert_example.cpp |
All assertion macros, safe and intentional-failure cases, CLI dispatch. |
| Scoped logging | examples/scoped_example.cpp |
Function scope, nested scopes, explicit levels, early return, manual BEGIN/END. |
| Hooks | examples/hooks_example.cpp |
SetHooks, individual setters, ClearHooks, named logger level change events. |
Contributions are always welcome! ❤️ Whether you are reporting bugs, fixing issues, adding new examples, or improving the documentation, your help is appreciated.
Before opening a pull request:
- Keep pull requests focused: prefer small, atomic PRs that address a single feature or fix.
- Write clear commit messages using Conventional Commits.
- Ensure the project builds cleanly without introducing new compiler warnings.
For major changes or new features, please open an issue first to discuss what you would like to change.
Vigil is licensed under the MIT License. See the LICENSE file for more information.