kvlog provides macros for emitting structured log messages with key-value pairs, optimized for high throughput and fast compile times. Log messages are encoded in a compact binary format with nanosecond-resolution timestamps.
Add to your Cargo.toml:
[dependencies]
kvlog = "0.1"// Initialize the collector
let _guard = kvlog::spawn_collector_from_env(Some("my-service"), false);
// Simple message
kvlog::info!("Request completed");
// With key-value pairs
let status = 200u16;
let object_id = 42u64;
kvlog::info!("Request completed", status, object_id);
// Explicit key names
kvlog::info!("Request completed", status = 200, id = object_id);debug!- Development-only logging (requiresdebugfeature)info!- Useful context about application statewarn!- Expected but potentially problematic conditionserror!- Unexpected failures requiring intervention
let err = "connection refused";
let config = vec![1, 2, 3];
// %expr uses Display formatting
kvlog::error!("Failed to connect", %err);
// ?expr uses Debug formatting
kvlog::warn!("Unexpected config", ?config);let response_length: Option<usize> = Some(1024);
kvlog::info!(
"Response sent",
if let Some(len) = response_length { length = len }
);Use an EnvGuard to enable a log site from an environment variable:
static AUDIO_LOGS: kvlog::EnvGuard =
kvlog::EnvGuard::new("APP_AUDIO_LOGS");
kvlog::info!(AUDIO_LOGS; "Audio initialized", codec = "opus");The environment variable must be present to enable the guard. The exact value
0 disables logging; any other value enables it. The result is cached after
the first check, and EnvGuard::set can override it explicitly. Declare guards
as static, not const, so that cached and explicitly set values persist.
The expression before the semicolon has its is_enabled() method called. When
it returns false, the logger is not accessed and the rest of the log expression
is not evaluated. This syntax is supported by debug!, info!, warn!, and
error!.
let timer = kvlog::Timer::start();
// ... do some work ...
kvlog::info!("Operation completed", elapsed = timer);kvlog supports hierarchical spans for distributed tracing. Span markers:
span.start = id- Mark the beginning of a spanspan.current = id- Log within an active spanspan.end = id- Mark the end of a spanspan.parent = Some(parent_id)- Establish parent-child relationship (use withspan.start)
Basic span usage:
use kvlog::SpanID;
let span = SpanID::next();
kvlog::info!("Starting operation", span.start = span);
kvlog::info!("Processing", span.current = span);
kvlog::info!("Operation complete", span.end = span);Nested spans with parent-child relationships:
use kvlog::SpanID;
let request_span = SpanID::next();
kvlog::info!("Request received", span.start = request_span);
// Create a child span for database work
let db_span = SpanID::next();
kvlog::info!("Querying database", span.start = db_span, span.parent = Some(request_span));
kvlog::info!("Query complete", span.end = db_span);
kvlog::info!("Request complete", span.end = request_span);Using SpanID::enter() to set thread-local span context:
use kvlog::SpanID;
fn do_database_work(parent: SpanID) {
let span = SpanID::next();
kvlog::info!("DB query", span.start = span, span.parent = Some(parent));
kvlog::info!("DB complete", span.end = span);
}
let span = SpanID::next();
let _guard = span.enter(); // Set as current thread-local span
kvlog::info!("Request received", span.start = span);
do_database_work(span); // Can access current span via SpanID::current()
kvlog::info!("Request complete", span.end = span);
// Guard dropped here, previous span context restoredWhen no collector is explicitly initialized, kvlog automatically emits logs to stdout in a human-readable format. This works seamlessly with Rust's test runner - use cargo test -- --nocapture to see log output during tests:
#[test]
fn my_test() {
// No setup needed - logs are automatically captured
kvlog::info!("Test starting", test_id = 42);
// ... test logic ...
kvlog::info!("Test complete");
}cargo test -- --nocaptureThis zero-configuration behavior is ideal for development and debugging. For production use, initialize a collector for better performance and output control.
For production or when you need specific output behavior, configure a collector via the KVLOG_COLLECTOR_CONFIG environment variable.
Format: [SERVICE_NAME@]KIND[:PATH]
| Kind | Description |
|---|---|
Stdout |
Log to standard output |
Directory:/path |
Log to timestamped files in directory |
SingleFile:/path |
Log to a single file |
SocketOrStdout |
Log to Unix socket, fallback to stdout |
Examples:
StdoutDirectory:/var/log/my_appSingleFile:/tmp/app.logmy-service@SocketOrStdout:/tmp/collector.sock
debug- Enable thedebug!macro (compiled out by default)jiff-02- Integration with the jiff datetime library
MIT
