NICo Logging
How NICo services emit logs, the structured fields each line carries, and how to set log levels.
TL;DR
- Most NICo services log in logfmt (
key=valuepairs) to stdout; a few use other formats (e.g.nico-dnsemits JSON — see Coverage). - Every logfmt line begins with
level=and carries acomponent=field identifying the emitting component, so logs can be filtered by component instead of grepping the message text. - Log verbosity is controlled by
RUST_LOG(defaultinfo). - For distributed tracing (spans exported via OTLP), see Traces.
Log format
There are two kinds of line.
Events — one per log call:
Spans — emitted when a unit of work closes (level=SPAN), carrying the span name, id, its attributes,
and timing:
Common keys: level, component, msg, location (file:line); span_id when the line is inside a
span; plus any structured fields the call site adds (e.g. controller=, object_id=).
Log level
Verbosity is an EnvFilter directive set from RUST_LOG, defaulting
to info:
nico-api can also adjust its filter at runtime via nico-admin-cli set log-filter.
The component field
On logfmt lines, NICo sets the component field to one of the following:
State-controller lines also carry a controller=<name> field with the same value.
Adding it to new code
- New binary — set the default when building the
logfmtlayer:logfmt::layer().with_event_fields([logfmt::EventField::with_default("component", "nico-my-service")])— or pass the name tocarbide_host_support::init_logging("nico-my-service")if the binary uses that helper. A binary that omits this sets no defaultcomponent. - New in-process subsystem of
nico-api— add acomponentfield to the subsystem’s root span:tracing::span!(parent: None, Level::INFO, "my_subsystem", component = "my-subsystem", /* … */). Nested instrumented functions and spawned tasks carried with.instrument(...)/.in_current_span()inherit it.
By convention, NICo uses the
componentkey for the emitting component — don’t reuse the key for unrelated data; give domain values their own key.
Coverage
Binaries that do not use the logfmt layer carry no component: nico-dns (emits JSON), nico-pxe
(hand-rolled formatter), nico-ssh-console, nico-dpu-otel-agent. CLI/dev tools and mocks are out of scope.
Querying
Parse the logfmt line and filter on the field in whatever store the logs land in. For example, with logql (Loki):