Why?
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.
Features
- 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
Installation
Usage
use 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, }, ], }); }
Pretty Mode
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"
}Compact Mode (Default)
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"}}