Skip to content

Design optional logforth-registry for configuration-driven logger lookup #225

Description

@tisonkun

Background

We are removing/rethinking the global default logger (#214). That keeps the core API explicit, but applications may still want a startup-provided, process-wide way to look up and reuse loggers without passing a Logger instance through every layer.

A future optional crate, tentatively named logforth-registry, could provide this capability. The idea is similar in spirit to OpenDAL-style registries: users provide a registry configuration, and application code can resolve a named/model/target-specific logger from that registry.

Goals

  • Provide an optional global registry that can be initialized at application startup.
  • Load logger definitions from a config file and/or programmatic registry settings.
  • Select loggers by dimensions such as target, module/logger name, app-defined model name, profile/tenant, or other selector settings.
  • Support a fallback root logger when no more specific logger matches.
  • Cache constructed logger instances in the registry so callers can reuse them without rebuilding pipelines or passing handles everywhere.
  • Support per-model/per-target logger relationships, including inheritance or fallback behavior where appropriate.
  • Keep this outside the core logger path so we do not recreate an implicit default global logger in logforth-core.

Prior art to study

These are not direct API models for Rust, but they are useful references for:

  • config-file shape and stability;
  • root logger fallback;
  • target/logger-name based selection;
  • appender/layout/filter references;
  • additivity/inheritance-like behavior;
  • runtime reload or replacement questions.

Sketch

A registry config might declare reusable appenders/layouts/filters, logger definitions, selector rules, and a root logger:

[root]
logger = "console"

[[loggers]]
name = "console"
selector.target = "*"
appenders = ["stdout"]

[[loggers]]
name = "model-a-file"
selector.model = "model-a"
selector.target = "my_crate::model_a::*"
appenders = ["rolling_file"]

The exact format is intentionally undecided. The important part is that the registry can map runtime lookup input to a cached logger instance:

let registry = logforth_registry::Registry::from_config(config)?;
logforth_registry::set_global(registry)?;

let logger = logforth_registry::get(LoggerSelector::new()
    .target("my_crate::model_a::worker")
    .setting("model", "model-a"));

Open questions

  • Should logforth-registry expose only explicit Registry values, or also provide a global singleton API?
  • What should the selector model look like: target/module path only, arbitrary key-value dimensions, typed dimensions, or a combination?
  • How should logger inheritance/additivity work, if at all?
  • What is the minimum useful config format: TOML, YAML, JSON, or format-agnostic serde data structures?
  • Should registry entries build full Logger values eagerly at startup, lazily on first lookup, or support both?
  • How should reload/reconfiguration work without surprising existing cached handles?
  • How should this interact with the log bridge and any future tracing-style bridge?
  • What belongs in logforth-core as reusable primitives, and what should remain in the optional registry crate?

Non-goals

  • Reintroducing a mandatory default global logger in core.
  • Making all applications configure logging through a registry.
  • Locking in a Log4j/Logback-compatible configuration syntax.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions