kamu-logging 1.1.0

Small helper crate to configure structured logging for IMMER's services using the tracing ecosystem.
Documentation

kamu-logging

Crates.io docs.rs CI

License MSRV

Opinionated tracing setup for PT IMMER services. One-line init for the zero-config path; a builder for everything else (JSON output, custom env vars, OTLP export, correlation ids).

Install

[dependencies]
kamu-logging = "1"

MSRV: Rust 1.88 (edition 2024).

Features

Feature Default What it enables
systemd yes TTY-aware console + journald sink, RUST_LOG, logtracing 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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    kamu_logging::init()?;
    kamu_logging::info!("hello");
    Ok(())
}

init() picks a sensible default for the target: pretty TTY output when stdout is a terminal, journald when not, and RUST_LOG for filtering.

Configuration

For anything beyond the default path, build an InitOptions:

use kamu_logging::{Format, InitOptions, Sink, init_with};

init_with(
    InitOptions::default()
        .with_service_name("my-service")
        .with_default_filter("info,my_service=debug")
        .with_env_var("MY_SERVICE_LOG")
        .with_format(Format::Json)
        .with_sink(Sink::Stdout),
)?;

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) Treat duplicate init as Ok(()) (test harnesses, embedded runs)
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

JSON output for log aggregators

use kamu_logging::{Format, InitOptions, Sink, init_with};

init_with(
    InitOptions::default()
        .with_format(Format::Json)
        .with_sink(Sink::Stdout),
)?;

Or set KAMU_LOG_FORMAT=json KAMU_LOG_SINK=stdout at the process level for zero-code adoption. Each event is one line of JSON suitable for Vector, Promtail, Fluent Bit, or the Datadog Agent.

Actix Web

use actix_web::{App, HttpServer};
use kamu_logging::get_actix_web_logger;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    kamu_logging::init().expect("init logging");
    HttpServer::new(|| App::new().wrap(get_actix_web_logger()))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

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>().

Correlation outside HTTP

For queue consumers, scheduled tasks, or any non-HTTP entry point:

use kamu_logging::correlation::{with_id, extract_from_headers, DEFAULT_HEADER_CHAIN};

with_id("job-42", || {
    kamu_logging::info!("processing job");
});

The header-chain extractor is reusable for any framework — pass a closure that fetches a header by name:

let id = extract_from_headers(&headers, DEFAULT_HEADER_CHAIN, |h, name| {
    h.get(name).cloned()
});

OTLP export

Enable the with-otlp feature and attach an OtlpConfig:

use kamu_logging::{InitOptions, init_with, otlp::OtlpConfig};

init_with(
    InitOptions::default()
        .with_service_name("checkout-api")
        .with_otlp(
            OtlpConfig::new("https://otel-collector.example.com:4318")
                .with_service_name("checkout-api")
                .with_header("authorization", "Bearer …")
                .with_resource_attribute("deployment.environment", "production"),
        ),
)?;

Uses a synchronous SimpleSpanProcessor so no async runtime is required. High-throughput services should replace the exporter with a batch processor configured against their runtime — open an issue if you want a built-in option.

WASM

[dependencies]
kamu-logging = { version = "1", default-features = false, features = ["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. The function is idempotent on this target by design; subsequent calls are no-ops.

Cloudflare Workers

Use workers-rs as usual, disable default features, and enable wasm32:

[dependencies]
kamu-logging = { version = "1", default-features = false, features = ["wasm32"] }
worker = "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() returns Err(Error::AlreadyInitialized) on a second call. Surfaces library double-init as a bug.
  • init_or_skip() returns Ok(()) on a second call. Use from test harnesses and embedded CLI runs.
  • InitOptions::idempotent(true) does the same thing via the builder.

Re-exported tracing items

So you can avoid a separate tracing import for the basics:

use kamu_logging::{debug, info, warn, error, instrument, span, Level, Span};

Troubleshooting

Symptom Fix
No logs in container Set KAMU_LOG_SINK=stdout — default routes non-TTY to journald
Error::IO at init in a container journald socket unavailable; use KAMU_LOG_SINK=stdout
Tests hang at init() Use init_or_skip() per-test or InitOptions::idempotent(true)
OTLP exporter slow SimpleSpanProcessor is synchronous; high-volume needs a batch processor
service.name missing from fmt output Only attached to startup event + OTLP Resource; aggregators add it from infra metadata

SemVer policy

1.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

MIT — see LICENSE.