eventline 0.8.0

Structured journaling and scoped logging for Rust applications.
Documentation

eventline

Structured journaling + logging for Rust, with scopes you can replay.

GitHub last commit License Rust Status

eventline records a complete, append-only execution history (events + scopes), while letting you control what gets emitted to console/files via log level and sink toggles.


Features

  • Append-only journal of structured records (events + scope exits)
  • RAII scopes with duration tracking and success/failure/aborted outcomes
  • Per-outcome exit messages for clean done: lines
  • Emission gating (log level) that does NOT affect recording
  • Console and file sinks (toggleable, configurable formatting)
  • Automatic log rotation with configurable size limits and backup retention
  • Run headers written as raw bytes at the start of each log session

What is eventline?

eventline is a small, predictable journaling/logging crate built for real systems software.

Core rule:

Always record the full structured history.
Only gate emission (console/file output) with log levels and sink toggles.

This makes eventline ideal for post-mortems, debugging, and deterministic "what happened?" replay.


Quick Start

Add it to your project:

[dependencies]
eventline = "0.8.0"

Initialize once early:

eventline::runtime::init().await;

Log events:

eventline::info!("starting");
eventline::debug!("debug details: {}", 123);
eventline::warn!("something looks off");
eventline::error!("something failed: {}", "oops");

Create a scope:

eventline::scope!("config", {
    eventline::info!("loading config");
    // work...
});

Scopes with exit messages:

eventline::scope!(
    "config",
    success="loaded",
    failure="failed",
    aborted="aborted",
{
    // work...
});

This produces a final line like:

done: config#12 loaded [success] (3.2ms)

(Exact formatting depends on runtime settings.)


Recording vs Emission (important)

  • Recording always happens — the journal stores the full structured history.
  • Emission is controlled by global log level and enabled sinks.

You get full fidelity history without spamming stdout.


Runtime Configuration

Console output:

eventline::runtime::enable_console_output(true);
eventline::runtime::enable_console_color(true);
eventline::runtime::enable_console_timestamp(false);
eventline::runtime::enable_console_duration(true);

File output (append, no rotation):

eventline::runtime::enable_file_output("/tmp/app.log")?;

File output with automatic rotation:

use eventline::runtime::{LogPolicy, RunHeader};

eventline::runtime::enable_file_output_rotating(
    "logs/app.log",
    LogPolicy::default(),              // 5 MiB max, 5 backups
    Some(RunHeader::new("my-app")),    // with PID annotation
)?;

Rotation renames the active log to app.log.1, shifts older backups up, and opens a fresh app.log. The oldest backup beyond keep_backups is silently dropped.

Disable all output (still records):

eventline::runtime::disable_all_output();

Set global log level (emission threshold only):

eventline::runtime::set_log_level(eventline::runtime::LogLevel::Debug);

Log Rotation

LogPolicy controls when and how rotation happens:

use eventline::runtime::LogPolicy;

// Custom: 10 MiB max, keep 3 backups
let policy = LogPolicy::new(10 * 1024 * 1024, 3);

// Or use the defaults (5 MiB, 5 backups)
let policy = LogPolicy::default();

Rotation is triggered automatically before a record is written that would push the file past max_bytes. Set keep_backups to 0 to simply delete the log on rotation rather than keeping any history.


Run Headers

A RunHeader is a single decorated line written as raw bytes at the start of a log session, before structured records begin. It is always visible in the file regardless of log level.

use eventline::runtime::RunHeader;

// With PID:
// ==================== my-daemon (pid=18432) ====================
RunHeader::new("my-daemon")

// Without PID:
// ====================== my-daemon ======================
RunHeader::without_pid("my-daemon")

// Custom width:
RunHeader::new("my-daemon").with_width(80)

When appending to an existing non-empty log file, a blank separator line is inserted automatically before the header so run boundaries are clearly visible.


Design Notes

  • Records are append-only and never rewritten
  • Scopes are created on enter and finalized exactly once on exit
  • The journal is the canonical execution history
  • Rendering and runtime only affect presentation, not data
  • Rotation and headers operate on raw bytes and bypass the record pipeline intentionally

Module Layout

  • core/ — data types only (Record, Scope, ids, Outcome, guards)
  • journal/ — append-only buffer, writers, rotation logic, structured fields
  • render/ — canonical formatting for console and file output
  • runtime/ — global config, sinks, filtering policy, run headers
  • macrosinfo!, debug!, warn!, error!, scope!

License

MIT