Skip to content

API Reference

LunarECL edited this page Feb 24, 2026 · 22 revisions

API Reference

Complete reference for all public APIs in LunarLog. Namespace: minta.

Table of Contents


LunarLog

The main logger class. Non-copyable but move-constructible (move assignment is deleted).

Constructor / Destructor

LunarLog(LogLevel minLevel = LogLevel::INFO, bool addDefaultConsoleSink = true)

Creates a new logger instance.

Parameter Type Default Description
minLevel LogLevel INFO Minimum log level. Messages below this are dropped.
addDefaultConsoleSink bool true If true, adds a ConsoleSink with HumanReadableFormatter.
// Default: INFO level, console sink
minta::LunarLog logger;

// TRACE level, console sink
minta::LunarLog logger(minta::LogLevel::TRACE);

// DEBUG level, no default sink — you must add your own
minta::LunarLog logger(minta::LogLevel::DEBUG, false);

~LunarLog() noexcept

Destructor. Calls flush() to ensure all buffered messages are written. All logging threads must be stopped before destroying the logger. See Thread Safety.


Fluent Builder (configure)

static LoggerConfiguration configure()

Returns a LoggerConfiguration builder for declarative logger setup. Chain configuration methods, then call build() to produce a fully-configured LunarLog instance.

auto logger = minta::LunarLog::configure()
    .minLevel(minta::LogLevel::DEBUG)
    .writeTo<minta::ConsoleSink>("console")
    .writeTo<minta::FileSink, minta::JsonFormatter>("json-out", "app.json.log")
    .enrich(minta::Enrichers::threadId())
    .build();

The returned LoggerConfiguration is move-only. See LoggerConfiguration for all builder methods and Fluent Builder for the full guide.


Log Level

void setMinLevel(LogLevel level)

Changes the minimum log level at runtime. Thread-safe (atomic).

logger.setMinLevel(minta::LogLevel::WARN); // only WARN+ from now on

LogLevel getMinLevel() const

Returns the current minimum log level.

auto level = logger.getMinLevel(); // e.g., LogLevel::INFO

Source Location

void setCaptureSourceLocation(bool capture)

Enables or disables capture of file, line, and function for each log entry. Default: false. Thread-safe (atomic).

When enabled, LogEntry::file, LogEntry::line, and LogEntry::function are populated.

logger.setCaptureSourceLocation(true);
logger.info("Where am I?");
// LogEntry will contain file="main.cpp", line=42, function="main"

bool getCaptureSourceLocation() const

Returns whether source location capture is enabled.


Sinks

void addSink<SinkType, Args...>(args...)

Adds a sink with the default formatter for that sink type. Template parameters are deduced.

logger.addSink<minta::FileSink>("output.log");
logger.addSink<minta::ConsoleSink>();

void addSink<SinkType, FormatterType, Args...>(args...)

Adds a sink with a specific formatter type.

logger.addSink<minta::FileSink, minta::JsonFormatter>("output.json");
logger.addSink<minta::FileSink, minta::XmlFormatter>("output.xml");

void addCustomSink(std::unique_ptr<ISink> sink)

Adds a fully custom sink. Use when you need control over both the sink and formatter.

auto sink = minta::detail::make_unique<MyCustomSink>();
logger.addCustomSink(std::move(sink));

Important: All addSink / addCustomSink calls must happen before the first log message. Calling after logging starts throws std::logic_error.


Named Sinks

void addSink<SinkType>(const SinkName& name, Args&&... args)

Adds a named sink. Use named("name") to create a SinkName.

logger.addSink<minta::ConsoleSink>(minta::named("console"));
logger.addSink<minta::FileSink>(minta::named("app-log"), "app.log");

void addSink<SinkType, FormatterType>(const SinkName& name, Args&&... args)

Adds a named sink with a custom formatter.

logger.addSink<minta::FileSink, minta::JsonFormatter>(minta::named("json-out"), "app.json");

void addSink<SinkType>(const std::string& name, Args&&... args)

String overload — equivalent to using named().

logger.addSink<minta::FileSink>("errors", "errors.log");

void addCustomSink(const std::string& name, std::unique_ptr<ISink> sink)

Adds a named custom sink.

auto sink = minta::detail::make_unique<MyCustomSink>();
logger.addCustomSink("my-sink", std::move(sink));

SinkProxy sink(const std::string& name)

Returns a SinkProxy for configuring the named sink. Throws std::invalid_argument if name is not found.

logger.sink("json-out").level(minta::LogLevel::INFO);

Duplicate names: Adding a sink with an already-registered name throws std::invalid_argument.

Auto-naming: Unnamed sinks are auto-named "sink_0", "sink_1", etc., skipping names already taken.


Logging Methods

All logging methods use Message Templates syntax. For named placeholders, arguments are passed as alternating name-value pairs. For indexed placeholders ({0}, {1}, ...), values are passed as positional arguments.

void log(LogLevel level, const std::string& messageTemplate, const Args&... args)

Generic log method with explicit level.

logger.log(minta::LogLevel::INFO, "User {name} logged in", "name", "Alice");

void trace(const std::string& messageTemplate, const Args&... args)

void debug(const std::string& messageTemplate, const Args&... args)

void info(const std::string& messageTemplate, const Args&... args)

void warn(const std::string& messageTemplate, const Args&... args)

void error(const std::string& messageTemplate, const Args&... args)

void fatal(const std::string& messageTemplate, const Args&... args)

Convenience methods for each log level.

logger.trace("Entering function {fn}", "fn", "process");
logger.debug("Cache size: {size}", "size", 42);
logger.info("Server started on port {port}", "port", 8080);
logger.warn("Disk usage at {pct}%", "pct", 85);
logger.error("Failed to connect: {reason}", "reason", "timeout");
logger.fatal("Out of memory, shutting down");

Argument passing: Placeholders are matched by position for formatting, not by name.

  • Named placeholders ({name}) consume values sequentially in template order, while storing values by name for structured output.
  • Indexed placeholders ({0}, {1}, ...) resolve a specific value slot directly by index.
  • Out-of-range indexed placeholders render as empty strings, and named placeholders continue to use the next available mapped value without being shifted by skipped index slots.
