kvlog 0.1.6

Fast Structual and Hierarchical binary logging for rust
Documentation
# kvlog: High-performance structured binary logging for Rust.

[![Crates.io](https://img.shields.io/crates/v/kvlog?style=flat-square)](https://crates.io/crates/kvlog)
[![Docs.rs](https://img.shields.io/docsrs/kvlog?style=flat-square)](https://docs.rs/kvlog/latest/kvlog/)
[![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)

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.

![landing](https://github.com/user-attachments/assets/72b40382-ed67-430b-a2f5-a73dd0633b7b)

## Usage

Add to your `Cargo.toml`:

```toml
[dependencies]
kvlog = "0.1"
```

### Basic Logging

```rust
// 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);
```

### Log Levels

- `debug!` - Development-only logging (requires `debug` feature)
- `info!` - Useful context about application state
- `warn!` - Expected but potentially problematic conditions
- `error!` - Unexpected failures requiring intervention

### Format Specifiers

```rust
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);
```

### Conditional Fields

```rust
let response_length: Option<usize> = Some(1024);
kvlog::info!(
    "Response sent",
    if let Some(len) = response_length { length = len }
);
```

### Conditional Logging

Use an `EnvGuard` to enable a log site from an environment variable:

```rust
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!`.

### Timer

```rust
let timer = kvlog::Timer::start();
// ... do some work ...
kvlog::info!("Operation completed", elapsed = timer);
```

### Spans

kvlog supports hierarchical spans for distributed tracing. Span markers:

- `span.start = id` - Mark the beginning of a span
- `span.current = id` - Log within an active span
- `span.end = id` - Mark the end of a span
- `span.parent = Some(parent_id)` - Establish parent-child relationship (use with `span.start`)

Basic span usage:

```rust
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:

```rust
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:

```rust
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 restored
```

## Default Test Logger

When 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:

```rust
#[test]
fn my_test() {
    // No setup needed - logs are automatically captured
    kvlog::info!("Test starting", test_id = 42);

    // ... test logic ...

    kvlog::info!("Test complete");
}
```

```bash
cargo test -- --nocapture
```

This zero-configuration behavior is ideal for development and debugging. For production use, initialize a collector for better performance and output control.

## Collector Configuration

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:

- `Stdout`
- `Directory:/var/log/my_app`
- `SingleFile:/tmp/app.log`
- `my-service@SocketOrStdout:/tmp/collector.sock`

## Features

- `debug` - Enable the `debug!` macro (compiled out by default)
- `jiff-02` - Integration with the jiff datetime library

## License

MIT