kamu-logging
Structured tracing setup for native services and Cloudflare Workers. Includes
console and journald output, Actix request spans, W3C correlation parsing, and
optional OTLP export.
Part of the kamu-public-crates workspace.
Install
[]
= "2"
Features
| Feature | Default | What it enables |
|---|---|---|
correlation |
yes | W3C traceparent parsing + correlation spans; installs no subscriber |
systemd |
yes | TTY-aware console + journald sink, RUST_LOG, log → tracing bridge |
with-actix-web |
yes | Correlation-enriched Actix Web middleware |
with-otlp |
no | OpenTelemetry OTLP exporter (HTTP/protobuf); requires systemd |
wasm32 |
no | Cloudflare Worker / web console + panic hook (mutually exclusive) |
systemd, wasm32, and with-actix-web each imply correlation, and at least
one of the four must be enabled. systemd and wasm32 are mutually exclusive,
as are wasm32 and the Actix Web and OTLP features.
Quickstart
On native targets, init() writes to stderr: pretty output when stderr is a
terminal and compact output otherwise. RUST_LOG controls filtering. Journald
is available through explicit configuration.
Configuration
For anything beyond the default path, build an InitOptions:
use ;
init_with?;
Builder methods (all consume self, all return Self):
| Method | Purpose |
|---|---|
with_service_name(n) |
Attach service.name to the startup event + OTLP Resource |
with_default_filter(f) |
Filter directive used when the env var is unset |
with_env_var(v) |
Env var read for the filter (default RUST_LOG) |
with_format(f) |
Auto / Compact / Pretty / Json |
with_sink(s) |
Auto / Stdout / Stderr / Journald |
idempotent(true) |
Accept a repeated installation owned by this crate |
with_otlp(cfg) |
(with-otlp) Add an OTLP exporter layer |
Env-var triggers (no code change)
| Variable | Values | Effect |
|---|---|---|
RUST_LOG |
tracing-subscriber directive | Filter directive (overridable per init) |
KAMU_LOG_FORMAT |
auto, compact, pretty, json |
Sets Format when the option is Auto |
KAMU_LOG_SINK |
auto, stdout, stderr, journald |
Sets Sink when the option is Auto |
Unknown values are errors. They never silently select Auto.
JSON output for log aggregators
use ;
init_with?;
Or set KAMU_LOG_FORMAT=json KAMU_LOG_SINK=stdout without rebuilding. Each
event is one JSON line suitable for a log collector.
Actix Web
use ;
use get_actix_web_logger;
async
get_actix_web_logger() uses an EnrichedRootSpanBuilder that adds a
correlation_id field to the root span by extracting (in order):
X-Request-ID, X-Correlation-ID, traceparent. For a custom builder,
use get_actix_web_logger_with::<MyBuilder>().
Request and correlation IDs must contain 1–128 visible ASCII bytes. A
traceparent value is used only after its complete four-field prefix passes
W3C validation.
Correlation outside HTTP
For queue consumers, scheduled tasks, or any non-HTTP entry point:
use ;
with_id;
The header-chain extractor is reusable for any framework — pass a closure that fetches a header by name:
let id = extract_from_headers;
To inspect every validated W3C field:
use TraceParent;
let parent = parse?;
assert_eq!;
assert!;
# Ok::
Correlation without a subscriber
A library has no business choosing the logging backend for the binary that
embeds it. Take correlation on its own and nothing that installs or sinks logs
enters the dependency graph — no tracing-subscriber, no journald, no console
writer, no exporter:
[]
= { = "2", = false, = ["correlation"] }
That build exposes kamu_logging::correlation and the re-exported tracing
vocabulary. init, init_with, InitOptions, Format, Sink, and Error
belong to the subscriber-owning surface and require systemd or wasm32.
The same holds one layer up: features = ["with-actix-web"] alone gives the
correlation-enriched middleware without committing the binary to a subscriber.
OTLP export
Enable the with-otlp feature and attach an OtlpConfig:
use ;
init_with?;
OtlpConfig::new accepts the collector base URL and adds /v1/traces.
with_service_name on InitOptions supplies the resource name unless the OTLP
configuration overrides it. Debug output redacts the endpoint, all header
values, and secret-like resource attributes.
Export uses an in-process BatchSpanProcessor by default: spans are buffered
and flushed from a dedicated background OS thread, so export never blocks the
thread that closed the span and no async runtime is required. Tune the
buffer with with_max_queue_size, with_scheduled_delay, and
with_max_export_batch_size.
The batch buffer drains on a periodic timer (~5 s by default), so a process
that exits abruptly can lose the last, unflushed batch. Call shutdown_otlp()
(or flush_otlp()) before exit — e.g. from your SIGTERM handler — to drain
it:
// after the server stops accepting work, before the process exits:
shutdown_otlp?;
For deterministic synchronous export (handy in tests or short-lived CLIs), opt
back into the inline processor with
.with_processor(SpanProcessorMode::Simple).
WASM
[]
= { = "2", = false, = ["wasm32"] }
init() on wasm32 installs console_error_panic_hook and a
tracing-subscriber console writer suitable for Cloudflare Workers Logs.
Format::Auto resolves to JSON, while Sink::Auto, Sink::Stdout, and
Sink::Stderr all write to the JavaScript console. A repeated strict init()
returns Error::AlreadyInitialized; use init_or_skip() when repetition is
intentional.
Cloudflare Workers
Use workers-rs as usual, disable default features, and enable wasm32:
[]
= { = "2", = false, = ["wasm32"] }
= "0.8"
The repository includes a standalone Worker app in
examples/cloudflare-worker/. For setup, Wrangler config, Workers Logs, and
correlation-id examples, see the Cloudflare Workers guide.
Idempotence
init()returnsErr(Error::AlreadyInitialized)on a second call. Surfaces library double-init as a bug.init_or_skip()returnsOk(())on a second call. Use from test harnesses and embedded CLI runs.InitOptions::idempotent(true)does the same thing via the builder.- A subscriber or
logfacade installed elsewhere returnsForeignGlobalSubscriberorForeignGlobalLogger, even in idempotent mode.
ForeignGlobalLogger is detected after kamu-logging commits its tracing
subscriber and any OTLP provider. Those remain active; the pre-existing logger
continues to own the log facade, and retries return the same conflict.
Migrating from 1.x
| 1.x behavior | 2.x replacement |
|---|---|
Unknown format or sink became Auto |
Handle the Result from from_env_value, or use FromStr |
Non-TTY Auto selected journald |
Select Sink::Journald explicitly |
TracingGlobal / TracingLog |
ForeignGlobalSubscriber / ForeignGlobalLogger |
Partial traceparent acceptance |
Use TraceParent::parse; invalid parent IDs and flags are rejected |
Re-exported tracing items
So you can avoid a separate tracing import for the basics:
use ;
Troubleshooting
| Symptom | Fix |
|---|---|
| Logs should go to stdout | Set KAMU_LOG_SINK=stdout or select Sink::Stdout |
Error::IO with explicit journald |
Journal socket is unavailable; choose stdout or stderr |
ForeignGlobalSubscriber during tests |
Install one harness subscriber, or let kamu-logging own the global slot |
ForeignGlobalLogger |
Remove or coordinate the earlier log owner; retrying cannot replace it |
| OTLP exporter is slow in simple mode | Keep the default batch processor for high-volume services |
service.name missing from fmt output |
It is on the startup event and OTLP resource, not every formatted log event |
Examples
Run with cargo run --example <name> --features systemd:
| Example | What it shows |
|---|---|
minimal |
Zero-config init(). |
json_stdout |
JSON on stdout for log aggregators (Vector, Promtail, Datadog). |
actix |
Correlation-enriched root spans; also needs with-actix-web. |
The standalone Worker app is examples/cloudflare-worker/,
covered in Cloudflare Workers above.
SemVer policy
2.x.y — breaking changes only on major bumps. Additive changes ship as minor
releases. Bug fixes ship as patches. The Error enum is
#[non_exhaustive]; new variants are not breaking.
License
Dual-licensed under either MIT or Apache-2.0 at
your option (MIT OR Apache-2.0). Previously MIT-only through 1.1.1.