// {username} gets "Alice", {action} gets "login"
logger.info("User {username} performed {action}", "username", "Alice", "action", "login");

// Indexed usage (positional values)
logger.info("{1} then {name} then {0}", "A", "B");
// => B then A then A

Alignment: Add ,N after the placeholder name for fixed-width output. Positive N = right-align, negative N = left-align. Applied after format specifiers and pipe transforms.

logger.info("{name,20}", "name", "Alice");         // right-align in 20 chars
logger.info("{name,-20}", "name", "Alice");        // left-align in 20 chars
logger.info("{price,12:.2f}", "price", 9.99);      // spec + alignment
logger.info("{0,10|upper}", "hello");               // indexed + transform + alignment

See Message Templates — Alignment & Padding for full details.

No arguments: Templates without placeholders work fine.

logger.info("Server started");

Type support: Any type convertible to string via std::to_string or operator<< works:

logger.info("Int: {a}, Double: {b}, Bool: {c}, String: {d}",
            "a", 42, "b", 3.14, "c", true, "d", "hello");

void logWithSourceLocation(LogLevel level, const char* file, int line, const char* function, const std::string& messageTemplate, const Args&... args)

Log with explicit source location (instead of relying on setCaptureSourceLocation).

void logWithContext(LogLevel level, const char* file, int line, const char* function, const std::string& messageTemplate, const Args&... args)

Log with source location and current context snapshot.

⚠️ Deprecated alias: logWithContext() is a backward-compatibility alias of logWithSourceLocation(). Prefer logWithSourceLocation().

Exception Overloads

Attach a caught std::exception to a log entry. See Exception Attachment for the full guide.

void log(LogLevel level, const std::exception& ex, const std::string& messageTemplate, const Args&... args)

Log with an exception and message template:

try {
    riskyOperation();
} catch (const std::exception& ex) {
    logger.log(minta::LogLevel::ERROR, ex, "Failed for {id}", "id", "req-001");
}

void log(LogLevel level, const std::exception& ex)

Log an exception only — ex.what() is used as the message:

try {
    riskyOperation();
} catch (const std::exception& ex) {
    logger.log(minta::LogLevel::ERROR, ex);
}

void trace(const std::exception& ex, const std::string& messageTemplate, const Args&... args)

void trace(const std::exception& ex)

void debug(const std::exception& ex, const std::string& messageTemplate, const Args&... args)

void debug(const std::exception& ex)

void info(const std::exception& ex, const std::string& messageTemplate, const Args&... args)

void info(const std::exception& ex)

void warn(const std::exception& ex, const std::string& messageTemplate, const Args&... args)

void warn(const std::exception& ex)

void error(const std::exception& ex, const std::string& messageTemplate, const Args&... args)

void error(const std::exception& ex)

void fatal(const std::exception& ex, const std::string& messageTemplate, const Args&... args)

void fatal(const std::exception& ex)

Per-level convenience methods. Each has two overloads: exception + template + args, and exception only.

try {
    riskyDatabaseQuery();
} catch (const std::exception& ex) {
    logger.error(ex, "Operation failed for user {name}", "john");
}

void logWithSourceLocationAndException(LogLevel level, const char* file, int line, const char* function, const std::exception& ex, const std::string& messageTemplate, const Args&... args)

Combine explicit source location with exception attachment:

try {
    process();
} catch (const std::exception& ex) {
    logger.logWithSourceLocationAndException(
        minta::LogLevel::ERROR, __FILE__, __LINE__, __func__,
        ex, "Processing failed for {id}", "id", "req-001");
}

Filtering — Global

Global filters apply to all sinks. See Filtering for detailed guide.

void setFilter(FilterPredicate filter)

Sets a global predicate filter. Only entries where the predicate returns true are logged. If the predicate throws, entries are let through (fail-open).

logger.setFilter([](const minta::LogEntry& entry) {
    return entry.level >= minta::LogLevel::WARN;
});

Type: FilterPredicate = std::function<bool(const LogEntry&)>

void clearFilter()

Removes the global predicate filter.

void addFilterRule(const std::string& ruleStr)

Adds a DSL filter rule. Multiple rules are AND-combined. See Filtering — DSL Rules for syntax.

logger.addFilterRule("level >= WARN");
logger.addFilterRule("not message contains 'heartbeat'");

void clearFilterRules()

Removes all global DSL filter rules.

void clearAllFilters()

Removes both the global predicate and all global DSL rules in one call.


Rolling File Sink

RollingPolicy::size(path, maxBytes) / ::daily(path) / ::hourly(path)

Static factories for creating rotation policies. Returns a RollingPolicy builder.

RollingPolicy& maxFiles(n) / maxSize(bytes) / maxTotalSize(bytes)

Fluent configuration for cleanup and hybrid rotation.

RollingFileSink(const RollingPolicy& policy)

Construct a rolling file sink. Use with addSink<RollingFileSink>(policy) or addCustomSink().

void useFormatter<T>()

Set formatter type. Must be called during initialization only, before logging begins.

See Rolling File Sink for full documentation.


void filter(const std::string& compactExpr)

Adds compact filter rules using shorthand syntax. Space-separated tokens are AND-combined. Thread-safe. See Compact Filter.

logger.filter("WARN+ ~timeout ctx:env=prod");

Filtering — Per-Sink

Per-sink filters only affect the specified sink. Sinks are indexed from 0 in the order they were added. The default console sink (if enabled) is index 0.

void setSinkLevel(size_t sinkIndex, LogLevel level)

Sets the minimum log level for a specific sink. The effective level is the stricter of the global and per-sink levels.

logger.setSinkLevel(0, minta::LogLevel::ERROR); // sink 0: errors only

void setSinkFilter(size_t sinkIndex, FilterPredicate filter)

Sets a predicate filter on a specific sink.

logger.setSinkFilter(0, [](const minta::LogEntry& entry) {
    return entry.message.find("sensitive") == std::string::npos;
});

void clearSinkFilter(size_t sinkIndex)

Removes the predicate filter from a specific sink.

void addSinkFilterRule(size_t sinkIndex, const std::string& ruleStr)

Adds a DSL filter rule to a specific sink.

logger.addSinkFilterRule(1, "context env == 'production'");

void clearSinkFilterRules(size_t sinkIndex)

Removes all DSL rules from a specific sink.

void clearAllSinkFilters(size_t sinkIndex)

Removes both the predicate and all DSL rules from a specific sink.


Context

Global key-value pairs attached to every log entry. See Context Capture.

void setContext(const std::string& key, const std::string& value)

Sets a context key-value pair. Thread-safe.

logger.setContext("session_id", "abc123");
logger.setContext("environment", "production");

void clearContext(const std::string& key)

Removes a specific context key. Thread-safe.

logger.clearContext("session_id");

void clearAllContext()

Removes all context keys. Thread-safe.

LogScope scope(std::initializer_list<std::pair<std::string, std::string>> pairs)

Creates an RAII scoped context. Key-value pairs are injected into all log entries on the current thread for the lifetime of the returned LogScope. See Scoped Context (LogScope).

{
    auto scope = logger.scope({{"requestId", "req-001"}, {"userId", "u-42"}});
    logger.info("Processing");
    // context includes requestId and userId
}
// context removed automatically

Note: Scoped context is thread-wide, not per-logger. See LogScope for details.


Template Cache

void setTemplateCacheSize(size_t size)

Sets the maximum number of parsed templates to cache. Default: 128. Set to 0 to disable caching.

When the cache is full, new templates are parsed on every call but existing cached entries remain. See Structured Output — Template Cache.

logger.setTemplateCacheSize(256); // increase cache
logger.setTemplateCacheSize(0);   // disable caching

Locale

void setLocale(const std::string& locale)

Sets the global locale for culture-specific formatting. Default: "C". Thread-safe. See Culture-Specific Formatting.

logger.setLocale("de_DE");
logger.info("Price: {amount:n}", "amount", 1234.56);
// Output: Price: 1.234,56

If the locale is not available on the system, falls back to "C". Accepts locale names like "en_US", "de_DE", "fr_FR", "ja_JP", etc. The .UTF-8 suffix is tried automatically.

std::string getLocale() const

Returns the current global locale name.

void setSinkLocale(size_t sinkIndex, const std::string& locale)

Sets a locale override for a specific sink. The sink re-renders messages using this locale instead of the global one. Thread-safe.

logger.setLocale("en_US");
logger.addSink<minta::FileSink>("german.log");
logger.setSinkLocale(1, "de_DE");

logger.info("Total: {val:n}", "val", 1234.56);
// sink 0 (en_US): Total: 1,234.56
// sink 1 (de_DE): Total: 1.234,56

Set to "" to revert to the entry's original locale.


Flush

void flush()

Flushes all sinks. Called automatically by the destructor.


Enrichers

void enrich(EnricherFn fn)

Registers an enricher function that runs on every log entry. Enrichers populate LogEntry::customContext with metadata before sinks see the entry.

Parameter Type Default Description
fn EnricherFn Function that modifies a LogEntry's context

Throws: std::logic_error if called after the first log entry has been emitted.

Enrichers run in registration order. Explicit context (setContext / scoped context) overwrites enricher values on key collisions: enricher < setContext < scoped context.

logger.enrich(minta::Enrichers::threadId());
logger.enrich(minta::Enrichers::processId());
logger.enrich(minta::Enrichers::property("service", "auth-api"));
logger.enrich([](minta::LogEntry& entry) {
    entry.customContext["correlationId"] = generateId();
});

See Enrichers for the full guide.


Built-in Enrichers (Enrichers namespace)

All functions return EnricherFn. Namespace: minta::Enrichers.

Function Context Key Value Evaluation
threadId() threadId std::hash<std::thread::id> as decimal string Per entry
processId() processId PID from getpid() / GetCurrentProcessId() Cached at registration
machineName() machine Hostname from gethostname() / GetComputerNameA() Cached at registration
environment() environment $APP_ENV then $ENVIRONMENT fallback Cached at registration
property(key, val) key val Cached at registration
fromEnv(envVar, key) key Value of env var envVar Cached at registration
caller() caller Source function name Per entry (requires setCaptureSourceLocation(true))
logger.enrich(minta::Enrichers::threadId());
logger.enrich(minta::Enrichers::machineName());
logger.enrich(minta::Enrichers::property("version", "2.1.0"));
logger.enrich(minta::Enrichers::fromEnv("AWS_REGION", "region"));

EnricherFn

using EnricherFn = std::function<void(LogEntry&)>;

IEnricher

Interface for polymorphic enrichers. Wrap with a shared_ptr capture for use with enrich().

class IEnricher {
public:
    virtual ~IEnricher() = default;
    virtual void enrich(LogEntry& entry) const = 0;
};
auto enricher = std::make_shared<MyEnricher>();
logger.enrich([enricher](minta::LogEntry& entry) {
    enricher->enrich(entry);
});

SinkProxy

Fluent proxy for configuring a named sink. Returned by logger.sink("name").

Methods

All methods return SinkProxy& for chaining.

Method Description
level(LogLevel lvl) Set the sink's minimum log level
filterRule(const std::string& dsl) Add a DSL filter rule
filter(const std::string& compactExpr) Add compact filter rules (shorthand syntax)
filter(FilterPredicate pred) Set a predicate filter
locale(const std::string& loc) Set a per-sink locale
formatter(std::unique_ptr<IFormatter> f) Set formatter (throws std::logic_error after logging starts)
outputTemplate(const std::string& templateStr) Set custom output format for HumanReadableFormatter only
only(const std::string& tag) Add an only-tag for tag routing
except(const std::string& tag) Add an except-tag for tag routing
clearFilter() Remove the predicate filter
clearFilterRules() Remove all DSL filter rules
clearFilters() Remove predicate + DSL rules (not tag filters)
clearTagFilters() Remove all only/except tag filters
logger.sink("json-out")
    .level(minta::LogLevel::INFO)
    .filterRule("not message contains 'heartbeat'")
    .outputTemplate("[{timestamp:HH:mm:ss}] [{level:u3}] {threadId,6} {message}")
    .only("important")
    .locale("de_DE");

LoggerConfiguration

Fluent builder for constructing a fully-configured LunarLog instance. Obtained via LunarLog::configure(). Non-copyable, move-only.

