11 unstable releases (3 breaking)

new 0.4.0 Apr 18, 2026
0.3.0 Apr 12, 2026
0.2.2 Apr 7, 2026
0.1.5 Apr 3, 2026
0.1.3 Mar 30, 2026

#349 in Database interfaces

Download history 139/week @ 2026-03-27 169/week @ 2026-04-03 222/week @ 2026-04-10

530 downloads per month
Used in 117 crates (54 directly)

MIT/Apache

125KB
2.5K SLoC

vil_log — VIL Semantic Log System

Zero-copy, non-blocking, structured logging for high-throughput VIL pipelines.

Why not tracing?

tracing (fmt + NonBlocking) VIL access_log! (flat) VIL app_log! (dynamic)
ns/event ~600 ~130 ~350
M events/s 1.7 7.5 2.9
Speedup baseline 4.5x faster 1.7x faster

VIL Log achieves this by:

  • No string formatting on hot path — format later, on drain thread
  • No heap allocation — fixed 256-byte slots in pre-allocated SPSC ring
  • No lock contention — single atomic CAS per push (SPSC, not MPMC)
  • Ring full? Drop + count. Never block the caller.

Architecture

HOT PATH (~35-140ns)                 COLD PATH (async)
────────────────────                 ─────────────────────────────
app_log!() ──┐                       ┌─► StdoutDrain (pretty/json)
access_log!()┤                       ├─► FileDrain (rolling)
ai_log!()    ├─► SpscRing ───────────┼─► ClickHouseDrain (batch)
db_log!()(lock-free,         ├─► NatsDrain (fan-out)
mq_log!()    │    256B slots)        └─► MultiDrain (N drains)
tracing ─────┘
  (bridge)

Quick Start

use vil_log::prelude::*;
use vil_log::drain::StdoutDrain;
use vil_log::runtime::init_logging;

#[tokio::main]
async fn main() {
    // Initialize with stdout drain
    let config = LogConfig {
        ring_slots: 16384,          // 16K slots = 4MB ring
        level: LogLevel::Info,
        batch_size: 1024,
        flush_interval_ms: 100,
    };
    init_logging(config, StdoutDrain::pretty());

    // Structured business event
    app_log!(Info, "order.created", {
        order_id: 12345u64,
        amount: 50000u64,
    });

    // Flat struct — zero serialization, pure memcpy
    access_log!(Info, AccessPayload {
        method: 1,  // POST
        status_code: 201,
        duration_us: 2300,
        path_hash: vil_log::dict::register_str("/api/orders"),
        ..Default::default()
    });
}

Development vs Production

During development, don't call init_logging(). All macros fall back to standard tracing — familiar output, zero setup:

tracing_subscriber::fmt().pretty().init();
app_log!(Info, "order.created", { order_id: 123u64 });
// → standard tracing pretty output

In production, call init_logging() for 4-6x faster logging:

init_logging(LogConfig::default(), StdoutDrain::resolved());
app_log!(Info, "order.created", { order_id: 123u64 });
// → 2026-03-28 INFO [App] svc=my-service | order.created {"order_id":123}

Important: vil_log stores binary payloads. Back up .vil_log_dict.json and crates/vil_log/src/types/*.rs with each release to ensure old logs remain readable. See USAGE.md for details.

7 Semantic Log Types

Macro Category Layout Use Case
app_log! App MsgPack KV Business logic events
access_log! Access Flat struct HTTP request/response
ai_log! AI Flat struct LLM/RAG/Agent operations
db_log! DB Flat struct Database queries
mq_log! MQ Flat struct Message queue pub/sub
system_log! System Flat struct Runtime internals (SHM, queue, process)
security_log! Security Flat struct Auth, rate limit, injection

Flat struct macros are fastest (~130ns) — pure memcpy, no serialization. app_log! is slower (~350ns) due to MsgPack encoding of dynamic KV pairs.

Drain Backends

Built-in (always available)

Drain Use Case
StdoutDrain Dev mode — pretty/compact/json format
FileDrain Rolling file — daily/hourly/size rotation, retention
NullDrain Benchmarks — discard everything
MultiDrain Fan-out to N drains simultaneously

Feature-gated

Drain Feature Flag Use Case
ClickHouseDrain clickhouse-drain Batch INSERT for analytics
NatsDrain nats-drain Cross-host log aggregation
# Enable ClickHouse drain
vil_log = { workspace = true, features = ["clickhouse-drain"] }

# Enable NATS drain
vil_log = { workspace = true, features = ["nats-drain"] }

# Enable all
vil_log = { workspace = true, features = ["all-drains"] }

Multi-Drain Example

use vil_log::drain::*;

let drain = MultiDrain::new()
    .add(StdoutDrain::compact())
    .add(FileDrain::new(FileConfig {
        dir: "/var/log/vil".into(),
        rotation: RotationStrategy::Daily,
        max_files: 30,
        ..Default::default()
    }));

init_logging(config, drain);

Level Filtering

Events below the configured level are filtered before touching the ring — cost is a single atomic load (~1ns).

let config = LogConfig {
    level: LogLevel::Info,  // Debug and Trace are discarded
    ..Default::default()
};

tracing Bridge

VIL Log can capture events from the Rust tracing ecosystem:

use vil_log::runtime::init_logging_with_tracing;

// This installs VilTracingLayer as the global tracing subscriber.
// All tracing::info!(), tracing::warn!(), etc. flow into the VIL ring.
init_logging_with_tracing(config, StdoutDrain::pretty());

Dictionary System

Hot-path logs use u32 hashes instead of strings. Register strings once:

use vil_log::dict::register_str;

let hash = register_str("/api/orders");  // returns u32
// Use hash in AccessPayload.path_hash, etc.

// Reverse lookup (cold path, for drain formatting)
let original = vil_log::dict::lookup(hash);

Examples

cargo run -p example-501-villog-stdout-dev         # Stdout pretty output
cargo run -p example-502-villog-file-rolling        # Rolling file drain
cargo run -p example-503-villog-multi-drain         # Multi-drain fan-out
cargo run -p example-504-villog-benchmark-comparison --release  # Benchmark
cargo run -p example-505-villog-tracing-bridge      # tracing ecosystem bridge
cargo run -p example-506-villog-structured-events   # All 7 log types

License

Apache-2.0

Dependencies

~11–32MB
~370K SLoC