feat: add native logging macros - #239
Draft
tisonkun wants to merge 2 commits into
Draft
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #205.
Summary
log!,fatal!,error!,warn!,info!,debug!,trace!, andlog_enabled!log!entry point:?debug capture, and:%display capturelogforthfacadelogfacade for librariesWhy this design
Explicit logger, not a second global singleton
The native macros require
logger::PR #226 deliberately removed the global logger from
logforth-core. Reintroducing one through macros would create two independent global configuration paths: Logforth's and thelogfacade's. It would also bring back the singleton, test-isolation, and context-propagation problems that motivated instance-oriented logging APIs across the ecosystem.The
logfacade now accepts an explicitlogger:argument, but its API still exposes only five levels and its global compile-time/runtime maximum applies even to explicit loggers. Logforth's native macros therefore stay instance-first and use eachLogger's filter as the authority.One generic level macro plus familiar conveniences
The six convenience macros cover the common path. The generic
log!accepts a level expression and makes all 24 Logforth/OpenTelemetry severities usable without addingtrace2!,trace3!, and so on.This shape follows the durable pattern used by Go's
slog.Logger.Log, Python'sLogger.log, and Log4j'sLogger.log(Level, ...): fixed-level conveniences paired with a generic level entry point. It also directly addresses repeated Rust requests for Notice, Critical, Fatal, or otherwise extensible levels:tracing::log!()to accept any log level tokio-rs/tracing#3585: Addtracing::log!()to accept any log level tokio-rs/tracing#3585fatal!is only a severity. It deliberately does not terminate the process or imply a flush.Structured and lazy by construction
The field grammar stays close to
log's key-value syntax to reduce migration cost:Plain values use the public
ToValueconversion trait and retain supported scalar types instead of becoming formatted strings.:?and:%are explicit escape hatches forDebugandDisplay. The macro checksLogger::enabledbefore formatting the message or evaluating fields.The macro also captures source metadata and supports structured-only records with an empty message.
Declarative macros in core, with no feature gate
The implementation uses hygienic
macro_rules!macros inlogforth-core, then re-exports them fromlogforth.This avoids:
LoggerfilteringThe macros are always available, introduce no new dependency, and use
$cratepaths so facade re-exports remain hygienic.Ecosystem research
The API was compared with:
log: five-level facade, global installation, explicit logger syntax, and typed key-valueshttps://docs.rs/log/latest/log/macro.log.html
slog: explicit contextual logger and structured field grammarhttps://docs.rs/slog/latest/slog/macro.log.html
tracing: static callsite metadata and dynamic-level constraintsAdd
tracing::log!()to accept any log level tokio-rs/tracing#3585log/slog: explicit logger, generic levels, typed attributes, and preflightEnabledhttps://pkg.go.dev/log/slog
logging: explicit loggers, integer levels, and genericLogger.loghttps://docs.python.org/3/library/logging.html
https://logging.apache.org/log4j/2.x/manual/customloglevels.html
isLevelEnabledhttps://github.com/pinojs/pino/blob/main/docs/api.md
ILogger: dependency-injected logger,IsEnabled, and allocation-aware generated logginghttps://learn.microsoft.com/en-us/dotnet/core/extensions/high-performance-logging
https://github.com/gabime/spdlog
The recurring Rust pain points considered here were:
Relevant discussions include rust-lang/log#149, #334, #343, #388, #541, #708 and tokio-rs/tracing#2081, #3585.
API surface
log!(logger: ..., [target: ...,] level, [fields;] "message", args...)fatal!/error!/warn!/info!/debug!/trace!(logger: ..., [target: ...,] [fields;] "message", args...)log_enabled!(logger: ..., [target: ...,] level)ToValuefor typed conversion of supported Rust valuesLogger, target, and level expressions are each evaluated once. The API accepts
Logger,&Logger, and dereferenceable owners such asArc<Logger>.Non-goals
logfacade for reusable librariesThose can be evaluated independently without constraining this base logging API.
Validation
cargo x testcargo-semver-checksforlogforth-coreandlogforthagainstorigin/main