The most popular logging crate, tracing, has problems with nested JSON unless you use the valuable crate with it which is unstable and behind a feature flag for 3 years... but that still has issues with enums and doesn't feel natural to use with .as_value() everywhere. The slog crate has similar issues—I've written about both here.
If you just want a simple JSON logger, you might find this useful.
- Batched, non-blocking writes
- Graceful shutdown (flushes on exit)
- Falls back to sync writes if buffer is full
- Customizable colors, timestamps, batch sizes
- Pretty-printing mode for development
- Works with any
Serializetype - Macros!
debug!(),info!(),warn!(), anderror!() - Global context fields that appear in every log message
cargo add sjluse sjl::{debug, error, info, warn, LogLevel, Logger, RGB};
use serde::Serialize;
use serde_json::json;
#[derive(Serialize)] // This is all you need
struct User {
id: u64,
name: String,
}
#[derive(Serialize)]
enum OrderStatus {
Pending,
Shipped { tracking_number: String },
Delivered,
}
#[derive(Serialize)]
struct Order {
user: User,
items: Vec<OrderItem>,
}
#[derive(Serialize)]
struct OrderItem {
name: String,
price: f64,
quantity: u32,
status: OrderStatus,
}
fn main() {
// Initialize once at startup
Logger::init()
// Optional config
.min_level(LogLevel::Debug) // Minimum log level (default: Debug)
.batch_size(100) // Logs per batch (default: 50)
.batch_duration_ms(100) // Max ms before flush (default: 50)
.buffer_size(5000) // Channel capacity (default: 1024)
.timestamp_format("%Y-%m-%dT%H:%M:%S%.3fZ") // ISO 8601 (default)
.timestamp_key("tz") // Rename this field if you want (default: timestamp)
.pretty(true) // Pretty-print JSON (default: false)
.debug_color(RGB::new(38, 45, 56)) // Customize colors
.info_color(RGB::new(15, 115, 255))
.warn_color(RGB::new(247, 155, 35))
.error_color(RGB::new(255, 0, 0))
// Context fields appear in EVERY log message at the top level
.context("environment", "production")
.context("service", "order-api")
.context("metadata", json!({
"instance_id": "i-1234567890abcdef0",
"pod_name": "order-api-7d4f8c9b5-x8k2p",
"git_sha": "abc123f"
}))
// Call this at the end
.build();
// Strings
debug!("App started");
info!("Server listening", "0.0.0.0:8080");
// Structs
info!(User { id: 1, name: "Alice".into() });
info!("User authenticated", User { id: 1, name: "Alice".into() });
// Enums (serialize correctly!)
warn!(OrderStatus::Pending);
warn!(OrderStatus::Shipped { tracking_number: "1Z999AA10123456784".into() });
// Ad-hoc JSON
error!(json!({
"error": "connection_failed",
"host": "db.example.com"
}));
// Complex: Vec of structs with enums
info!("Order processed", Order {
user: User { id: 42, name: "John".into() },
items: vec![
OrderItem {
name: "Widget".into(),
price: 29.99,
quantity: 2,
status: OrderStatus::Shipped { tracking_number: "1Z999AA10123456784".into() },
},
OrderItem {
name: "Gadget".into(),
price: 49.99,
quantity: 1,
status: OrderStatus::Pending,
},
],
});
}When .pretty(true) is enabled, logs are formatted with indentation and newlines for easier reading during development:
{
"data": {
"items": [
{
"name": "Widget",
"price": 29.99,
"quantity": 2,
"status": {
"Shipped": {
"tracking_number": "1Z999AA10123456784"
}
}
},
{
"name": "Gadget",
"price": 49.99,
"quantity": 1,
"status": "Pending"
}
],
"user": {
"id": 42,
"name": "John"
}
},
"environment": "production",
"level": "INFO",
"message": "Order processed",
"metadata": {
"git_sha": "abc123f",
"instance_id": "i-1234567890abcdef0",
"pod_name": "order-api-7d4f8c9b5-x8k2p"
},
"service": "order-api",
"tz": "2025-10-31T07:33:05.627Z"
}With .pretty(false) or omitted (default), logs are output as single-line JSON:
{"level":"DEBUG","tz":"2025-10-31T07:33:44.333Z","message":"App started","environment": "production", "service": "order-api", "metadata": {"git_sha":"abc123f","instance_id":"i-1234567890abcdef0","pod_name":"order-api-7d4f8c9b5-x8k2p"}}
{"level":"INFO","tz":"2025-10-31T07:33:44.333Z","message":"Server listening","data":"0.0.0.0:8080","environment": "production", "service": "order-api", "metadata": {"git_sha":"abc123f","instance_id":"i-1234567890abcdef0","pod_name":"order-api-7d4f8c9b5-x8k2p"}}
{"level":"INFO","tz":"2025-10-31T07:33:44.333Z","data":{"id":1,"name":"Alice"},"environment": "production", "service": "order-api", "metadata": {"git_sha":"abc123f","instance_id":"i-1234567890abcdef0","pod_name":"order-api-7d4f8c9b5-x8k2p"}}
{"level":"INFO","tz":"2025-10-31T07:33:44.333Z","message":"User authenticated","data":{"id":1,"name":"Alice"},"environment": "production", "service": "order-api", "metadata": {"git_sha":"abc123f","instance_id":"i-1234567890abcdef0","pod_name":"order-api-7d4f8c9b5-x8k2p"}}
{"level":"WARN","tz":"2025-10-31T07:33:44.333Z","message":"Pending","environment": "production", "service": "order-api", "metadata": {"git_sha":"abc123f","instance_id":"i-1234567890abcdef0","pod_name":"order-api-7d4f8c9b5-x8k2p"}}
{"level":"WARN","tz":"2025-10-31T07:33:44.333Z","data":{"Shipped":{"tracking_number":"1Z999AA10123456784"}},"environment": "production", "service": "order-api", "metadata": {"git_sha":"abc123f","instance_id":"i-1234567890abcdef0","pod_name":"order-api-7d4f8c9b5-x8k2p"}}
{"level":"ERROR","tz":"2025-10-31T07:33:44.333Z","data":{"error":"connection_failed","host":"db.example.com"},"environment": "production", "service": "order-api", "metadata": {"git_sha":"abc123f","instance_id":"i-1234567890abcdef0","pod_name":"order-api-7d4f8c9b5-x8k2p"}}