slog-json
JSON drain for slog-rs that serializes Rust log records to structured, machine-readable JSON.
Repository Health
Technical Analysis
slog-json is a Drain implementation for the slog-rs structured logging framework that renders every log record as a single JSON object written to any io::Write target (stdout, stderr, a file, or a socket). It builds on slog’s key-value record model, walking a record’s default fields plus any logger-scoped and per-call key-value pairs and serializing them through serde_json, so JSON output composes naturally with the rest of the slog-rs drain ecosystem (async wrapping, level filtering, fan-out to multiple drains).
It ships with a sensible default key set (ts, level, msg) via Json::default(), or a builder (Json::new()/JsonBuilder) for full control over newline behavior, flushing, pretty-printing, and additional static key-value pairs — making it a common choice for shipping Rust application logs to log aggregators, container log collectors, or any pipeline that expects line-delimited JSON.
What You Get
- A
Json<W>Drainthat serializes any slogRecordplus its owned and logger key-values into a single JSON object viaserde_json Json::default(io)for a zero-configuration drain withts(RFC3339 timestamp),level, andmsgfields pre-populatedJsonBuilderfor custom configuration:set_newlines,set_flush,set_pretty, andadd_key_valuefor extra static or computed fields- Optional
nested-valuesfeature (viaerased-serde) to serialize structured/nested values instead of flattening everything to strings - Optional
dynamic-keysfeature that forwards to slog’s own dynamic-keys support for runtime-determined key names
Common Use Cases
- Emitting line-delimited JSON application logs to stdout/stderr for collection by Docker, Kubernetes, or a log shipper (Fluentd, Vector, Filebeat)
- Writing structured JSON logs to a rotating file for later ingestion into Elasticsearch, Loki, or a similar log store
- Combining with
slog-asyncto serialize logs to JSON off the hot path in latency-sensitive services - Fanning out the same slog
Loggerto both a human-readable terminal drain and a JSON drain for production log pipelines
Under The Hood
Architecture - The crate centers on two types: Json<W: io::Write>, the slog::Drain implementation, and JsonBuilder<W>, its configuration builder. On each log() call, Json picks a serde_json::Serializer (pretty or compact, per the pretty flag) wrapping the wrapped io::Write, then delegates to log_impl, which builds a SerdeSerializer<S> adapter — a newtype around serde_json’s SerializeMap state — and feeds it three things in order: the builder’s static values (an OwnedKVList produced by add_key_value/add_default_keys), the logger’s own OwnedKVList, and finally the record’s per-call key-values, so later keys can shadow earlier ones in the resulting map. SerdeSerializer implements slog’s Serializer trait (emit_bool, emit_str, emit_arguments, etc.), translating each slog value type into a serialize_entry call on the underlying serde map; formatted (emit_arguments) values are rendered into a thread-local String buffer (TL_BUF) to avoid a fresh allocation per field. After serialization, Json optionally appends a newline and flushes the writer, both gated by builder flags.
Tech Stack - Pure Rust, edition = "2018", MSRV 1.53. Core dependencies are slog (the drain/record/KV trait ecosystem this crate plugs into), serde and serde_json (the actual JSON serialization), and time (RFC3339 timestamp formatting for the default ts field, replacing an earlier chrono dependency per the changelog). erased-serde is an optional dependency gated behind the nested-values Cargo feature for serializing arbitrary nested structured values. slog-async appears only as a dev-dependency, used in the examples/pretty.rs example to demonstrate non-blocking usage. No async runtime, no unsafe code beyond what slog/serde pull in, no macros beyond slog’s own o!.
Code Quality - There is no dedicated test suite (no tests/ directory, no #[test] functions in src/lib.rs); the only executable verification is a doctest embedded in the crate-level doc comment showing basic Json::default usage, plus the examples/pretty.rs example. The single-file src/lib.rs (~350 lines) is organized with {{{/}}} vim fold markers into clearly delimited Imports, Serialize, Json, and JsonBuilder sections, uses #![warn(missing_docs)] to enforce doc coverage on public items, and keeps error handling consistent by wrapping serde errors into io::Error::new(io::ErrorKind::Other, ...) via a shared impl_m! macro. Field and method naming follows slog-rs conventions consistently (add_key_value, set_pretty, set_newlines) but the absence of unit tests is a real gap for a crate handling record serialization.
API Design - The API is a small, idiomatic builder over a single Drain type: Json::default(io) covers the common case in one call, while Json::new(io) returns a JsonBuilder for set_pretty/set_newlines/set_flush/add_key_value chaining ending in .build(). Because Json<W> implements slog::Drain, it composes directly with Fuse, Mutex, and slog-async::Async the same way any other slog drain does, so there’s effectively zero boilerplate beyond what slog itself requires. The main ergonomic rough edge is that the crate assumes familiarity with slog-rs’s own drain/KV vocabulary (OwnedKVList, SendSyncRefUnwindSafeKV) — it is not meant to be used standalone, and its docs point users toward the parent slog-rs project for broader context.