Global Configuration Methods

All methods return LoggerConfiguration& for chaining.

Method Description Default
minLevel(LogLevel level) Set minimum log level INFO
minLevel(std::shared_ptr<LevelSwitch> sw) Set shared observable level (runtime-changeable)
watchConfig(const std::string& path, duration interval) Watch JSON config file for runtime reload
captureSourceLocation(bool enable) Enable/disable source location capture false
rateLimit(size_t maxLogs, std::chrono::milliseconds window) Set rate limit 1000 / 1000ms
templateCacheSize(size_t size) Set template cache size (0 = disable) 128
locale(const std::string& loc) Set global locale "" (C)
enrich(EnricherFn fn) Register an enricher
filter(const std::string& compact) Add compact filter rules
filterRule(const std::string& dsl) Add a DSL filter rule
minta::LunarLog::configure()
    .minLevel(minta::LogLevel::DEBUG)
    .captureSourceLocation(true)
    .rateLimit(5000, std::chrono::milliseconds(1000))
    .templateCacheSize(256)
    .locale("en_US")
    .enrich(minta::Enrichers::threadId())
    .filter("!~heartbeat")
    .filterRule("level >= INFO")

writeTo Methods

Add sinks to the builder. Six overloads cover all combinations.

Unnamed Sink

template<typename SinkType, typename... Args>
LoggerConfiguration& writeTo(Args&&... args);

template<typename SinkType, typename FormatterType, typename... Args>
LoggerConfiguration& writeTo(Args&&... args);

Auto-named "sink_0", "sink_1", etc. Args are forwarded to the SinkType constructor.

.writeTo<minta::ConsoleSink>()
.writeTo<minta::FileSink, minta::JsonFormatter>("app.json.log")

Named Sink

template<typename SinkType, typename... Args>
LoggerConfiguration& writeTo(const std::string& name, Args&&... args);

template<typename SinkType, typename FormatterType, typename... Args>
LoggerConfiguration& writeTo(const std::string& name, Args&&... args);

First argument is the sink name; remaining args go to the constructor.

.writeTo<minta::ConsoleSink>("console")
.writeTo<minta::FileSink>("app-log", "app.log")
.writeTo<minta::FileSink, minta::JsonFormatter>("json-out", "app.json.log")

SFINAE: The named overload is disabled when SinkType is constructible from (const std::string&, Args...) to prevent the name from being consumed as a constructor argument.

Named Sink with Lambda Configuration

template<typename SinkType, typename ConfigFn, typename... CtorArgs>
LoggerConfiguration& writeTo(const std::string& name, ConfigFn&& configure, CtorArgs&&... args);

template<typename SinkType, typename FormatterType, typename ConfigFn, typename... CtorArgs>
LoggerConfiguration& writeTo(const std::string& name, ConfigFn&& configure, CtorArgs&&... args);

The lambda receives a SinkProxy& for inline sink configuration (level, filters, locale, output template, tag routing).

.writeTo<minta::FileSink>("errors",
    [](minta::SinkProxy& s) {
        s.level(minta::LogLevel::ERROR)
         .only("critical")
         .outputTemplate("[{timestamp:HH:mm:ss}] [{level:u3}] {message}{exception}");
    }, "errors.log")

See SinkProxy for all available configuration methods.

subLogger()

LoggerConfiguration& subLogger(const std::string& name, std::function<void(SubLoggerConfiguration&)> configFn)

Adds a sub-logger with independent filters, enrichers, and sinks. The sub-logger receives all entries that pass the parent's global filter, then applies its own pipeline.

.subLogger("errors", [](SubLoggerConfiguration& sub) {
    sub.filter("ERROR+")
       .enrich(Enrichers::property("pipeline", "alerts"))
       .writeTo<CallbackSink>("alert", alertFn);
})

SubLoggerConfiguration supports: minLevel(), filter(), filterRule(), enrich(), writeTo() (named and unnamed variants).

See Sub-Logger for the full guide.

build()

LunarLog build()

Constructs the configured LunarLog instance and returns it by move. Applies all settings, registers enrichers, adds sinks, and validates the configuration.

Behavior Description
Single use Throws std::logic_error if called more than once
No sinks Prints warning to stderr; returns a silent logger that discards all messages
Return LunarLog by move — the builder is consumed
auto logger = minta::LunarLog::configure()
    .writeTo<minta::ConsoleSink>()
    .build();

See Fluent Builder for the full guide, examples, and comparison with the imperative API.


LogScope

RAII scoped context that injects key-value pairs into log entries for the lifetime of the scope. Thread-wide (not per-logger). See Scoped Context (LogScope) for the full guide.

Factory: LogScope LunarLog::scope(std::initializer_list<std::pair<std::string, std::string>> pairs)

{
    auto scope = logger.scope({{"requestId", "req-001"}});
    scope.add("userId", "u-42");
    logger.info("Processing");
}
// context removed

Methods

Method Returns Description
add(const std::string& key, const std::string& value) LogScope& Append a pair to the scope (chainable). No-op if moved-from. Last value wins for duplicate keys.

Properties

  • Non-copyable, move-only — can be returned from functions
  • Thread-wide — all loggers on the same thread see the same scope stack
  • Inner shadows outer — duplicate keys in nested scopes: inner wins, restored on exit
  • Exception-safe — destructor always cleans up

ContextScope

RAII helper that sets a context key on construction and removes it on destruction.

{
    minta::ContextScope scope(logger, "request_id", "req-456");
    logger.info("Processing");
    // LogEntry::customContext contains {"request_id": "req-456"}
}
// "request_id" is automatically removed here

Constructor: ContextScope(LunarLog& logger, const std::string& key, const std::string& value)

Destructor: Calls logger.clearContext(key).

Warning: ContextScope holds a reference to the logger. The logger must outlive the scope.


ISink

Base class for all sinks. Extend this to create custom sinks.

Key Methods

