C++ library for stylized console and file logging with timestamps, level tags, and colors.
- RamRod Log
- Log levels: Verbose, Info, Debug, Warning, and Error — each with a distinct tag and color on the terminal, plus optional filtering via
Logger::log_level(details). - Output: Console (stdout/stderr), file only, or file and console (
OutputType::CONSOLE,FILE,FILE_AND_CONSOLE). - Format: Each line is prefixed with
[YYYY-MM-DD HH:MM:SS.uuuuuu][LEVEL]then your message. - API: Stream-style
logger.info() << "message" << valueandprintf-style formatting on level loggers. - Runtime: Switch output via
output(OutputType, path); set date format and printf buffer size without recreating the logger.
- C++20 (compiler with C++20 support)
- CMake 3.22 or later
- Dependencies: none (uses only the C++ standard library)
The library is built as a static target RamRodLog. It is usually included as a subdirectory from a parent CMakeLists.txt.
add_subdirectory(path/to/lib/ramrod/log)
target_link_libraries(your_target PRIVATE ${RamRodLog_LIBRARIES})
target_include_directories(your_target PRIVATE ${RamRodLog_INCLUDE_DIRS})(Adjust to match your project’s CMake variables for the log library.)
mkdir build && cd build
cmake ..
cmake --build .Or run the compile.sh script from the project root; it configures and builds in the build directory.
Unit tests use Google Test. Build with tests enabled, then run the test executable:
./tests/run_tests.shRun a subset of tests by passing a GTest filter:
./tests/run_tests.sh "LoggerTest.*"
./tests/run_tests.sh --gtest_filter=LoggerTest.DefaultConstruction#include "ramrod/log/Logger.hpp"Levels are defined by ramrod::LogLevel in ramrod/log/Enumerators.hpp (the enum is also visible when you include Logger.hpp).
LogLevel |
Line tag | Role |
|---|---|---|
VERBOSE |
[VERBOSE] |
Fine-grained detail |
INFO |
[INFO] |
General operational messages |
DEBUG |
[DEBUG] |
Development diagnostics |
WARNING |
[WARNING] |
Recoverable or suspicious conditions |
ERROR |
[ERROR] |
Failures and errors |
Enum order matches severity for filtering: verbose is lowest, error is highest. On a color terminal each level has its own color; file output uses the same tags without ANSI escape codes.
Call logger.log_level(level) to set a minimum severity: a channel writes only if its level is greater than or equal to that threshold. The default is LogLevel::VERBOSE, so every channel is enabled until you change it.
ramrod::Logger log;
log.log_level(ramrod::LogLevel::WARNING);
log.verbose() << "skipped";
log.info() << "skipped";
log.debug() << "skipped";
log.warning() << "printed";
log.error() << "printed";ramrod::Debug, ramrod::Info, and the other single-level types always represent one channel; use ramrod::Logger when you want one threshold applied to all of them together.
Create a Logger (default constructor) and use the level accessors with the stream operator <<:
ramrod::Logger log;
log.error() << "Connection failed";
log.info() << "User count: " << 42;
log.debug() << "State: " << state_name << " code: " << code;
log.warning() << "Low memory: " << free_mb << " MB";
log.verbose() << "Detail: " << detail_string;Output on terminal (with colors) looks like:
# Following line is printed with red letters
[2026-02-16 14:30:00.002316][ERROR] Connection failed
# Following line is printed with green letters
[2026-02-16 14:30:00.002316][INFO] User count: 42
# Following line is printed with blue letters
[2026-02-16 14:30:00.002316][DEBUG] State: idle code: 0
# Following line is printed with yellow letters
[2026-02-16 14:30:00.002316][WARNING] Low memory: 3 MB
# Following line is printed with default terminal's color
[2026-02-16 14:30:00.002316][VERBOSE] Detail: this is a detailed verbose messageConstruct the logger with an output type and path. All levels write to the same file. File output has no ANSI color codes.
ramrod::Logger log(ramrod::Logger::OutputType::FILE, "/var/log/myapp.log");
log.info() << "Application started";
log.error() << "Error: " << error_message;Redirect all levels to a new file or back to console without creating a new logger:
ramrod::Logger log;
log.info() << "This goes to stdout";
if (!log.output(ramrod::Logger::OutputType::FILE, "/var/log/myapp.log"))
std::cerr << "Failed to open log file\n";
else
log.info() << "This goes to the file";
// Back to console
log.output(ramrod::Logger::OutputType::CONSOLE);Use .printf() on any level logger. The default buffer size is 1024 bytes; increase it if you need longer lines.
ramrod::Logger log;
log.error().printf("Error code %d: %s\n", errno, strerror(errno));
log.info().printf("Progress: %3d%%\n", percent);
log.printf_buffer_size(4096);
log.info().printf("Long message: %s ...\n", long_string);Logger::throw_if is a template on ramrod::Logger only. If the first argument (condition) is true, it builds a message by streaming the remaining arguments into a string (same idea as chaining <<), writes that text through the error channel (with the usual timestamp and [ERROR] styling), then throws an exception of type E.
- Template
Emust derive fromstd::exception. The default isstd::invalid_argument. Emust be constructible from aconst char*(the implementation passes the assembled buffer’s data pointer). Standard types such asstd::invalid_argument,std::runtime_error, andstd::logic_errorwork.- If
conditionis false, nothing is logged and nothing is thrown.
ramrod::Logger log;
// Default exception type: std::invalid_argument
log.throw_if(value < 0, "value must be >= 0, got ", value);
// Explicit exception type
log.throw_if<std::runtime_error>(ptr == nullptr, "null pointer");Use this when you want a single check to both record the failure in the log and signal it to callers.
Set the strftime pattern and the internal date buffer size with date_format(format, buffer_size, add_microseconds). The writer always wraps the formatted time in [ … ]; the pattern you pass is what strftime receives (see man strftime).
Microseconds (add_microseconds) — The third argument defaults to true. When enabled, the fractional part of the current second is appended after the strftime text as a six-digit value, e.g. [2026-02-16 14:30:00.002316]. It is computed from the high-resolution clock (fractional second only, not a full timestamp). Pass false to omit the .uuuuuu suffix and shorten timestamps to [2026-02-16 14:30:00].
Size the buffer for: opening [, the longest strftime output, optional .000000, closing ], and the null terminator. The library’s own default uses buffer size 32 when microseconds are on (see Writer).
NOTE: Set the date format and the size of the internal date buffer (must fit the formatted string including null terminator)
ramrod::Logger log;
// Default construction: "%Y-%m-%d %H:%M:%S", microseconds on → [2026-02-16 14:30:00.002316]
// No fractional seconds; smaller buffer is enough
log.date_format("%Y-%m-%d %H:%M:%S", 24, false);
log.info() << "After format change";
// Example: "[2026-02-16 14:30:00][INFO] After format change"
// Explicit microseconds (same as omitting the third argument)
log.date_format("%Y-%m-%d %H:%M:%S", 32, true);A global logger and convenience macros are available :
#include "ramrod/log/Logger.hpp"
// Changing output log file
RR_LOG.output("/log/new_log_file.log");
RR_LOGI << "info message" << RR_ENDL;
// Prints: [2026-02-16 14:30:00.002316][INFO][file_name.cpp][258] info message\n
RR_LOGE << "error at " << __FILE__ << RR_ENDL;
// Prints: [2026-02-16 14:30:00.002316][ERROR][another_file.cpp][18] error at /home/user/another_file.cpp\n| Macro | Expands to |
|---|---|
RR_LOG |
ramrod::global_logger |
RR_LOGD |
global_logger.debug().file_info(__FILE__, __LINE__) |
RR_LOGE |
global_logger.error().file_info(__FILE__, __LINE__) |
RR_LOGI |
global_logger.info().file_info(__FILE__, __LINE__) |
RR_LOGV |
global_logger.verbose().file_info(__FILE__, __LINE__) |
RR_LOGW |
global_logger.warning().file_info(__FILE__, __LINE__) |
RR_ENDL |
ramrod::endl |
For a single level, use ramrod::Debug, ramrod::Error, ramrod::Info, ramrod::Verbose, or ramrod::Warning with a Writer (or WriterCerr for Error). See the headers and tests for examples.
Each log line is:
# Without file_info(__FILE__, __LINE__)
[<date>][<LEVEL>] <your message>
# With file_info(__FILE__, __LINE__)
[<date>][<LEVEL>][<filename(__FILE__)>][<__LINE__>] <your message>- Date: By default local time as
[YYYY-MM-DD HH:MM:SS.uuuuuu]with six fractional digits unless you calldate_format(..., /*buffer*/, false)to disable them (details). - LEVEL: One of
[DEBUG],[ERROR],[INFO],[VERBOSE],[WARNING]. - File info: if
file_info(__FILE__, __LINE__)is used, then the filename and line are added, e.g.[filename.cpp][648] - Message: Whatever you stream or pass to
printf; file output is plain text (no ANSI codes).
| Class / member | Description |
|---|---|
ramrod::Logger |
Holds debug(), error(), info(), verbose(), warning() level loggers. |
ramrod::LogLevel |
VERBOSE, INFO, DEBUG, WARNING, ERROR — used with logger.log_level. |
logger.log_level(level) |
Minimum severity to print; default VERBOSE (all levels on). |
logger.debug() / .error() / .info() / .verbose() / .warning() |
Level logger; use << or .printf(format, ...). |
Logger::OutputType |
CONSOLE, FILE, FILE_AND_CONSOLE. |
logger.output() |
Current output path (empty if console). |
logger.output(type, path) |
Switch output. Returns true on success. |
logger.date_format() / logger.date_format(fmt, size, add_microseconds) |
Get or set strftime pattern, buffer size, and optional .uuuuuu suffix. |
logger.printf_buffer_size() / logger.printf_buffer_size(n) |
Get or set printf buffer size (default 1024). |
logger.throw_if<E>(condition, args...) |
If condition, log to error() then throw E (default std::invalid_argument). |
logger.clear(), logger.end(), logger.flush() |
Clear output, end line, flush. |
ramrod::global_logger, RR_LOG, RR_LOGD, … |
Global logger and macros. |
See the project root for license information.