LogIt++ is a macro-first C++ logging library. The core and most built-in backends support C++11; OTLP, Prometheus HTTP server, and MDBX integrations require C++17. It pairs lightweight instrumentation macros with configurable backends (console, rotating files, memory, system/crash loggers, OTLP, Prometheus, MDBX, or custom sinks). Most general-purpose native backends are asynchronous by default, while specialized backends may be synchronous or own their own worker and queue.
Key characteristics:
- Macro-oriented API. Consistent macro families (
LOGIT_<LEVEL>,LOGIT_PRINTF_<LEVEL>,LOGIT_STREAM_<LEVEL>, etc.) cover immediate messages, printf-style formatting, streaming, throttling, and tagging. DefiningLOGIT_SHORT_NAMEwhen including<logit.hpp>enables compact aliases likeLOG_I,LOG_WPF, andLOG_S_INFO. - Flexible formatting and routing. Customize output patterns, mix console, file, system, telemetry, and storage backends, or supply custom logger implementations.
- Configurable delivery. General-purpose native backends use asynchronous queues by default; queue limits, overflow policies, dedicated executors, and synchronous modes are configurable per backend where supported.
Normal usage goes through the public LOGIT_* / LOG_* macro families. Pick
the family that matches your logging style: plain, printf, stream,
conditional, targeted, or scope-based.
The library ships with self-contained umbrella headers that provide a predictable include order:
| Entry point | Purpose |
|---|---|
<logit.hpp> |
Brings in configuration macros, enums, utilities, formatters, loggers, the singleton Logger and public macros. |
<logit/utils.hpp> |
Aggregates everything in logit/utils/, including LogRecord, formatting helpers and argument utilities. |
<logit/formatter.hpp> |
Provides formatter interfaces and the default pattern compiler implementation. |
<logit/loggers.hpp> |
Exposes the logger backends and prepares their internal dependencies. |
Most application code should include <logit.hpp>. If you work directly with a module class and need a leaf header, follow the Nearest Header Requirement (NHR): include the matching umbrella first and then the specific header, e.g. for direct use of MemoryLogger:
#include <logit/loggers.hpp>
#include <logit/loggers/MemoryLogger.hpp>Internal headers under logit/detail/ are private implementation details and should not be included directly by consumers.
See the macro examples below or browse the examples/ folder for focused demonstrations, including queue tuning and crash handling.
Recent focused examples include:
examples/example_logit_memory_logger.cpp- in-memory snapshots plus sharedILogReaderandILogSubscribermacros.examples/example_logit_mdbx_logger.cpp- persistent MDBX storage plus the same shared read/callback macros and MDBX-specific session/payload APIs.examples/example_logit_otlp_http.cpp- OTLP/HTTP export with batching, retries, optional compression, and contextual trace/span fields.examples/example_logit_prometheus_payload.cpp- callback-based Prometheus payload emission with custom registry metrics.examples/example_logit_prometheus_server.cpp- embedded/metricsendpoint with built-in and application metrics.examples/example_logit_mdc_ndc.cpp- mapped and nested diagnostic context across scopes and threads.
Detailed guides and documentation map:
-
docs/quickstart.md— quick start and documentation map. -
docs/installation.md— CMake, vendored, installed-package, and package-manager setup. -
docs/backends.md— backend, platform, dependency, and packaging matrix. -
docs/benchmarks.md— benchmark methodology and historical snapshot. -
docs/comparison.md— comparison with spdlog, Quill, Boost.Log, glog, and IceCream-Cpp. -
docs/OtlpHttpLogger.md— OTLP/HTTP and callback exporters, structured attributes, retries, splitting, and compression. -
docs/PrometheusLogger.md— payload/server backends, registry metrics, scrape configuration, and limitations. -
docs/TaskExecutor.md— queue variants, overflow policies, hot resize, and lifecycle guarantees. -
docs/backpressure.md— application-facing queue tuning and drop counters.
#include <logit.hpp>
int main() {
LOGIT_ADD_CONSOLE_DEFAULT();
LOGIT_SET_MAX_QUEUE(32);
LOGIT_SET_QUEUE_POLICY(LOGIT_QUEUE_DROP);
const bool verbose = true;
int attempt = 1;
double latency_ms = 12.5;
LOGIT_TRACE0();
LOGIT_DEBUG_IF(verbose, "Verbose diagnostics enabled");
LOGIT_INFO("Starting service", attempt);
LOGIT_WARN_ONCE("initializing subsystem");
LOGIT_ERROR_EVERY_N(3, "retrying connection", attempt);
LOGIT_ERROR_THROTTLE(250, "still failing");
LOGIT_PRINTF_WARN("Latency %.2f ms", latency_ms);
LOGIT_FORMAT_INFO("%.2f", 1.23f, 4.56f);
LOGIT_INFO_TAG(({{"order_id", 123}, {"side", "BUY"}}), "sent order");
LOGIT_STREAM_INFO() << "Streaming value: " << attempt;
LOGIT_WAIT();
}Define LOGIT_SHORT_NAME before including <logit.hpp> to enable single-letter level prefixes:
#define LOGIT_SHORT_NAME
#include <logit.hpp>
void short_names_demo() {
LOGIT_ADD_CONSOLE_DEFAULT(); // call once during initialization
int attempt = 2;
LOG_I("Short alias for info");
LOG_IPF("Attempt %d finished", attempt);
LOG_W("Warning alias");
LOG_WPF("Retry %d/3", attempt);
LOG_S_INFO() << "Streaming alias " << attempt;
}For a standalone program that brings everything together and intentionally aborts after logging a fatal message, check examples/example_logit_minimal_crash.cpp.
Mapped diagnostic context (MDC) stores thread-local key-value pairs, while nested
diagnostic context (NDC) stores a thread-local stack of scope names. The context
is available when the library is configured with -DLOGIT_WITH_CONTEXT=ON.
When this option is off, LogRecord keeps the original hot-path shape and the
context macros become no-ops.
With context enabled, a LogRecord captures a shared snapshot only when the
current thread has non-empty MDC or NDC values.
#include <logit.hpp>
int main() {
LOGIT_ADD_LOGGER(
logit::ConsoleLogger, (),
logit::SimpleLogFormatter,
("[%T] request=%K{request_id} ndc=[%J] %v")
);
LOGIT_MDC_PUT("request_id", "req-42");
LOGIT_NDC_PUSH("checkout");
{
LOGIT_NDC_GUARD("payment");
LOGIT_INFO("charge started");
}
LOGIT_MDC_CLEAR();
LOGIT_NDC_CLEAR();
LOGIT_WAIT();
}LOGIT_SYSERR_<LEVEL> captures the current errno (or GetLastError() on Windows) and appends the decoded information to the message, so failure details stay attached to the original context. The lower-level LOGIT_PERROR_<LEVEL> and LOGIT_WINERR_<LEVEL> families are also available if you want to explicitly choose the platform macro.
#include <logit.hpp>
#include <fcntl.h>
int main() {
LOGIT_ADD_CONSOLE_DEFAULT();
if (::open("missing.cfg", O_RDONLY) == -1) {
LOGIT_SYSERR_ERROR("Failed to open configuration");
}
LOGIT_WAIT();
}The error suffix can be customised at compile time via config.hpp macros:
#define LOGIT_OS_ERROR_JOIN " <- "
#define LOGIT_POSIX_ERROR_PATTERN "[%s] errno=%d (%s)"
#define LOGIT_WINDOWS_ERROR_PATTERN "[%s] GetLastError=%lu (%s)"
#include <logit.hpp>
// ... later in the code ...
LOGIT_SYSERR_ERROR("Deleting temp directory failed");The public macro surface also includes targeted logger variants, conditional variants, and RAII scope timers:
#include <logit.hpp>
int main() {
LOGIT_ADD_CONSOLE_DEFAULT(); // index 0
LOGIT_ADD_UNIQUE_FILE_LOGGER_DEFAULT_SINGLE_MODE(); // index 1
const bool verbose = true;
int retry = 3;
LOGIT_INFO_TO(1, "write directly to the single-mode logger");
LOGIT_PRINTF_INFO_IF(verbose, "retry=%d", retry);
LOGIT_SCOPE_INFO("load_config");
LOGIT_SCOPE_PRINTF_WARN_T(10, "slow step %d", retry);
LOGIT_WAIT();
}For remote control panels, diagnostics endpoints, or operator tooling, you can register a dedicated in-memory backend and fetch the latest buffered logs by logger index.
#include <logit.hpp>
int main() {
LOGIT_ADD_MEMORY_LOGGER_SINGLE_MODE(1000, 1024 * 1024, 24LL * 60 * 60 * 1000); // index 0
LOGIT_INFO_TO(0, "remote-ready info");
LOGIT_WARN_TO(0, "latest warning");
const auto lines = LOGIT_GET_BUFFERED_STRINGS(0);
const auto entries = LOGIT_GET_BUFFERED_ENTRIES(0);
const auto level = LOGIT_GET_LOG_LEVEL(0);
(void)lines;
(void)entries;
(void)level;
}MemoryLogger snapshots are returned in chronological order. The retention
budget uses max_bytes as buffered formatted-message payload bytes, not the
full object footprint of each BufferedLogEntry. Snapshot reads are lightweight
at the Logger layer and do not take the per-backend execution mutex, but they
still synchronize on the memory backend's own mutex while copying the current
buffer.
Use ILogReader and ILogSubscriber when application code should work with
either MemoryLogger or MdbxLogger. The shared macro helpers return
LogRecordSnapshot records and avoid depending on backend-specific storage:
#include <logit.hpp>
int main() {
// This can be a MemoryLogger index or an MdbxLogger index.
const int backend_index = 0;
const int64_t now_ms = LOGIT_CURRENT_TIMESTAMP_MS();
const auto recent = LOGIT_READ_RECENT_ASC(backend_index, 100, 0);
const auto window = LOGIT_READ_RANGE(
backend_index,
now_ms - 60LL * 60 * 1000,
now_ms + 1,
0);
std::vector<logit::LogRecordSnapshot> live_updates;
const uint64_t callback_id = LOGIT_ADD_LOG_CALLBACK(
backend_index,
([&live_updates](const logit::LogRecordSnapshot& record) {
live_updates.push_back(record);
}));
LOGIT_INFO_TO(backend_index, "visible through read and callback APIs");
LOGIT_WAIT();
LOGIT_REMOVE_LOG_CALLBACK(backend_index, callback_id);
(void)recent;
(void)window;
(void)live_updates;
}LOGIT_READ_RANGE, LOGIT_READ_RECENT_ASC, and
LOGIT_READ_RECENT_DESC use ILogReader. LOGIT_ADD_LOG_CALLBACK and
LOGIT_REMOVE_LOG_CALLBACK use ILogSubscriber; callbacks receive a
LogRecordSnapshot after the backend has written the record. Snapshots own
their string fields, so they can be copied or stored by value. Callback
dispatch follows registration order. This is the preferred fallback-friendly
API between MemoryLogger and MdbxLogger.
LOGIT_GET_BUFFERED_STRINGS and LOGIT_GET_BUFFERED_ENTRIES are convenience
helpers for the MemoryLogger snapshot buffer. They are useful for local
diagnostics panes, but code that should switch between in-memory and MDBX
storage should prefer the shared LOGIT_READ_* and callback macros above.
File-based backends also expose persisted-file access through
LOGIT_LIST_LOG_FILES(index), LOGIT_READ_LOG_FILE(index, path), and
LOGIT_READ_LOG_FILES(index, paths). These helpers read only what has already
reached disk, do not drain async queues, and currently treat compressed rotated
files as metadata-only entries. Use MemoryLogger for near-real-time snapshots
and the file APIs for operational reads of today's or previous days' persisted
logs.
The asynchronous TaskExecutor supports both a mutex-protected deque and an
optional lock-free MPSC ring (enable via LOGIT_USE_MPSC_RING). The same queue
policy names (Block, DropNewest, DropOldest) are available in both
implementations, but DropOldest has intentionally different semantics: the
deque removes the oldest accepted task, while MPSC drops the incoming task to
keep accepted work ordered. The ring build also allows
"hot" queue resizes where producers briefly wait while the worker rebuilds the
ring buffer without losing in-flight tasks. The default MPSC buffer holds
LOGIT_TASK_EXECUTOR_DEFAULT_RING_CAPACITY tasks (1024 by default) and can be
retuned by combining LOGIT_SET_MAX_QUEUE(...) with the compile-time macro if
your workload needs a different baseline. See
docs/TaskExecutor.md for a full breakdown and tuning
tips.
Logger backend Config structs can opt into use_dedicated_executor=true when
one slow sink must not delay other async loggers. On native builds this creates
one worker thread per configured logger, so use it deliberately for expensive or
isolated backends. Single-threaded Emscripten builds keep the same per-instance
queue semantics but drain cooperatively on the browser event loop.
You can always pass a configured backend through the generic macro:
logit::ConsoleLogger::Config cfg;
cfg.async = true;
cfg.use_dedicated_executor = true;
cfg.queue_capacity = 1024;
cfg.queue_policy = logit::QueuePolicy::Block;
LOGIT_ADD_LOGGER(
logit::ConsoleLogger,
(cfg),
logit::SimpleLogFormatter,
(LOGIT_CONSOLE_PATTERN)
);Built-in helpers also expose config-first and short dedicated forms:
LOGIT_ADD_CONSOLE_CONFIG(cfg, LOGIT_CONSOLE_PATTERN);
LOGIT_ADD_CONSOLE_DEDICATED(
LOGIT_CONSOLE_PATTERN,
1024,
logit::QueuePolicy::DropNewest
);- Flexible Log Formatting:
Customize log message formats using patterns. You can redefine patterns via macros or specify them directly when adding a logger backend. Both standard format flags (%H, %M, %S, %v, etc.) and special ones like %N([...]) for fallback logs without arguments are supported.
#define LOGIT_CONSOLE_PATTERN "%H:%M:%S.%e | %^%N([%!g:%#])%v%$"
try {
throw std::runtime_error("An example runtime error");
} catch (const std::exception& ex) {
LOGIT_FATAL(ex);
}
// Output:
> 23:59:59.128 | An example runtime error
- Macro-Based Logging:
Easily log variables and messages using macros. Choose the macro that matches the desired formatting style:
LOGIT_PRINTF_<LEVEL>mimicsprintf, where the format string controls each argument.LOGIT_FORMAT_<LEVEL>applies the same format to every argument in the list.
float someFloat = 123.456f;
int someInt = 789;
LOGIT_INFO(someFloat, someInt);
auto now = std::chrono::system_clock::now();
LOGIT_PRINT_INFO("TimePoint example: ", now);
LOGIT_PRINTF_INFO("%.2f %d", someFloat, someInt); // printf-style
LOGIT_FORMAT_INFO("%.2f", someFloat, 654.321f); // same format for all args
- Log Filters and Throttling:
Reduce noise from repetitive messages with macros like LOGIT_WARN_ONCE,
LOGIT_INFO_EVERY_N, and LOGIT_ERROR_THROTTLE. Use the _THROTTLE
variants (e.g., LOGIT_INFO_THROTTLE) to limit output to one message per
time period.
for (int i = 0; i < 10; ++i) {
LOGIT_WARN_ONCE("initializing"); // prints once
LOGIT_INFO_EVERY_N(3, "heartbeat", i); // every 3rd call
LOGIT_ERROR_THROTTLE(200, "repeated error"); // max once/200ms
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}- Tagged Logging:
Attach simple key-value attributes for easier filtering in log aggregators.
LOGIT_INFO_TAG(({{"order_id", 123}, {"side", "BUY"}}), "sent order");
// Output: [info] sent order order_id=123 side=BUY- Raw and Section Logging:
Write human-readable diagnostic snapshots without level or formatter prefixes. Raw records bypass log-level filters, but still use the configured backends, queues, file rotation, and targeted logger routing.
LOGIT_SECTION("App");
LOGIT_RAW("Name: sample.desktop.app");
LOGIT_RAW("Version: 3.3.106.wzr");
LOGIT_SECTION("Proxy");
LOGIT_RAW("Proxy enabled: False");- Diagnostic Context (MDC/NDC):
Enable LOGIT_WITH_CONTEXT to attach mapped and nested diagnostic context to
records and format it with %K, %K{key}, and %J.
LOGIT_MDC_PUT("request_id", "req-42");
LOGIT_NDC_GUARD("checkout");
LOGIT_INFO("processing order");- Structured and telemetry backends:
Optional LOGIT_WITH_MDBX persists structured records and payloads through
mdbx-containers. LOGIT_WITH_OTLP enables OTLP exporters: OtlpHttpLogger
sends HTTP requests through kurlyk, while OtlpPayloadLogger delivers serialized
payloads through a callback. LOGIT_WITH_PROMETHEUS /
LOGIT_WITH_PROMETHEUS_SERVER provide callback and embedded /metrics
backends. See the dedicated guides above for setup and platform/package
limitations.
- Console stream routing and cleanup:
ConsoleLogger::Config::routes can route level ranges to std::cout,
std::cerr, or a caller-owned stream. LOGIT_CLEAR_LOGGER and
LOGIT_CLEAR_ALL_LOGGERS clear supported in-memory or persisted records and
return a LogClearResult describing the outcome.
-
Rotating File Logs:
Automatic file rotation based on size with optional asynchronous compression using gzip or zstd.
-
Support for Multiple Backends:
Easily configure loggers for console and file output. If necessary, add support for sending messages to servers or databases by creating custom backends.
// Adding three backends: console, file, and unique file loggers
LOGIT_ADD_CONSOLE_DEFAULT();
LOGIT_ADD_FILE_LOGGER_DEFAULT();
LOGIT_ADD_UNIQUE_FILE_LOGGER_DEFAULT_SINGLE_MODE();
- System Backends:
Use the host OS logging facility. SyslogLogger works with POSIX syslog, while EventLogLogger writes to the Windows Event Log.
- Asynchronous Logging:
Most general-purpose native backends are asynchronous by default. Crash
backends and PrometheusPayloadLogger are synchronous. OTLP HTTP and payload
exporters own their queues and workers and can be configured for synchronous or
asynchronous delivery. Dedicated executors create one worker per selected
backend, and Emscripten without pthreads drains cooperatively without OS worker
threads.
- Stream-Based Logging:
Use stream operators for complex messages.
LOGIT_STREAM_INFO() << "Stream-based info logging with short macro. Integer value: " << 123;
- Extensibility:
Create custom loggers and formatters to meet your specific requirements. See Custom Logger Backend and Formatter below for a complete implementation that matches the current interfaces.
Here’s a simple example demonstrating how to use LogIt++ in your application. The task queue size and overflow behavior are configurable via LOGIT_SET_MAX_QUEUE and LOGIT_SET_QUEUE_POLICY (use LOGIT_QUEUE_DROP or LOGIT_QUEUE_BLOCK):
#define LOGIT_SHORT_NAME
#include <logit.hpp>
int main() {
// Initialize the logger with default console output
LOGIT_ADD_CONSOLE_DEFAULT();
LOGIT_SET_MAX_QUEUE(64);
LOGIT_SET_QUEUE_POLICY(LOGIT_QUEUE_DROP);
float a = 123.456f;
int b = 789;
int c = 899;
const char* someStr = "Hello, World!";
// Basic logging using macros
LOG_INFO("Starting the application");
LOG_DEBUG("Variable values", a, b);
LOG_WARN("This is a warning message");
// Formatted logging
LOG_PRINTF_INFO("Formatted log: value of a = %.2f", a);
LOG_FORMAT_WARN("%.4d", b, c);
// Error and fatal logs
LOG_ERROR("An error occurred", b);
LOG_FATAL("Fatal error. Terminating application.");
// Conditional logging
LOG_ERROR_IF(b < 0, "Value of b is negative");
LOG_WARN_IF(a > 100, "Value of a exceeds 100");
// Stream-based logging with short and long names
LOG_S_INFO() << "Logging a float: " << a << ", and an int: " << b;
LOG_S_ERROR() << "Error occurred in the system";
LOGIT_STREAM_WARN() << "Warning: potential issue detected with value: " << someStr;
// Using LOGIT_TRACE for tracing function execution
LOGIT_TRACE0(); // Trace without arguments
LOG_PRINT_TRACE("Entering main function with variable a =", a);
// Wait for all asynchronous logs to be processed
LOGIT_WAIT();
return 0;
}For more usage examples, please refer to the examples folder in the repository, where you can find detailed demonstrations of various logging scenarios and configurations.
You can exclude lower-severity logs from the binary by specifying the minimum
severity compiled into the program. Define the LOGIT_COMPILED_LEVEL macro
during compilation:
g++ -DLOGIT_COMPILED_LEVEL=LOGIT_LEVEL_WARN ...With the example above, TRACE, DEBUG, and INFO macros are turned into no-ops at compile time.
Runtime level changes via LOGIT_SET_LOG_LEVEL(...) and
LOGIT_SET_LOG_LEVEL_TO(...) still work for compiled-in severities, but they
cannot re-enable log statements that were removed by LOGIT_COMPILED_LEVEL.
LogIt++ supports customizable log message formatting using patterns that define how each message should appear. You can specify patterns through macros or provide them when adding logger backends.
Example of setting a custom format for the console logger:
LOGIT_ADD_LOGGER(
logit::ConsoleLogger, (),
logit::SimpleLogFormatter,
("%Y-%m-%d %H:%M:%S.%e [%l] %^%N(%g:%#)%v%$")
);
Or specify the pattern using macros:
#define LOGIT_CONSOLE_PATTERN "%H:%M:%S.%e | %^%N([%!g:%#])%v%$"
LOGIT_ADD_CONSOLE_DEFAULT();
The logger will automatically substitute the specified data into the template, for example:
23:59:59.128 | path/to/file.cpp:123 A sample log message
LogIt++ supports customizable log message formatting using flags. You can define how each log message should appear by including placeholders for various data, such as timestamps, log levels, file names, function names, and messages.
Below is a list of supported formatting flags:
-
Date and Time Flags:
%Y: Year (e.g., 2024)%m: Month (01-12)%d: Day of the month (01-31)%H: Hour (00-23)%M: Minute (00-59)%S: Second (00-59)%e: Millisecond (000-999)%C: Two-digit year (e.g., 24 for 2024)%c: Full date and time (e.g., Mon Oct 4 12:45:30 2024)%D: Short date (e.g., 10/04/24)%T,%X: Time in ISO 8601 format (e.g., 12:45:30)%F: Date in ISO 8601 format (e.g., 2024-10-04)%s,%E: Unix timestamp in seconds%ms: Unix timestamp in milliseconds%b: Abbreviated month name (e.g., Jan)%B: Full month name (e.g., January)%a: Abbreviated weekday name (e.g., Mon)%A: Full weekday name (e.g., Monday)
-
Log Level Flags:
%l: Full log level (e.g., INFO, ERROR)%L: Short log level (e.g., I for INFO, E for ERROR)
-
File and Function Flags:
%f,%fn,%bs: Base name of the source file%g,%ffn: Full file path%#: Line number%!: Function name
-
Thread Flags:
%t: Thread identifier
-
Diagnostic Context Flags:
%K: All mapped diagnostic context values askey=valuepairs%K{key}: One mapped diagnostic context value by key%J: Nested diagnostic context stack
-
Color Flags:
%^: Start color formatting%$: End color formatting%SC: Start removing color codes (Strip Color)%EC: End removing color codes (End Color)
-
Message Flags:
%v: The log message content%N(...): Used as a fallback when no arguments are provided (e.g., inLOG_TRACE0()calls). The pattern specified in parentheses will be used. Example:%N(%g:%#)will add the file name and line number if no message is provided.
LogIt++ allows message text formatting with width, alignment, and truncation:
-
Alignment:
- Left: Use the
-sign before the width number, e.g.,%-10v. - Center: Use the
=sign before the width number, e.g.,%=10v. - Right (default):
%10v.
- Left: Use the
-
Truncation:
- The
!symbol after the width number specifies that text should be truncated if it exceeds the specified length. Example:%10!v.
- The
Examples:
%10v– Right-align the message to 10 characters.%-10v– Left-align the message to 10 characters.%10!v– Truncate the message to 10 characters with right alignment.%-10!v– Truncate the message to 10 characters with left alignment.
For file-related flags (%f, %g, %@), truncation ensures that the filename and the beginning of the path are preserved, replacing the middle portion with ... if the width is smaller than the path length.
Example:
- Input:
/very/long/path/to/file.cpp - Truncated to width=15:
/very...file.cpp
LogIt++ provides shortened versions of logging macros when LOGIT_SHORT_NAME is defined. These macros allow for concise logging across different log levels, including both standard and stream-based logging.
-
Basic logging:
LOG_T(...): Logs a TRACE-level message.LOG_T0(): Logs a TRACE-level message without arguments.LOG_0T(): Alias forLOG_T0().LOG_0_T(): Alias forLOG_T0().LOG_T_NOARGS(): Alias forLOG_T0().LOG_NOARGS_T(): Alias forLOG_T0().
-
Formatted logging:
LOG_TF(fmt, ...): Logs a formatted TRACE-level message using format strings.LOG_FT(fmt, ...): Alias forLOG_TF(fmt, ...).LOG_T_PRINT(...): Logs a TRACE-level message by printing each argument.LOG_PRINT_T(...): Alias forLOG_T_PRINT(...).LOG_T_PRINTF(fmt, ...): Logs a formatted TRACE-level message using printf-style formatting.LOG_PRINTF_T(fmt, ...): Alias forLOG_T_PRINTF(fmt, ...).LOG_TP(...): Alias forLOG_T_PRINT(...).LOG_PT(...): Alias forLOG_T_PRINT(...).LOG_TPF(fmt, ...): Alias forLOG_T_PRINTF(fmt, ...).LOG_PFT(fmt, ...): Alias forLOG_T_PRINTF(fmt, ...).
-
Alternative TRACE-level macros:
LOG_TRACE(...): Logs a TRACE-level message (same asLOG_T(...)).LOG_TRACE0(): Logs a TRACE-level message without arguments (same asLOG_T0()).LOG_0TRACE(): Alias forLOG_TRACE0().LOG_0_TRACE(): Alias forLOG_TRACE0().LOG_TRACE_NOARGS(): Logs a TRACE-level message with no arguments (same asLOG_T_NOARGS()).LOG_NOARGS_TRACE(): Alias forLOG_TRACE_NOARGS().LOG_TRACEF(fmt, ...): Logs a formatted TRACE-level message (same asLOG_TF(fmt, ...)).LOG_FTRACE(fmt, ...): Alias forLOG_TRACEF(fmt, ...).LOG_TRACE_PRINT(...): Logs a TRACE-level message by printing each argument (same asLOG_T_PRINT(...)).LOG_PRINT_TRACE(...): Alias forLOG_TRACE_PRINT(...).LOG_TRACE_PRINTF(fmt, ...): Logs a formatted TRACE-level message using printf-style formatting (same asLOG_T_PRINTF(fmt, ...)).LOG_PRINTF_TRACE(fmt, ...): Alias forLOG_TRACE_PRINTF(fmt, ...).
These macros provide flexibility and convenience when logging messages at the TRACE level. They allow you to choose between different logging styles, such as standard logging, formatted logging, and printing each argument separately.
Note: Similar macros are available for other log levels — INFO (LOG_I, LOG_INFO), DEBUG (LOG_D, LOG_DEBUG), WARN (LOG_W, LOG_WARN), ERROR (LOG_E, LOG_ERROR), and FATAL (LOG_F, LOG_FATAL). The naming conventions are consistent across levels, you only need to replace the level letter or word in the macro name.
- Example:
LOG_T("Trace message using short macro");
LOG_TF("%.4d", 999);
LOG_T_PRINT("Printing trace message with multiple variables: ", var1, var2);
LOG_TRACE("Trace message (alias for LOG_T)");
LOG_TRACE_PRINTF("Formatted trace: value = %d", value);LogIt++ provides several macros that allow for customization and configuration. Below are the available configuration macros:
- LOGIT_BASE_PATH: Defines the base path used for log file paths. If
LOGIT_BASE_PATHis not defined or is empty ({}), the full path from__FILE__will be used for log file paths. You can override this to specify a custom base path for your log files.
#define LOGIT_BASE_PATH "/path/to/your/project"- LOGIT_DEFAULT_COLOR: Defines the default color for console output. If
LOGIT_DEFAULT_COLORis not defined, it defaults toTextColor::LightGray. You can set a custom console text color by overriding this macro.
#define LOGIT_DEFAULT_COLOR TextColor::Green- LOGIT_CURRENT_TIMESTAMP_MS: Macro to get the current timestamp in milliseconds. By default, it uses std::chrono to get the timestamp. You can override this to provide a custom timestamp function if needed.
#define LOGIT_CURRENT_TIMESTAMP_MS() my_custom_timestamp_function()- LOGIT_CONSOLE_PATTERN: Defines the default log pattern for the console logger. This pattern controls the formatting of log messages sent to the console, including timestamp, message, and color. If
LOGIT_CONSOLE_PATTERNis not defined, it defaults to%H:%M:%S.%e | %^%v%$.
#define LOGIT_CONSOLE_PATTERN "%H:%M:%S.%e | %^%v%$"- LOGIT_FILE_LOGGER_PATH: Defines the default directory path for log files. If
LOGIT_FILE_LOGGER_PATHis not defined, it defaults to "data/logs". You can set this to a custom path to control where the log files are stored.
#define LOGIT_FILE_LOGGER_PATH "/custom/log/directory"- LOGIT_FILE_LOGGER_AUTO_DELETE_DAYS: Defines the number of days after which old log files are deleted. If
LOGIT_FILE_LOGGER_AUTO_DELETE_DAYSis not defined, it defaults to30days. You can set this to a custom value to control the log file retention policy.
#define LOGIT_FILE_LOGGER_AUTO_DELETE_DAYS 60 // Keep logs for 60 days- LOGIT_FILE_LOGGER_PATTERN: Defines the default log pattern for file-based loggers. This pattern controls the formatting of log messages written to log files, including timestamp, filename, line number, function, and thread information. If
LOGIT_FILE_LOGGER_PATTERNis not defined, it defaults to[%Y-%m-%d %H:%M:%S.%e] [%ffn:%#] [%!] [thread:%t] [%l] %SC%v.
#define LOGIT_FILE_LOGGER_PATTERN "[%Y-%m-%d %H:%M:%S.%e] [%l] %SC%v"- LOGIT_UNIQUE_FILE_LOGGER_PATH: Defines the default directory path for unique log files. If
LOGIT_UNIQUE_FILE_LOGGER_PATHis not defined, it defaults to "data/logs/unique_logs". You can specify a custom path for unique log files.
#define LOGIT_UNIQUE_FILE_LOGGER_PATH "/custom/unique/log/directory"- LOGIT_UNIQUE_FILE_LOGGER_PATTERN: Defines the default log pattern for unique file-based loggers. If
LOGIT_UNIQUE_FILE_LOGGER_PATTERNis not defined, it defaults to"%v". You can customize this pattern to control the format of log messages in unique files.
#define LOGIT_UNIQUE_FILE_LOGGER_PATTERN "%v"- LOGIT_UNIQUE_FILE_LOGGER_HASH_LENGTH: Defines the length of the hash used in unique log file names. If
LOGIT_UNIQUE_FILE_LOGGER_HASH_LENGTHis not defined, it defaults to8characters. This ensures that unique filenames are generated for each log entry.
#define LOGIT_UNIQUE_FILE_LOGGER_HASH_LENGTH 12 // Set hash length to 12 characters- LOGIT_SHORT_NAME: Enables short names for logging macros, such as
LOG_T,LOG_D,LOG_E, etc., for more concise logging statements.
Normal application code should keep using the public LOGIT_* / LOG_*
macros. The low-level API shown below is for extension points: custom
ILogger / ILogFormatter implementations, backend registration, tests, and
other infrastructure code that intentionally works below the macro layer.
You can extend LogIt++ by implementing your own loggers and formatters. Here’s how:
#include <fstream>
#include <mutex>
#include <logit.hpp>
class FileLogger : public logit::ILogger {
public:
FileLogger(const std::string& file_name) : m_file_name(file_name) {
m_log_file.open(file_name, std::ios::out | std::ios::app);
}
~FileLogger() {
if (m_log_file.is_open()) {
m_log_file.close();
}
}
void log(const logit::LogRecord& record, const std::string& message) override {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_log_file.is_open()) {
m_log_file << message << std::endl;
}
}
void wait() override {}
std::string get_string_param(const logit::LoggerParam& param) const override {
(void)param;
return std::string();
}
int64_t get_int_param(const logit::LoggerParam& param) const override {
(void)param;
return 0;
}
double get_float_param(const logit::LoggerParam& param) const override {
(void)param;
return 0.0;
}
void set_log_level(logit::LogLevel level) override { m_log_level = level; }
logit::LogLevel get_log_level() const override { return m_log_level; }
private:
std::string m_file_name;
std::ofstream m_log_file;
std::mutex m_mutex;
logit::LogLevel m_log_level = logit::LogLevel::LOG_LVL_TRACE;
};#include <logit.hpp>
#include <json/json.h>
class JsonLogFormatter : public logit::ILogFormatter {
public:
void set_timestamp_offset(int64_t offset_ms) override {
(void)offset_ms;
}
std::string format(const logit::LogRecord& record) const override {
Json::Value log_entry;
log_entry["level"] = static_cast<int>(record.log_level);
log_entry["timestamp_ms"] = record.timestamp_ms;
log_entry["file"] = record.file;
log_entry["line"] = record.line;
log_entry["function"] = record.function;
log_entry["format"] = record.format;
Json::StreamWriterBuilder writer;
return Json::writeString(writer, log_entry);
}
};| Macro pattern | Description |
|---|---|
LOGIT_<LEVEL>(...) |
Log a message with the given level (TRACE, DEBUG, INFO, WARN, ERROR, FATAL). |
LOGIT_PRINT_<LEVEL>(...) |
Log a pre-formatted string or stream-built message. |
LOGIT_PRINTF_<LEVEL>(fmt, ...) |
printf-style formatting with placeholders for each argument. |
LOGIT_FORMAT_<LEVEL>(fmt, ...) |
Apply the same format string to every argument. |
LOGIT_FMT_<LEVEL>(fmt, ...) |
fmt-style formatting when LOGIT_WITH_FMT is enabled. |
LOGIT_STREAM_<LEVEL>() |
Stream-style logging with << operators; short aliases LOG_S_<LEVEL>() when LOGIT_SHORT_NAME is defined. |
LOGIT_<LEVEL>_IF(condition, ...) |
Log only when condition is true. |
LOGIT_PRINT_<LEVEL>_IF(...), LOGIT_PRINTF_<LEVEL>_IF(...), LOGIT_FORMAT_<LEVEL>_IF(...), LOGIT_FMT_<LEVEL>_IF(...) |
Conditional variants for the formatting families. |
LOGIT_<LEVEL>_ONCE(...) |
Log only the first time the macro is executed. |
LOGIT_<LEVEL>_EVERY_N(n, ...) |
Log on every nth invocation. |
LOGIT_<LEVEL>_THROTTLE(period_ms, ...) |
Log at most once per period_ms milliseconds. |
LOGIT_<LEVEL>_TAG(({{"k", "v"}}), msg) |
Attach key-value tags to a message. |
LOGIT_MDC_PUT(key, value), LOGIT_MDC_REMOVE(key), LOGIT_MDC_CLEAR() |
Manage thread-local mapped diagnostic context when LOGIT_WITH_CONTEXT is enabled. |
LOGIT_NDC_PUSH(value), LOGIT_NDC_POP(), LOGIT_NDC_CLEAR(), LOGIT_NDC_GUARD(value) |
Manage thread-local nested diagnostic context when LOGIT_WITH_CONTEXT is enabled. |
LOGIT_RAW(msg), LOGIT_RAW_TO(index, msg), LOGIT_RAW_IF(condition, msg) |
Write already formatted text without applying level filters or formatter patterns. |
LOGIT_SECTION(name), LOGIT_SECTION_TO(index, name), LOGIT_SECTION_IF(condition, name) |
Write raw section headers such as [Proxy]. |
LOGIT_<LEVEL>_TO(index, ...) |
Target a specific logger index, including single-mode backends. |
LOGIT_PRINT_<LEVEL>_TO(...), LOGIT_PRINTF_<LEVEL>_TO(...), LOGIT_FORMAT_<LEVEL>_TO(...), LOGIT_FMT_<LEVEL>_TO(...), LOGIT_STREAM_<LEVEL>_TO(...) |
Targeted variants for the print/printf/format/fmt/stream families. |
LOGIT_SCOPE_<LEVEL>(phase) / LOGIT_SCOPE_<LEVEL>_T(threshold_ms, phase) |
RAII scope-duration logging, optionally only when a threshold is exceeded. |
LOGIT_SCOPE_PRINTF_<LEVEL>(...) / LOGIT_SCOPE_PRINTF_<LEVEL>_T(...) |
Scope timers with printf-style formatting. |
LOGIT_SCOPE_FMT_<LEVEL>(...) / LOGIT_SCOPE_FMT_<LEVEL>_T(...) |
Scope timers with fmt-style formatting. |
LOGIT_CLEAR_LOGGER(index) / LOGIT_CLEAR_ALL_LOGGERS() |
Clear supported logger-owned records and return a LogClearResult; _EX variants accept LogClearOptions. |
LOGIT_PERROR_<LEVEL>(msg), LOGIT_WINERR_<LEVEL>(msg), LOGIT_SYSERR_<LEVEL>(msg) |
Append decoded platform error information to a message. |
LOGIT_ADD_LOGGER(...) and LOGIT_ADD_* backend macros |
Register console, memory, file, unique-file, crash, syslog, event-log, or custom backends. |
LOGIT_GET_*, LOGIT_SET_*, LOGIT_IS_*, LOGIT_WAIT(), LOGIT_SHUTDOWN() |
Query and manage logger/task-executor state. |
LOGIT_QUEUE_*, LOGIT_GET_DROPPED_TASKS(), LOGIT_RESET_DROPPED_TASKS() |
Queue policy constants and dropped-task counters. |
Pattern-consistent aliases also exist for short-name and compatibility paths; the rows above document the canonical public families.
| Macro | Description |
|---|---|
LOGIT_BASE_PATH |
Trim this prefix from __FILE__ paths shown in logs. |
LOGIT_DEFAULT_COLOR |
Default console color for messages. |
LOGIT_COLOR_TRACE, LOGIT_COLOR_DEBUG, LOGIT_COLOR_INFO, LOGIT_COLOR_WARN, LOGIT_COLOR_ERROR, LOGIT_COLOR_FATAL, LOGIT_COLOR_DEFAULT |
Override per-level and default console colors. |
LOGIT_WALLCLOCK_MS() / LOGIT_MONOTONIC_MS() |
Override the wall-clock or monotonic time source helpers used by the library. |
LOGIT_CURRENT_TIMESTAMP_MS() |
Override the main timestamp hook used in log records. |
LOGIT_CONSOLE_PATTERN |
Default format pattern for console output. |
LOGIT_FILE_LOGGER_PATH |
Directory for rotating file logs. |
LOGIT_FILE_LOGGER_AUTO_DELETE_DAYS |
Retention window for file logger cleanup. |
LOGIT_FILE_LOGGER_PATTERN |
Default message pattern for file loggers. |
LOGIT_FILE_LOGGER_MAX_FILE_SIZE_BYTES |
Rotation threshold for size-based file rotation. |
LOGIT_FILE_LOGGER_MAX_ROTATED_FILES |
Maximum number of rotated files to retain. |
LOGIT_UNIQUE_FILE_LOGGER_PATH |
Directory for one-message-per-file logs. |
LOGIT_UNIQUE_FILE_LOGGER_PATTERN |
Default message pattern for unique-file loggers. |
LOGIT_UNIQUE_FILE_LOGGER_HASH_LENGTH |
Hash length used in unique-file logger names. |
LOGIT_OS_ERROR_JOIN, LOGIT_POSIX_ERROR_PATTERN, LOGIT_WINDOWS_ERROR_PATTERN, LOGIT_SYSTEM_ERROR_PATTERN |
Control how decoded system errors are appended to messages. |
LOGIT_TAGS_JOIN, LOGIT_TAG_PAIR_SEP, LOGIT_TAG_KV_SEP, LOGIT_TAG_QUOTE_VALUES |
Control how key-value tags are rendered after the message. |
LOGIT_TASK_EXECUTOR_BLOCK_WAIT_USEC |
Polling cadence used by blocking producers when the queue is full. |
LOGIT_TASK_EXECUTOR_DRAIN_BUDGET |
Maximum number of queued tasks drained per worker iteration in ring mode. |
LOGIT_TASK_EXECUTOR_DEFAULT_RING_CAPACITY |
Default MPSC ring capacity used when unlimited queue mode needs a backing size. |
LOGIT_SHORT_NAME |
Enable compact aliases such as LOG_I, LOG_WPF, and LOG_S_INFO. |
| Macro | Description |
|---|---|
LOGIT_SET_MAX_QUEUE(size) |
Limit the asynchronous task queue (0 for unlimited). |
LOGIT_SET_QUEUE_POLICY(mode) |
Set overflow behavior: LOGIT_QUEUE_DROP_NEWEST, LOGIT_QUEUE_DROP_OLDEST, or LOGIT_QUEUE_BLOCK. |
LOGIT_SET_LOG_LEVEL_TO(index, level) |
Set minimum log level for a specific logger. |
LOGIT_SET_LOG_LEVEL(level) |
Set minimum log level for all loggers. |
LOGIT_GET_LOG_LEVEL(index) |
Read the current minimum log level of a specific logger. |
LOGIT_SET_LOGGER_ENABLED(index, enabled) |
Enable or disable a logger. |
LOGIT_IS_LOGGER_ENABLED(index) |
Check whether a logger is enabled. |
LOGIT_SET_SINGLE_MODE(index, single_mode) |
Toggle single-message-per-file mode for a logger. |
LOGIT_IS_SINGLE_MODE(index) |
Determine if a logger is in single mode. |
LOGIT_SET_TIME_OFFSET(index, offset_ms) |
Adjust timestamp offset for a logger. |
LOGIT_GET_STRING_PARAM(index, param) |
Retrieve a string parameter from a logger. |
LOGIT_GET_INT_PARAM(index, param) |
Retrieve an integer parameter from a logger. |
LOGIT_GET_FLOAT_PARAM(index, param) |
Retrieve a floating-point parameter from a logger. |
LOGIT_GET_LAST_FILE_NAME(index) |
Get the last file name written by a logger. |
LOGIT_GET_LAST_FILE_PATH(index) |
Get the last file path written by a logger. |
LOGIT_GET_LAST_LOG_TIMESTAMP(index) |
Get the timestamp of the last log entry. |
LOGIT_GET_TIME_SINCE_LAST_LOG(index) |
Seconds elapsed since the last log entry. |
LOGIT_GET_BUFFERED_STRINGS(index) |
Return buffered formatted messages from a logger that supports snapshots. |
LOGIT_GET_BUFFERED_ENTRIES(index) |
Return buffered structured entries from a logger that supports snapshots. |
LOGIT_LIST_LOG_FILES(index) |
List persisted log files exposed by a file-based logger. |
LOGIT_READ_LOG_FILE(index, path) |
Read one persisted plain-text log file owned by a file-based logger. |
LOGIT_READ_LOG_FILES(index, paths) |
Read several persisted plain-text log files and preserve request order. |
LOGIT_WAIT() |
Wait for all asynchronous loggers to finish. |
LOGIT_SHUTDOWN() |
Shut down the logging system. |
LogIt++ itself is header-only. When consumed through the CMake target, enabled optional features may add transitive compile and link dependencies. Choose one of the following integration paths.
- Clone the repository with its submodules:
git clone --recurse-submodules https://github.com/LimiNode/log-it-cpp.git- Include the LogIt++ headers in your project:
#include <logit.hpp>- Configure dependencies.
CMake builds first look for the required TimeShield package and then fall back to this repository's bundled external/time-shield-cpp submodule when it is present. If you vendor dependencies inside your own project, use your own install or vendor paths; the directory does not need to be named external.
Optional dependencies are needed only for the features you enable: fmt for
LOGIT_WITH_FMT, zlib for LOGIT_WITH_GZIP, zstd for
LOGIT_WITH_ZSTD, kurlyk for LOGIT_WITH_OTLP, and
mdbx-containers for LOGIT_WITH_MDBX. Install them as packages, provide
them from your own dependency layout, or set LOGIT_USE_SUBMODULES=ON to let
CMake use bundled copies for development. Installed package exports require
optional dependencies to be provided as installed/imported targets.
add_subdirectory(external/log-it-cpp)
target_link_libraries(my_app PRIVATE log-it-cpp::log-it-cpp)find_package(log-it-cpp CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE log-it-cpp::log-it-cpp)Build and install the package with:
cmake -S . -B build -DLOGIT_CPP_BUILD_TESTS=OFF
cmake --build build
cmake --install build --prefix ./installPass -DCMAKE_PREFIX_PATH=/path/to/install when configuring the consumer.
LOGIT_WITH_PROMETHEUS_SERVER=ON and bundled optional dependency targets are
currently supported for source/build-tree development only; the install step
rejects them rather than exporting a broken package.
- (Optional) Enable fmt-style macros:
LogIt++ includes the fmt library for {}-based formatting. To use the LOGIT_FMT_* and LOGIT_SCOPE_FMT_* macros, build the library with the CMake option -DLOGIT_WITH_FMT=ON.
The following toggles cover all build-time features:
LOGIT_CPP_BUILD_TESTS(default: ON when the project is the root build) — build the test suite.LOGIT_CPP_BUILD_EXAMPLES(default: OFF) — build the example programs.LOGIT_BENCH_ENABLE(default: OFF) — build benchmarks;LOGIT_BENCH_WITH_SPDLOG(default: OFF) also builds the spdlog comparisons.LOGIT_WITH_GZIP/LOGIT_WITH_ZSTD(defaults: OFF) — enable gzip or zstd support for rotated files.LOGIT_WITH_FMT(default: OFF) — include the{}-style formatting macros.LOGIT_WITH_CONTEXT(default: OFF) — enable MDC/NDC helpers and%K,%K{key},%Jformatter tokens.LOGIT_WITH_OTLP(default: OFF, C++17) — enable OTLP/HTTP export through kurlyk; not supported on Emscripten.LOGIT_WITH_PROMETHEUS(default: OFF) — enable Prometheus text payload support; not supported on Emscripten.LOGIT_WITH_PROMETHEUS_SERVER(default: OFF, C++17) — enable the embedded Prometheus HTTP server; not supported on Emscripten and currently rejected bycmake --install.LOGIT_WITH_MDBX(default: OFF, C++17) — enable structured MDBX storage through mdbx-containers; not supported on Emscripten or MSVC.LOGIT_USE_SUBMODULES(default: OFF) allows bundled optional dependency fallbacks such as fmt, zlib, and zstd when system packages are missing.LOGIT_WITH_SYSLOG(default: ON on Unix-like targets) — build the syslog backend.LOGIT_WITH_WIN_EVENT_LOG(default: ON on Windows) — build the Windows Event Log backend.LOGIT_FORCE_ASYNC_OFF(default: OFF) — force synchronous logging even in multi-threaded builds.LOGIT_USE_MPSC_RING(default: ON) — use the lock-free task queue instead of the mutex-backed deque.LOGIT_EMSCRIPTEN(default: ON under Emscripten toolchains) — adjust the build for single-threaded WebAssembly environments.
Supported backends include console, file, memory, system logging, OTLP, Prometheus, and MDBX. See the canonical backend matrix for standards, feature-specific dependencies, and platform/package restrictions.
LogIt++ can forward messages to system logging facilities.
Available when LOGIT_WITH_SYSLOG=ON on Unix-like systems. Log levels are mapped as follows: TRACE/DEBUG → LOG_DEBUG, INFO → LOG_INFO, WARN → LOG_WARNING, ERROR/FATAL → LOG_ERR/LOG_CRIT.
LOGIT_ADD_SYSLOG_DEFAULT();
LOGIT_INFO("Syslog is alive");Enabled with LOGIT_WITH_WIN_EVENT_LOG=ON on Windows. Levels map TRACE/DEBUG/INFO → INFORMATION, WARN → WARNING, ERROR/FATAL → ERROR.
LOGIT_ADD_EVENT_LOG_DEFAULT();
LOGIT_ERROR("Something went wrong");Both loggers compile to no-ops on unsupported platforms.
When building with Emscripten the library runs without threads. Console logging works as usual while file-based loggers are replaced by stubs that warn when used.
The canonical benchmark guide is docs/benchmarks.md.
It contains the methodology, interpretation rules, historical snapshot, and
LatencyRecorder notes referenced by the detailed material below.
Latency and throughput benchmarks live under bench/. Enable them during configuration and optionally pull in the spdlog
adapters:
cmake -S . -B build -DLOGIT_BENCH_ENABLE=ON -DLOGIT_BENCH_WITH_SPDLOG=ON
cmake --build build --target logit_benchRun ./build/bench/logit_bench to record the full matrix (sync/async × null/file × producer counts × message sizes). Results
are appended to bench/results/latency.csv with one row per library/combination. Override the workload via LOGIT_BENCH_TOTAL
and LOGIT_BENCH_WARMUP environment variables if you need a lighter run.
See the canonical benchmark guide for the measurement model, comparison caveats, and interpretation rules.
The historical snapshot and full comparison table are maintained in the benchmark guide.
See the benchmark guide for LatencyRecorder details.
Detailed documentation for LogIt++, including API reference and usage examples, can be found here.
This library is licensed under the MIT License. See the LICENSE file in the repository for more details.