Method Description
virtual void write(const LogEntry& entry) Override this. Called for each log entry that passes filters.
void setMinLevel(LogLevel level) Set per-sink minimum level
LogLevel getMinLevel() const Get per-sink minimum level
void setFilter(FilterPredicate filter) Set per-sink predicate filter
void clearFilter() Remove predicate filter
void addFilterRule(const std::string& ruleStr) Add DSL rule
void clearFilterRules() Remove all DSL rules
void clearAllFilters() Remove predicate + all DSL rules
void setLocale(const std::string& locale) Set per-sink locale (delegates to formatter)
std::string getLocale() const Get per-sink locale
bool passesFilter(const LogEntry& entry) const Check if entry passes this sink's filters
void setFormatter(std::unique_ptr<IFormatter> fmt) Set the formatter (call from constructor). Protected on ISink (used by sink implementations / friend helpers such as SinkProxy).
void setTransport(std::unique_ptr<ITransport> transport) Set the transport (call from constructor). Protected on ISink (used by sink implementations / friend helpers such as SinkProxy).
IFormatter* formatter() const Access the formatter. Protected on ISink (used by sink implementations / friend helpers such as SinkProxy).

See Custom Sinks and Formatters for examples.

Named Sink & Tag Methods on ISink

These methods are available on all sinks (called internally or via SinkProxy):

Method Description
void setSinkName(const std::string& name) Set the sink's name (called by LogManager)
const std::string& getSinkName() const Get the sink's name
void addOnlyTag(const std::string& tag) Add a tag to the only-list (allowlist)
void addExceptTag(const std::string& tag) Add a tag to the except-list (blocklist)
void clearOnlyTags() Clear the only-list
void clearExceptTags() Clear the except-list
void clearTagFilters() Clear both only and except lists
bool shouldAcceptTags(const vector<string>& tags) const Check if entry tags pass this sink's tag filters
std::set<std::string> getOnlyTags() const Get current only-tags (copy)
std::set<std::string> getExceptTags() const Get current except-tags (copy)

Tag routing rules:

  • If only-tags are set: accept only entries with at least one matching tag
  • If except-tags are set: reject entries with any matching tag
  • only() takes precedence over except()
  • Untagged entries go to sinks with no only() filter

IFormatter

Base class for all formatters.

Key Methods

Method Description
virtual std::string format(const LogEntry& entry) const Override this. Returns the formatted log string.
void setLocale(const std::string& locale) Set formatter locale (thread-safe)
std::string getLocale() const Get formatter locale (thread-safe)
std::string localizedMessage(const LogEntry& entry) const Returns re-rendered message if locale differs from entry. Protected on IFormatter (for formatter subclasses).

Built-in Formatters

  • HumanReadableFormatter[timestamp] [LEVEL] message
  • JsonFormatter — JSON with level, timestamp, message, messageTemplate, templateHash, properties
  • XmlFormatter — XML with same fields

FilterRule

Parsed DSL filter rule. Usually created via string parsing.

Static Methods

Method Description
static FilterRule parse(const std::string& rule) Parse a DSL rule string. Throws on invalid syntax.

Instance Methods

Method Description
bool evaluate(const LogEntry& entry) const Returns true if the entry passes this rule.

See Filtering — DSL Rules for syntax.


LogEntry

The data structure passed to sinks and formatters for each log event.

Field Type Description
level LogLevel Log level of this entry
message std::string Rendered message (placeholders resolved)
timestamp std::chrono::system_clock::time_point When the log was created
templateStr std::string Original message template
templateHash uint32_t FNV-1a hash of the template (for grouping)
arguments vector<pair<string, string>> Name-value pairs of resolved placeholders
file std::string Source file (if setCaptureSourceLocation(true))
line int Source line (if captured)
function std::string Function name (if captured)
customContext map<string, string> Context key-value pairs
properties vector<PlaceholderProperty> Structured properties with operator info
tags std::vector<std::string> Tags parsed from [bracketed] prefixes in the message template
locale std::string Locale used when rendering this entry
exception std::unique_ptr<detail::ExceptionInfo> nullptr when no exception attached; otherwise holds type, message, chain. Check with hasException() before accessing.

PlaceholderProperty

Field Type Description
name std::string Placeholder name (without operator prefix)
value std::string Resolved value as string
op char Operator: '@' (destructure), '$' (stringify), or 0 (none)
transforms std::vector<std::string> Pipe transform names applied to this placeholder (e.g. ["upper", "comma"])

Transforms are applied after format specifiers during message rendering. The transforms field records the transform names for downstream consumers (custom formatters, log processors).

for (const auto& prop : entry.properties) {
    if (prop.op == '@') { /* destructure */ }
    if (prop.op == '$') { /* stringify */ }
    if (prop.op == 0)   { /* no operator */ }
    for (const auto& t : prop.transforms) {
        // e.g. "upper", "comma", "truncate:10"
    }
}

Built-in Pipe Transforms

Transforms are applied to placeholder values via the | operator: {name|transform}. See Pipe Transforms for the full guide.

All transform functions live in minta::detail and have the signature std::string(const std::string& value) or std::string(const std::string& value, const std::string& arg).

String Transforms

Function Transform Description
transformUpper(value) upper ASCII uppercase
transformLower(value) lower ASCII lowercase
transformTrim(value) trim Strip leading/trailing whitespace
transformTruncate(value, arg) truncate:N Limit to N UTF-8 codepoints, append …
transformPad(value, arg) pad:N Right-pad with spaces to N codepoints
transformPadLeft(value, arg) padl:N Left-pad with spaces to N codepoints
transformQuote(value) quote Wrap in double quotes

Number Transforms

Function Transform Description
transformComma(value) comma Thousands separator
transformHex(value) hex Hex with 0x prefix
transformOct(value) oct Octal with 0 prefix
transformBin(value) bin Binary with 0b prefix
transformBytes(value) bytes Human-readable byte size
transformDuration(value) duration Human-readable time from ms
transformPct(value) pct Percentage (×100)

Structural Transforms

Function Transform Description
transformJson(value) json JSON serialization
transformType(value) type Detected type name
(metadata only) expand Alias for @ operator
(metadata only) str Alias for $ operator

Pipeline Functions

Function Description
parseTransforms(pipeStr) Parse "comma|truncate:10" into vector<Transform>
applyTransforms(value, transforms) Apply a transform chain to a formatted value

The Transform struct holds name (string) and arg (string, empty if no argument).


Source Location Macros

Header: <lunar_log/macros.hpp>

Convenience macros that capture __FILE__, __LINE__, and __func__ at the call site. All macros are guarded by #ifndef LUNAR_LOG_NO_MACROS.

Standard Macros

Each calls logWithSourceLocation() after a level check that short-circuits argument evaluation.

Macro Signature Calls
LUNAR_LOG LUNAR_LOG(logger, level, ...) logWithSourceLocation(level, __FILE__, __LINE__, __func__, ...)
LUNAR_TRACE LUNAR_TRACE(logger, ...) LUNAR_LOG(logger, TRACE, ...)
LUNAR_DEBUG LUNAR_DEBUG(logger, ...) LUNAR_LOG(logger, DEBUG, ...)
LUNAR_INFO LUNAR_INFO(logger, ...) LUNAR_LOG(logger, INFO, ...)
LUNAR_WARN LUNAR_WARN(logger, ...) LUNAR_LOG(logger, WARN, ...)
LUNAR_ERROR LUNAR_ERROR(logger, ...) LUNAR_LOG(logger, ERROR, ...)
LUNAR_FATAL LUNAR_FATAL(logger, ...) LUNAR_LOG(logger, FATAL, ...)
#include <lunar_log/macros.hpp>

LUNAR_INFO(logger, "User {name} logged in", "name", "alice");

Exception Macros

Each calls logWithSourceLocationAndException() with an attached std::exception.

Macro Signature Calls
LUNAR_LOG_EX LUNAR_LOG_EX(logger, level, ex, ...) logWithSourceLocationAndException(level, __FILE__, __LINE__, __func__, ex, ...)
LUNAR_TRACE_EX LUNAR_TRACE_EX(logger, ex, ...) LUNAR_LOG_EX(logger, TRACE, ex, ...)
LUNAR_DEBUG_EX LUNAR_DEBUG_EX(logger, ex, ...) LUNAR_LOG_EX(logger, DEBUG, ex, ...)
LUNAR_INFO_EX LUNAR_INFO_EX(logger, ex, ...) LUNAR_LOG_EX(logger, INFO, ex, ...)
LUNAR_WARN_EX LUNAR_WARN_EX(logger, ex, ...) LUNAR_LOG_EX(logger, WARN, ex, ...)
LUNAR_ERROR_EX LUNAR_ERROR_EX(logger, ex, ...) LUNAR_LOG_EX(logger, ERROR, ex, ...)
LUNAR_FATAL_EX LUNAR_FATAL_EX(logger, ex, ...) LUNAR_LOG_EX(logger, FATAL, ex, ...)
try {
    connectToDatabase();
} catch (const std::exception& ex) {
    LUNAR_ERROR_EX(logger, ex, "Connection failed for {host}", "host", "db-01");
}

LUNAR_LOG_NO_MACROS

Define before including the header to suppress all 14 macros:

#define LUNAR_LOG_NO_MACROS
#include <lunar_log/macros.hpp>   // no macros defined

See Source Location Macros for the full guide, design notes, and comparison with the manual API.


Global Logger

Header: <lunar_log/global.hpp>

minta::Log is a static facade over a process-wide LunarLog instance.

Lifecycle / Access

Method Signature Description
configure static GlobalLoggerConfiguration configure() Returns builder wrapper. build() sets global instance.
init static void init(LunarLog&& logger) Install pre-built logger as global instance.
shutdown static void shutdown() Clear global logger instance.
isInitialized static bool isInitialized() Returns whether a global logger is configured.
instance static std::shared_ptr<LunarLog> instance() Returns shared handle to global logger. Throws if not initialized.
flush static void flush() Flush all sinks on global logger.
minta::Log::configure()
    .minLevel(minta::LogLevel::INFO)
    .writeTo<minta::ConsoleSink>()
    .build();

minta::Log::info("Started");
minta::Log::shutdown();

Logging Methods (minta::Log)

Method Signature
trace template<typename... Args> static void trace(const std::string& msg, Args&&... args)
debug template<typename... Args> static void debug(const std::string& msg, Args&&... args)
info template<typename... Args> static void info(const std::string& msg, Args&&... args)
warn template<typename... Args> static void warn(const std::string& msg, Args&&... args)
error template<typename... Args> static void error(const std::string& msg, Args&&... args)
fatal template<typename... Args> static void fatal(const std::string& msg, Args&&... args)
log template<typename... Args> static void log(LogLevel level, const std::string& msg, Args&&... args)

Exception Overloads (minta::Log)

Method Signature
log template<typename... Args> static void log(LogLevel level, const std::exception& ex, const std::string& msg, Args&&... args)
log static void log(LogLevel level, const std::exception& ex)
trace template<typename... Args> static void trace(const std::exception& ex, const std::string& msg, Args&&... args)
trace static void trace(const std::exception& ex)
debug template<typename... Args> static void debug(const std::exception& ex, const std::string& msg, Args&&... args)
debug static void debug(const std::exception& ex)
info template<typename... Args> static void info(const std::exception& ex, const std::string& msg, Args&&... args)
info static void info(const std::exception& ex)
warn template<typename... Args> static void warn(const std::exception& ex, const std::string& msg, Args&&... args)
warn static void warn(const std::exception& ex)
error template<typename... Args> static void error(const std::exception& ex, const std::string& msg, Args&&... args)
error static void error(const std::exception& ex)
fatal template<typename... Args> static void fatal(const std::exception& ex, const std::string& msg, Args&&... args)
fatal static void fatal(const std::exception& ex)

GlobalLoggerConfiguration

Builder wrapper returned by Log::configure(). Mirrors LoggerConfiguration (minLevel, captureSourceLocation, rateLimit, templateCacheSize, locale, enrich, filter, filterRule, and all writeTo overloads).

void build()

Builds a LunarLog via internal LoggerConfiguration and installs it through Log::init().


Global Macros

Header: <lunar_log/global.hpp>

Defined unless LUNAR_LOG_NO_GLOBAL_MACROS is set.

