baobab/core/logger/README.md
2025-08-07 09:59:16 +02:00

8.0 KiB

Hero Logger

A hierarchical logging system for the Hero project that provides system-level and per-job logging with complete isolation using the tracing ecosystem.

Features

  • Hierarchical Organization: Physical separation of logs by component and job
  • System Logger: Global logging for all non-job-specific events
  • Per-Job Logger: Isolated logging for individual job execution
  • Custom Log Format: Readable format with precise formatting rules
  • Hourly Rotation: Automatic log file rotation every hour
  • Rhai Integration: Capture Rhai script print() and debug() calls
  • High Performance: Async logging with efficient filtering
  • Structured Logging: Rich context and metadata support

Custom Log Format

Hero Logger uses a custom format designed for readability and consistency:

21:23:42
 system     - This is a normal log message
 system     - This is a multi-line message
              second line with proper indentation
              third line maintaining alignment
E error_cat  - This is an error message
E             second line of error
E             third line of error

Format Rules

  • Time stamps (HH:MM:SS) are written once per second when the log time changes
  • Categories are:
    • Limited to 10 characters maximum
    • Padded with spaces to exactly 10 characters
    • Any - in category names are converted to _
  • Each line starts with either:
    • (space) for normal logs (INFO, WARN, DEBUG, TRACE)
    • E for error logs
  • Multi-line messages maintain consistent indentation (14 spaces after the prefix)

Architecture

The logging system uses a hybrid approach with two main components:

System Logger (Global)

  • Long-lived logger initialized at application startup
  • Routes logs to different files based on tracing targets
  • Supports multiple components simultaneously

Per-Job Logger (Dynamic)

  • Created on-demand for each job execution
  • Provides complete isolation for job-specific logs
  • Automatically disposed after job completion

Directory Structure

logs/
├── supervisor/               # System logs for supervisor
│   └── 2025-08-06-11.log
└── actor/
    ├── osis/
    │   ├── 2025-08-06-11.log   # General OSIS actor logs
    │   ├── job-a1b2c3d4/       # Job-specific logs
    │   │   └── 2025-08-06-11.log
    │   └── job-9a8b7c6d/
    │       └── 2025-08-06-12.log
    └── sal/
        ├── 2025-08-06-13.log   # General SAL actor logs
        └── job-f1e2d3c4/
            └── 2025-08-06-13.log

Quick Start

1. Initialize System Logger

use hero_logger;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Define your system components
    let components = vec![
        "supervisor".to_string(),
        "osis_actor".to_string(),
        "sal_actor".to_string(),
    ];
    
    // Initialize the system logger
    let _guards = hero_logger::init_system_logger("logs", &components)?;
    
    // Now you can use tracing macros with targets
    tracing::info!(target: "supervisor", "System started");
    tracing::info!(target: "osis_actor", "Actor ready");
    
    Ok(())
}

2. Per-Job Logging

use hero_logger::create_job_logger;
use tracing::subscriber::with_default;

async fn process_job(job_id: &str, actor_type: &str) {
    // Create job-specific logger
    let job_logger = create_job_logger("logs", actor_type, job_id)?;
    
    // Execute job within logging context
    with_default(job_logger, || {
        tracing::info!(target: "osis_actor", "Job {} started", job_id);
        
        // All tracing calls here go to the job-specific log
        tracing::debug!(target: "osis_actor", "Processing data...");
        tracing::info!(target: "osis_actor", "Job {} completed", job_id);
    });
}

3. Rhai Script Integration

use hero_logger::rhai_integration::configure_rhai_logging;
use rhai::Engine;

fn setup_rhai_engine() -> Engine {
    let mut engine = Engine::new();
    
    // Configure Rhai to capture print/debug calls
    configure_rhai_logging(&mut engine, "osis_actor");
    
    engine
}

// Now Rhai scripts can use print() and debug()
let script = r#"
    print("Hello from Rhai!");
    debug("Debug information");
    42
"#;

let result = engine.eval::<i64>(script)?;

API Reference

Core Functions

init_system_logger(logs_root, components)

Initialize the global system logger with component-based filtering.

Parameters:

  • logs_root: Root directory for log files
  • components: List of component names for dedicated logging

Returns: Vector of WorkerGuards that must be kept alive

create_job_logger(logs_root, actor_type, job_id)

Create a per-job logger for isolated logging.

Parameters:

  • logs_root: Root directory for log files
  • actor_type: Type of actor (e.g., "osis", "sal")
  • job_id: Unique job identifier

Returns: Boxed subscriber for use with with_default()

Rhai Integration

configure_rhai_logging(engine, target)

Configure a Rhai engine to capture print/debug output.

add_custom_logging_functions(engine, target)

Add custom logging functions (log_info, log_debug, etc.) to Rhai.

create_logging_enabled_engine(target, include_custom)

Create a new Rhai engine with full logging integration.

Utilities

ensure_log_directories(logs_root, components)

Ensure the log directory structure exists.

extract_actor_type(component)

Extract actor type from component name.

cleanup_old_logs(directory, pattern, max_age_days)

Clean up old log files based on age.

Configuration

Log Levels

The system supports standard tracing log levels:

  • ERROR: Critical errors
  • WARN: Warning messages
  • INFO: Informational messages
  • DEBUG: Debug information
  • TRACE: Detailed trace information

Environment Variables

  • RUST_LOG: Set log level filtering (e.g., RUST_LOG=debug)

File Rotation

  • Hourly: Default rotation every hour
  • Daily: Optional daily rotation
  • Never: Single file (no rotation)

Examples

Basic Usage

cargo run --example logging_demo

Custom Format Demo

cargo run --example custom_format_demo

Integration with Actor System

// In your actor implementation
async fn process_job(&self, job: &Job) {
    let job_logger = hero_logger::create_job_logger(
        "logs", 
        &self.actor_type, 
        &job.id
    ).unwrap();

    let job_task = async move {
        tracing::info!(target: &self.actor_type, "Job processing started");
        
        // Configure Rhai engine for this job
        let mut engine = Engine::new();
        hero_logger::rhai_integration::configure_rhai_logging(
            &mut engine, 
            &self.actor_type
        );
        
        // Execute Rhai script - print/debug calls captured
        let result = engine.eval::<String>(&job.script)?;
        
        tracing::info!(target: &self.actor_type, "Job finished: {}", result);
        Ok(result)
    };

    // Execute with job-specific logging
    tracing::subscriber::with_default(job_logger, job_task).await;
}

Performance Considerations

  • Async Logging: All file I/O is asynchronous
  • Efficient Filtering: Target-based filtering minimizes overhead
  • Memory Usage: Per-job loggers are short-lived and automatically cleaned up
  • File Handles: Automatic rotation prevents excessive file handle usage

Troubleshooting

Common Issues

  1. Logs not appearing: Ensure WorkerGuards are kept alive
  2. Permission errors: Check write permissions on log directory
  3. Missing directories: Use ensure_log_directories() before logging
  4. Rhai output not captured: Verify configure_rhai_logging() is called

Debug Mode

Enable debug logging to see internal logger operations:

RUST_LOG=hero_logger=debug cargo run

Testing

Run the test suite:

cargo test

Run the demo example:

cargo run --example logging_demo

License

This project is part of the Hero ecosystem and follows the same licensing terms.