Skip to content

Sub Logger

LunarEC's AI edited this page Feb 23, 2026 · 1 revision

Sub-Logger (Nested Pipeline)

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.

Quick Start

#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 alert

How It Works

A sub-logger is implemented as SubLoggerSink — an ISink that owns a mini pipeline:

  1. Parent logger dispatches a LogEntry to all sinks (including sub-logger sinks)
  2. SubLoggerSink::write() evaluates its own filters against the entry
  3. If the entry passes, it's deep-copied via cloneEntry() and enriched with sub-logger enrichers
  4. 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")

Builder API

subLogger(name, configFn)

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();

Filter Layering

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.

Examples

Error Alerting

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();

Audit Trail

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)

Multiple Sub-Loggers

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();

Async Sub-Logger

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();

Global Logger

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");

Nested Sub-Logger

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);
       });
})

Error Isolation

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)

Thread Safety

  • SubLoggerSink members are immutable after construction — safe for concurrent write() calls
  • minLevel uses std::atomic<LogLevel> (inherited from ISink)
  • Each sub-sink is responsible for its own thread safety (same contract as all ISink implementations)

Design Notes

  • 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

API Reference

SubLoggerSink

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;
};

SubLoggerConfiguration

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);
};

LoggerConfiguration additions

class LoggerConfiguration {
    // ...existing methods...

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

Clone this wiki locally