Macro Expands to
LUNAR_GTRACE(...) ::minta::Log::trace(__VA_ARGS__)
LUNAR_GDEBUG(...) ::minta::Log::debug(__VA_ARGS__)
LUNAR_GINFO(...) ::minta::Log::info(__VA_ARGS__)
LUNAR_GWARN(...) ::minta::Log::warn(__VA_ARGS__)
LUNAR_GERROR(...) ::minta::Log::error(__VA_ARGS__)
LUNAR_GFATAL(...) ::minta::Log::fatal(__VA_ARGS__)

LevelSwitch

Shared, thread-safe observable log level for runtime changes.

#include <lunar_log.hpp>

auto sw = std::make_shared<minta::LevelSwitch>(minta::LogLevel::INFO);
Method Description
LevelSwitch(LogLevel level = LogLevel::INFO) Construct with initial level
LogLevel get() const noexcept Get current level (atomic)
void set(LogLevel level) noexcept Set level (atomic, immediate effect)

Pass to LoggerConfiguration::minLevel(sw) to enable runtime level changes. Can be shared across multiple loggers.

ConfigWatcher

Internal class — not directly constructed. Created via LoggerConfiguration::watchConfig().

Polls a JSON config file at a configurable interval. Supports:

  • Global minLevel changes
  • Per-sink level overrides (by sink name)
  • Filter rule hot-reload (COW)
  • Graceful degradation on malformed config

See Dynamic Configuration wiki page for full details.

Enums and Types

LogLevel

enum class LogLevel {
    TRACE = 0,
    DEBUG = 1,
    INFO = 2,
    WARN = 3,
    ERROR = 4,
    FATAL = 5
};

ConsoleStream

enum class ConsoleStream {
    StdOut,
    StdErr
};

Selects target stream for ConsoleSink / ColorConsoleSink.

FilterPredicate

using FilterPredicate = std::function<bool(const LogEntry&)>;

SinkName

Tag type for disambiguating named-sink overloads:

struct SinkName {
    std::string value;
    explicit SinkName(const std::string& n);
    explicit SinkName(const char* n);
};

named()

Convenience factory for SinkName:

inline SinkName named(const std::string& name);
inline SinkName named(const char* name);

Utility Functions

Function Description
const char* getLevelString(LogLevel level) Returns "TRACE", "DEBUG", etc.

Built-in Sinks

ConsoleSink

Writes to standard console stream. Default formatter: HumanReadableFormatter.

logger.addSink<minta::ConsoleSink>(); // StdOut
logger.addSink<minta::ConsoleSink>(minta::ConsoleStream::StdErr);
Constructor Description
explicit ConsoleSink(ConsoleStream stream = ConsoleStream::StdOut) Select output stream (StdOut / StdErr)

ColorConsoleSink

Drop-in alternative to ConsoleSink that colorizes the [LEVEL] bracket with ANSI escape codes. Message body is left uncolored. Color is auto-disabled when target stream is not a TTY or NO_COLOR / LUNAR_LOG_NO_COLOR is set.

logger.addSink<minta::ColorConsoleSink>(); // StdOut
logger.addSink<minta::ColorConsoleSink>(minta::ConsoleStream::StdErr);

// Runtime toggle
auto& sink = /* get sink reference */;
sink.setColor(false);  // disable color
Constructor / Method Signature
Constructor explicit ColorConsoleSink(ConsoleStream stream = ConsoleStream::StdOut)
setColor void setColor(bool enabled)
isColorEnabled bool isColorEnabled() const
colorize static std::string colorize(const std::string& text, LogLevel level)
getColorCode static const char* getColorCode(LogLevel level)
Level Color
TRACE dim
DEBUG cyan
INFO green
WARN yellow
ERROR red
FATAL bold red

See Color Console Sink for full documentation.

CallbackSink

Sink that forwards entries to user callbacks.

// Raw LogEntry callback
logger.addSink<minta::CallbackSink>(
    minta::CallbackSink::EntryCallback(
        [](const minta::LogEntry& e) {
            // custom handling
        }));

// Formatted string callback (CompactJsonFormatter default)
logger.addSink<minta::CallbackSink>(
    minta::CallbackSink::StringCallback(
        [](const std::string& line) {
            // send line to external system
        }));
Type / Method Signature
EntryCallback using EntryCallback = std::function<void(const LogEntry&)>
StringCallback using StringCallback = std::function<void(const std::string&)>
Constructor (entry) explicit CallbackSink(EntryCallback cb)
Constructor (string) explicit CallbackSink(StringCallback cb, std::unique_ptr<IFormatter> fmt = nullptr)
write void write(const LogEntry& entry) override
flush void flush() override (no-op)

String variant uses CompactJsonFormatter when fmt == nullptr.

FileSink

Writes to a file. Default formatter: HumanReadableFormatter.

logger.addSink<minta::FileSink>("app.log");
logger.addSink<minta::FileSink, minta::JsonFormatter>("app.json");
Constructor Parameter Type Description
filename std::string Path to the log file

AsyncSink (template)

Asynchronous sink decorator that wraps another sink type.

logger.addSink<minta::AsyncSink<minta::FileSink>>("app.log");

OverflowPolicy

enum class OverflowPolicy {
    Block,
    DropOldest,
    DropNewest
};

AsyncOptions

Field Type Default Description
queueSize size_t 8192 Max queued entries
overflowPolicy OverflowPolicy DropNewest Overflow behavior
flushIntervalMs size_t 0 Periodic flush interval (0 = disabled)

Public API (AsyncSink<SinkType>)

Method Signature
Constructor (default options) template<typename... Args> explicit AsyncSink(Args&&... args)
Constructor (custom options) template<typename... Args> explicit AsyncSink(AsyncOptions opts, Args&&... args)
write void write(const LogEntry& entry) override
flush void flush() override
droppedCount size_t droppedCount() const
innerSink SinkType* innerSink()
innerSink (const) const SinkType* innerSink() const

BatchedSink (abstract)

Reusable base sink for batch delivery.

BatchOptions

