cosmossdk.io/log/v2 is the Cosmos SDK logging package.
At a high level, there are three pieces to understand:
log.NewLogger(...)creates the default Cosmos SDK logger. It is backed byzerolog.cosmossdk.io/log/v2/sloglets you satisfy the same SDKLoggerinterface with a standard library*slog.Logger.log.NewMultiLogger(...)fans one log call out to multiple SDK loggers. The SDK uses this during server startup when OpenTelemetry log exporting is enabled. To learn more about how we support OpenTelemetry, read the Telemetry docs.
log.NewLogger, which is automatically provisioned and set on sdk.Context.
Default Logger
The default implementation is a small wrapper aroundzerolog.
NewLogger writes human-readable console output by default. The server command wiring switches options based on CLI configuration, for example:
OutputJSONOption()for JSON logsLevelOption(...)for a global log levelFilterOption(...)for module-based filteringTraceOption(true)to include stack traces on error logsVerboseLevelOption(...)for temporary verbose mode
module field consistently. The package exposes log.ModuleKey for this:
module field when parsing values such as consensus:debug,*:error.
Structured Context
Logger.With(...) returns a derived logger with additional fields:
Context-Aware Logging
The v2Logger interface adds *Context methods:
Info,Warn,Error, andDebuglog without inspecting acontext.ContextInfoContext,WarnContext,ErrorContext, andDebugContextuse the provided context for trace correlation
zerolog implementation, the *Context methods extract the active OpenTelemetry span from ctx and add:
trace_idspan_idtrace_flagswhen present
Trace Correlation
When you want logs to line up with spans, use the context-aware methods.sdk.Context.StartSpan(...)returns a newsdk.Contextwith the Gocontext.Contextupdated to include the span.- The logger only sees trace information when you call one of the logger’s
*Contextmethods with that updated context.
*Context call, the default logger will not add trace fields to the log record.
log/slog
cosmossdk.io/log/v2/slog is an adapter for code that already has a standard library *slog.Logger.
*slog.Logger satisfy the Cosmos SDK Logger interface. Filtering, formatting, sinks, and handler behavior are whatever the underlying slog.Logger is configured to do.
MultiLogger
log.NewMultiLogger(loggers...) returns a logger that dispatches each log call to every wrapped logger.
That includes:
- ordinary log methods such as
Info(...) - context-aware methods such as
InfoContext(...) With(...), which derives a child logger for each wrapped logger
VerboseModeLogger, SetVerboseMode(...) is also forwarded.
In other words, MultiLogger is just fanout. It does not merge records or add new fields on its own.
When The SDK Configures MultiLogger
MultiLogger is not created for every app automatically.
During the node’s server start, the SDK first builds the normal server logger from CLI/config flags. That logger is the usual zerolog-backed logger.
Then the SDK initializes OpenTelemetry from config/otel.yaml. If telemetry.IsOtelLoggerEnabled() reports that the global OpenTelemetry logger provider has active log processors/exporters, the SDK wraps the existing server logger like this:
- the existing console/stdout logger
- an OpenTelemetry-backed logger for export
What otelslog Is
otelslog is an OpenTelemetry bridge for Go’s log/slog package.
More specifically, it provides a slog.Handler and slog.Logger that convert slog.Record values into OpenTelemetry log records and sends them to the configured OpenTelemetry logger provider.
In the Cosmos SDK startup path:
otelslog.NewLogger("")creates an*slog.Loggerbacked by that bridgecosmossdk.io/log/v2/slog.NewCustomLogger(...)wraps it so it satisfies the SDKLoggerinterfacelog.NewMultiLogger(...)fans logs out to both the normalzerologlogger and the OpenTelemetry bridge
slog has native InfoContext/WarnContext/ErrorContext/DebugContext methods, the otelslog side receives the context directly. That means trace/span correlation is handled by the OpenTelemetry logging pipeline without the SDK needing to manually inject trace_id fields into that branch.
Two Common Setups
1. Stdout only
If you do not configure an OpenTelemetry logger provider, logs only go to the normal SDK logger output. This does not restrict you from log correlation, however. For trace correlation in tools such as Grafana Tempo and Loki, you can:- Emit JSON logs to stdout/stderr.
- Scrape those logs with an agent such as the OpenTelemetry Collector filelog receiver.
- Forward them to Loki.
- Query by the
trace_idfield in the logs.
trace_id is only injected into the log if a contextual method was called with a context that contains an active span.
2. OpenTelemetry log exporter enabled
Ifotel.yaml enables an OpenTelemetry log pipeline with real log processors/exporters, the SDK configures a MultiLogger.
In that setup:
- console logging still works as before
- logs are also exported through OpenTelemetry
- context-aware log calls carry trace context into the OpenTelemetry branch as well
Future Direction
Today the SDK uses aMultiLogger because the default logger is zerolog, while OpenTelemetry currently offers a bridge for slog rather than zerolog.
If a first-class zerolog bridge becomes available and suitable, that would likely be a simpler export path than maintaining a separate fanout logger. Relevant discussion: