-
Notifications
You must be signed in to change notification settings - Fork 1
Sub Logger
LunarEC's AI edited this page Feb 23, 2026
·
1 revision
Sub-loggers let you create independent mini-pipelines inside your logger — each with its own filters, enrichers, and sinks. This enables complex routing scenarios like error alerting, audit trails, and conditional logging that go beyond simple tag routing.
#include <lunar_log.hpp>
using namespace minta;
auto logger = LunarLog::configure()
.writeTo<ConsoleSink>("console")
.subLogger("errors", [](SubLoggerConfiguration& sub) {
sub.filter("ERROR+")
.writeTo<CallbackSink>("alert", [](const std::string& msg) {
// Send to PagerDuty, Slack, etc.
std::cerr << "[ALERT] " << msg << std::endl;
});
})
.build();
logger.info("Normal message"); // → console only
logger.error("Disk full!"); // → console AND error alertA sub-logger is implemented as SubLoggerSink — an ISink that owns a mini pipeline:
- Parent logger dispatches a
LogEntryto all sinks (including sub-logger sinks) -
SubLoggerSink::write()evaluates its own filters against the entry - If the entry passes, it's deep-copied via
cloneEntry()and enriched with sub-logger enrichers - The enriched entry is dispatched to the sub-logger's own sinks
Parent Logger
├── ConsoleSink ("console")
├── FileSink ("app-log")
├── SubLoggerSink ("errors") ← filters ERROR+, enriches, dispatches to:
│ └── CallbackSink ("alert")
└── SubLoggerSink ("audit") ← filters INFO+ ~audit, dispatches to:
└── FileSink ("audit-trail")
auto logger = LunarLog::configure()
.writeTo<ConsoleSink>("console")
.subLogger("name", [](SubLoggerConfiguration& sub) {
// Configure the sub-logger pipeline:
sub.minLevel(LogLevel::WARN) // ISink-level gate (before write)
.filter("ERROR+") // Sub-pipeline filter (inside write)
.filterRule(filterFn) // Custom filter predicate
.enrich(Enrichers::property("k", "v")) // Independent enrichers
.writeTo<SomeSink>("sink-name", args...);
})
.build();Sub-loggers have two filter layers:
| Layer | Method | When | Behavior |
|---|---|---|---|
| ISink level | sub.minLevel(WARN) |
Before write() is called |
Skips entry entirely |
| Sub-pipeline |
sub.filter("ERROR+") / sub.filterRule(fn)
|
Inside write()
|
Evaluated per-entry after ISink gate |
Both are optional. If neither is set, the sub-logger receives all entries from the parent.
auto logger = LunarLog::configure()
.writeTo<ConsoleSink>("console")
.writeTo<FileSink>("app", "app.log")
.subLogger("error-alerts", [](SubLoggerConfiguration& sub) {
sub.filter("ERROR+")
.enrich(Enrichers::property("pipeline", "alerts"))
.writeTo<HttpSink>("pagerduty", HttpSinkOptions("https://events.pagerduty.com/..."));
})
.build();auto logger = LunarLog::configure()
.writeTo<ConsoleSink>("console")
.subLogger("audit", [](SubLoggerConfiguration& sub) {
sub.filter("INFO+ ~audit")
.enrich(Enrichers::property("audit", "true"))
.writeTo<FileSink, JsonFormatter>("audit-file", "audit.jsonl");
})
.build();
logger.info("[audit] User login: {name}", "name", "alice");
// → console (plain) AND audit file (JSON)auto logger = LunarLog::configure()
.writeTo<ConsoleSink>("console")
.subLogger("errors", [](SubLoggerConfiguration& sub) {
sub.filter("ERROR+")
.writeTo<CallbackSink>("alert", alertFn);
})
.subLogger("metrics", [](SubLoggerConfiguration& sub) {
sub.filter("INFO+ ~metric")
.writeTo<FileSink, CompactJsonFormatter>("metrics", "metrics.jsonl");
})
.build();Wrap SubLoggerSink in AsyncSink for non-blocking sub-pipelines:
auto logger = LunarLog::configure()
.writeTo<ConsoleSink>("console")
.writeTo<AsyncSink<SubLoggerSink>>("async-errors",
[](SubLoggerConfiguration& sub) {
sub.filter("ERROR+")
.writeTo<HttpSink>("webhook", httpOpts);
})
.build();minta::Log::configure()
.writeTo<ConsoleSink>("console")
.subLogger("errors", [](SubLoggerConfiguration& sub) {
sub.filter("ERROR+")
.writeTo<CallbackSink>("alert", alertFn);
})
.build();
LUNAR_GERROR("Critical failure: {reason}", "reason", "disk full");Sub-loggers can be nested by using writeTo<SubLoggerSink>:
.subLogger("level1", [](SubLoggerConfiguration& sub) {
sub.filter("WARN+")
.writeTo<SubLoggerSink>("level2", [](SubLoggerConfiguration& inner) {
inner.filter("ERROR+")
.writeTo<CallbackSink>("deep-alert", alertFn);
});
})Sub-loggers isolate failures:
- A throwing sub-sink doesn't affect other sinks (parent or sibling)
- A throwing enricher is skipped; remaining enrichers still run
- A throwing filter predicate fails open (entry passes through)
-
SubLoggerSinkmembers are immutable after construction — safe for concurrentwrite()calls -
minLevelusesstd::atomic<LogLevel>(inherited fromISink) - Each sub-sink is responsible for its own thread safety (same contract as all ISink implementations)
- Sub-sinks are not registered in
LogManager's name registry (they live inside the sub-logger's scope) - Unnamed sub-sinks get auto-names:
sub_sink_0,sub_sink_1, etc. -
cloneEntry()deep-copies everything including exception chains — correctness over performance - Filter evaluation runs before
cloneEntry()to avoid unnecessary copies
class SubLoggerSink : public ISink {
public:
// Lambda constructor (SFINAE-constrained)
template<typename ConfigFn>
explicit SubLoggerSink(ConfigFn&& configFn);
void write(const LogEntry& entry) override;
void flush() override;
};class SubLoggerConfiguration {
public:
SubLoggerConfiguration& minLevel(LogLevel level);
SubLoggerConfiguration& filter(const std::string& compactFilter);
SubLoggerConfiguration& filterRule(std::function<bool(const LogEntry&)> predicate);
SubLoggerConfiguration& enrich(Enricher enricher);
template<typename SinkType, typename... Args>
SubLoggerConfiguration& writeTo(const std::string& name, Args&&... args);
template<typename SinkType, typename FormatterType, typename... Args>
SubLoggerConfiguration& writeTo(const std::string& name, Args&&... args);
// Unnamed variants (auto-named sub_sink_N)
template<typename SinkType, typename... Args>
SubLoggerConfiguration& writeTo(Args&&... args);
};class LoggerConfiguration {
// ...existing methods...
LoggerConfiguration& subLogger(const std::string& name,
std::function<void(SubLoggerConfiguration&)> configFn);
};