Field Type Default Description
batchSize_ size_t 100 Flush threshold
flushIntervalMs_ size_t 5000 Timer flush interval (0 = disabled)
maxQueueSize_ size_t 10000 Max buffered entries before drop
maxRetries_ size_t 3 Retry count when writeBatch() throws
retryDelayMs_ size_t 100 Retry delay in ms
Setter Signature
setBatchSize BatchOptions& setBatchSize(size_t n)
setFlushIntervalMs BatchOptions& setFlushIntervalMs(size_t ms)
setMaxQueueSize BatchOptions& setMaxQueueSize(size_t n)
setMaxRetries BatchOptions& setMaxRetries(size_t n)
setRetryDelayMs BatchOptions& setRetryDelayMs(size_t ms)

Public API (BatchedSink)

Method Signature
Constructor explicit BatchedSink(BatchOptions opts = BatchOptions())
Destructor virtual ~BatchedSink() noexcept
stopAndFlush void stopAndFlush() noexcept
write void write(const LogEntry& entry) final
flush void flush() override
options const BatchOptions& options() const
droppedCount size_t droppedCount() const

Protected hooks (BatchedSink)

Method Signature
writeBatch virtual void writeBatch(const std::vector<const LogEntry*>& batch)
onFlush virtual void onFlush()
onBatchError virtual void onBatchError(const std::exception& e, size_t retryCount)

SyslogSink

POSIX syslog sink (#ifndef _WIN32).

SyslogOptions

Field Type Default Description
facility_ int LOG_USER Facility code for openlog()
logopt_ int LOG_PID | LOG_NDELAY openlog() options
includeLevel_ bool false Prefix message with [LEVEL]
Setter Signature
setFacility SyslogOptions& setFacility(int f)
setLogopt SyslogOptions& setLogopt(int o)
setIncludeLevel SyslogOptions& setIncludeLevel(bool b)

Public API (SyslogSink)

Method Signature
Constructor explicit SyslogSink(const std::string& ident, SyslogOptions opts = SyslogOptions())
Destructor ~SyslogSink() noexcept
write void write(const LogEntry& entry) override
toSyslogPriority static int toSyslogPriority(LogLevel level)

HttpSink

Batched HTTP sink based on BatchedSink.

HttpSinkOptions

Field Type Default Description
url std::string required HTTP/HTTPS endpoint
contentType std::string "application/json" Content-Type value
headers std::map<std::string,std::string> {} Additional HTTP headers
timeoutMs size_t 10000 Request timeout
verifySsl bool true HTTPS certificate verification
batchSize size_t 50 Batch size
flushIntervalMs size_t 5000 Timer flush interval
maxRetries size_t 3 Retry count
maxQueueSize size_t 10000 Max buffered entries
retryDelayMs size_t 1000 Retry delay in ms
Setter Signature
Constructor explicit HttpSinkOptions(const std::string& url_)
setHeader HttpSinkOptions& setHeader(const std::string& key, const std::string& val)
setContentType HttpSinkOptions& setContentType(const std::string& ct)
setTimeoutMs HttpSinkOptions& setTimeoutMs(size_t ms)
setBatchSize HttpSinkOptions& setBatchSize(size_t n)
setFlushIntervalMs HttpSinkOptions& setFlushIntervalMs(size_t ms)
setMaxRetries HttpSinkOptions& setMaxRetries(size_t n)
setMaxQueueSize HttpSinkOptions& setMaxQueueSize(size_t n)
setVerifySsl HttpSinkOptions& setVerifySsl(bool v)
setRetryDelayMs HttpSinkOptions& setRetryDelayMs(size_t ms)

Public API (HttpSink)

Method Signature
Constructor explicit HttpSink(HttpSinkOptions opts)
Destructor ~HttpSink() noexcept

(write() / flush() are inherited from BatchedSink.)


Built-in Formatters

HumanReadableFormatter

Output: [2026-02-17 12:00:00.000] [INFO] User Alice logged in

Includes source location if captured: [2026-02-17 12:00:00.000] [INFO] [main.cpp:42] User Alice logged in

void setOutputTemplate(const std::string& templateStr) (on formatter)

HumanReadableFormatter can render logs using a custom output template with these tokens:

  • {timestamp} / {timestamp:FORMAT}
  • {level} / {level:u3} / {level:l}
  • {message}
  • {newline}
  • {properties} (comma-separated key=value list)
  • {template} (original message template)
  • {source} (file:line function)
  • {threadId}
  • {exception} (exception type + message + chain; empty if no exception)

Alignment and formatting are supported:

  • {level,6} (width/right-aligned)
  • {level,-6} (width/left-aligned)
  • {level,6:u3} (u3 spec + width)
  • {level:-6:l} (l spec + width)
logger.sink("console").outputTemplate("[{timestamp:yyyy-MM-dd HH:mm:ss}] [{level:u3}] {threadId,6} {message}");

For JSON and XML formatters, this call is a no-op.

JsonFormatter

{
  "level": "INFO",
  "timestamp": "2026-02-17T12:00:00.000Z",
  "messageTemplate": "User {username} logged in",
  "templateHash": "a1b2c3d4",
  "message": "User Alice logged in",
  "properties": {
    "username": "Alice"
  }
}

Includes file, line, function if captured. Includes context if set. Properties with @ operator emit native JSON types.

CompactJsonFormatter

JSONL output optimized for log pipelines. Short @-prefixed keys, flattened properties, single-line output.

{"@t":"2026-02-18T00:30:05.123Z","@l":"WRN","@mt":"Connection to {host} failed","@i":"a1b2c3d4","host":"db-01"}
  • @l omitted for INFO level
  • Properties at top level (not nested)
  • User keys starting with @ escaped to @@
  • includeRenderedMessage(true) adds @m field
logger.addSink<minta::FileSink, minta::CompactJsonFormatter>("logs/app.jsonl");

// Optional: include rendered message
auto fmt = minta::detail::make_unique<minta::CompactJsonFormatter>();
fmt->includeRenderedMessage(true);

See Compact JSON Formatter for full details.

XmlFormatter

<log level="INFO" timestamp="2026-02-17T12:00:00.000Z">
  <messageTemplate>User {username} logged in</messageTemplate>
  <templateHash>a1b2c3d4</templateHash>
  <message>User Alice logged in</message>
  <properties>
    <property name="username">Alice</property>
  </properties>
</log>

Properties with @ get destructure="true" attribute. Properties with $ get stringify="true".

Clone this wiki locally