eventline 0.1.0

A structured event journaling library with scoped timelines, outcomes, and serialization support.
Documentation

Eventline

Eventline is a human-friendly, append-only logging and tracing system written in Rust.
It is designed for systems-level programs, daemons, and eventually Linux distributions, offering deterministic replay, easy inspection, and logs that make sense to both developers and regular users.

Eventline is ideal when you want logs that explain what happened, why it happened, and how long it took — without parsing walls of text.


Features

  • Append-only journal for scopes and events
  • Human-readable rendering with Unicode bullets ()
  • Optional color output for improved readability (success/green, failure/red, aborted/yellow)
  • Summaries of scopes, outcomes, and durations
  • Enhanced filtering – selectively render scopes and events based on outcome, depth, duration, event kind, message content, and more
  • RAII-based scope management (ScopeGuard)
  • Works for daemons, interactive apps, or CLI tools
  • Temporarily store records in memory and flush in batches
  • Simultaneous logging to terminal and file for live monitoring and persistent storage
  • Designed to respark joy in using computers

Roadmap

  • Custom formatters – allow users to define how events are serialized for files or terminal output
  • Structured data – support for key-value pairs and structured event metadata
  • Query interface – zero-copy, in-memory querying of journal history for analysis and debugging
  • Tag-based filtering – add tags to scopes/events for more flexible filtering

Project Status

Eventline is actively developed and usable today.

  • The core journal model and append-only guarantees are stable
  • Renderer output and formatting APIs may evolve before 1.0
  • Feedback and contributions welcome

Quick Example

use eventline::{Journal, Outcome};

fn main() {
    let mut journal = Journal::new();
    
    // Scoped block with automatic success/failure tracking
    journal.scoped(None, Some("Startup"), |journal, scope| {
        journal.record(Some(scope), "Application starting");
        journal.record(Some(scope), "Loading configuration");
        journal.record(Some(scope), "Initializing modules");
    });
    
    // Manual scope management for more control
    let task_scope = journal.enter_scope(None, Some("BackgroundTask"));
    journal.record(Some(task_scope), "Performing background task");
    journal.record(Some(task_scope), "Task completed successfully");
    journal.exit_scope(task_scope, Outcome::Success);
    
    // Write to file
    journal.write_to_file("application.log").unwrap();
}

Using Buffered Logging

For high-throughput scenarios, use JournalBuffer to batch writes:

use eventline::{Journal, Outcome};

fn main() {
    let mut journal = Journal::new();
    
    // Create a buffer for batched logging
    let mut buffer = journal.create_buffer();
    
    let scope = buffer.enter_scope(None, Some("Processing"));
    buffer.record(Some(scope), "Processing item 1");
    buffer.record(Some(scope), "Processing item 2");
    buffer.exit_scope(scope, Outcome::Success);
    
    // Flush buffer to journal (IDs are rebased atomically)
    journal.flush_buffer(buffer);
    
    journal.write_to_file("batch.log").unwrap();
}

Custom Output with JournalWriter

use eventline::{Journal, JournalWriter};
use std::io;

fn main() {
    let mut journal = Journal::new();
    
    journal.scoped(None, Some("Task"), |journal, scope| {
        journal.record(Some(scope), "Doing work...");
    });
    
    // Write to multiple destinations simultaneously
    let mut file = std::fs::File::create("output.log").unwrap();
    
    JournalWriter::new()
        .with_bullet("")
        .write_to_all(
            &mut [
                &mut io::stdout() as &mut dyn io::Write,
                &mut file as &mut dyn io::Write
            ],
            &journal
        )
        .unwrap();
}

Rendering with Color

use eventline::{Journal, render_journal_tree, render_summary};

fn main() {
    let mut journal = Journal::new();
    
    journal.scoped(None, Some("Task"), |journal, scope| {
        journal.record(Some(scope), "Processing data...");
    });
    
    // Render with colors enabled
    render_journal_tree(&journal, true, None);
    
    // Or render a summary with colors
    render_summary(&journal, true, None);
    
    // Pass false to disable colors for file output or non-color terminals
    render_journal_tree(&journal, false, None);
}

Filtering Logs

Filter scopes and events based on various criteria:

use eventline::{Journal, Filter, ScopeFilter, EventFilter, Outcome, EventKind};
use eventline::render_journal_tree;

fn main() {
    let mut journal = Journal::new();
    
    // Create some scopes with different outcomes
    let scope1 = journal.enter_scope(None, Some("database-query"));
    journal.record(Some(scope1), "Querying users table");
    journal.warn(Some(scope1), "Slow query detected");
    journal.exit_scope(scope1, Outcome::Success);
    
    let scope2 = journal.enter_scope(None, Some("api-request"));
    journal.error(Some(scope2), "Connection timeout");
    journal.exit_scope(scope2, Outcome::Failure);
    
    // Filter 1: Show only failed scopes
    let filter = Filter::scope(ScopeFilter::Outcome(Outcome::Failure));
    render_journal_tree(&journal, true, Some(&filter));
    
    // Filter 2: Show only warnings and errors
    let filter = Filter::event(
        EventFilter::Kind(EventKind::Warning)
            .or(EventFilter::Kind(EventKind::Error))
    );
    render_journal_tree(&journal, true, Some(&filter));
    
    // Filter 3: Complex filter - failed scopes with errors
    let filter = Filter::new(
        ScopeFilter::Outcome(Outcome::Failure),
        EventFilter::Kind(EventKind::Error)
    );
    render_journal_tree(&journal, true, Some(&filter));
}

See FILTERING_GUIDE.md for comprehensive filtering examples and use cases.


Philosophy

Eventline is not just another logging library. It is meant to:

  • Be intuitive for humans reading logs
  • Provide full traceability of application behavior
  • Enable replay and inspection without mutation
  • Serve as a foundation for logging across multiple apps in a Linux distro
  • Make debugging and monitoring a pleasant experience

Design Principles

Append-Only Invariant

Once written, journal entries are never modified or removed. This guarantees:

  • Deterministic replay of program execution
  • Safe concurrent reads
  • Reliable audit trails

Separation of Concerns

  • Journal: Pure data structure for scopes and events
  • JournalWriter: Rendering policy (output format, destinations)
  • JournalBuffer: Batching mechanism for high-throughput scenarios
  • Filter: Criteria-based selection of scopes and events for rendering

This separation allows the same journal to be:

  • Rendered to terminal with colors
  • Written to files in plain text
  • Filtered for specific outcomes, event kinds, or other criteria
  • Serialized to JSON/binary formats
  • Streamed to remote logging systems

Outcomes vs Events

Event kinds (Info, Warning, Error) describe what happened, while scope outcomes (Success, Failure, Aborted) describe the result. This distinction allows:

  • Warnings during successful operations
  • Successful completion despite errors encountered
  • Clear separation between diagnostics and results

Filtering Philosophy

Filtering is applied at render time, not at journal construction. This means:

  • Zero overhead when not filtering
  • The complete journal is always preserved
  • Different views can be created from the same data
  • Filters are composable using logical operators (AND, OR, NOT)

Non-Goals

Eventline is not:

  • A metrics system (use Prometheus, etc.)
  • A distributed tracing backend
  • A replacement for structured logging frameworks (yet)

It focuses on local, human-readable execution traces.


Installation

Add this to your Cargo.toml:

[dependencies]
eventline = "0.1"

License

MIT