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"
MSRV: Rust 1.94 (edition 2024).
Features
| Feature | Default | What it enables |
|---|---|---|
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) |
wasm32 |
no | Cloudflare Worker / web console + panic hook (mutually exclusive) |
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::
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.