Logger

Trait Logger 

Source
pub trait Logger:
    Debug
    + Send
    + Sync {
    // Required methods
    fn finish_log_record(&self, record: LogRecord);
    fn finish_log_record_async<'s>(
        &'s self,
        record: LogRecord,
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 's>>;
    fn prepare_to_die(&self);
}
Expand description

Core trait for implementing logging backends in logwise.

This trait defines the interface that all loggers must implement to receive and process log records from the logwise logging system. Implementations can output logs to any destination: stderr, files, network services, or in-memory buffers.

§Requirements

All implementations must be:

  • Thread-safe: The logger will be called from multiple threads simultaneously
  • Send + Sync: Required for use in multi-threaded environments
  • Debug: For diagnostic purposes and error reporting

§Example Implementation

use logwise::{Logger, LogRecord};
use std::sync::atomic::{AtomicUsize, Ordering};

#[derive(Debug)]
struct CountingLogger {
    count: AtomicUsize,
}

impl CountingLogger {
    fn new() -> Self {
        Self {
            count: AtomicUsize::new(0),
        }
    }

    fn get_count(&self) -> usize {
        self.count.load(Ordering::Relaxed)
    }
}

impl Logger for CountingLogger {
    fn finish_log_record(&self, _record: LogRecord) {
        self.count.fetch_add(1, Ordering::Relaxed);
        // In a real implementation, you would process the record here
    }

    fn finish_log_record_async<'s>(
        &'s self,
        record: LogRecord,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 's>> {
        Box::pin(async move {
            self.finish_log_record(record);
        })
    }

    fn prepare_to_die(&self) {
        // No cleanup needed for this simple logger
    }
}

// Usage
let logger = CountingLogger::new();
let mut record = LogRecord::new(logwise::Level::Info);
record.log("Test message");
logger.finish_log_record(record);
assert_eq!(logger.get_count(), 1);

Required Methods§

Source

fn finish_log_record(&self, record: LogRecord)

Processes a log record synchronously.

This method receives a complete LogRecord and is responsible for outputting it to the logger’s destination. The implementation should be as fast as possible since it may be called frequently and from performance-critical code paths.

§Parameters
  • record - The log record containing the formatted message and metadata
§Implementation Notes
  • This method may be called from multiple threads simultaneously
  • The implementation should not panic; errors should be handled gracefully
  • Consider buffering writes for better performance
  • The record is passed by value to avoid lifetime issues
§Examples
use logwise::{Logger, LogRecord, Level};

impl Logger for ConsoleLogger {
    fn finish_log_record(&self, record: LogRecord) {
        // Simple console output
        println!("{}", record);
    }
}
Source

fn finish_log_record_async<'s>( &'s self, record: LogRecord, ) -> Pin<Box<dyn Future<Output = ()> + Send + 's>>

Processes a log record asynchronously.

This method provides an async interface for processing log records, allowing loggers to leverage existing async contexts and avoid blocking async executors. This is particularly useful for loggers that perform I/O operations or need to integrate with async-first systems.

§Parameters
  • record - The log record containing the formatted message and metadata
§Returns

A pinned future that completes when the log record has been processed. The future must be Send to work across thread boundaries.

§Implementation Options

Loggers have two common implementation strategies:

  1. Simple wrapper: For loggers that don’t benefit from async, wrap the sync method:

    fn finish_log_record_async<'s>(
        &'s self,
        record: LogRecord,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 's>> {
        Box::pin(async move {
            self.finish_log_record(record)
        })
    }
  2. True async: For loggers that benefit from async I/O:

    fn finish_log_record_async<'s>(
        &'s self,
        record: LogRecord,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 's>> {
        Box::pin(async move {
            // Perform actual async I/O here
            // e.g., async_write_to_file(record).await
            todo!("Implement async logging")
        })
    }
§Lifetime Notes

The lifetime parameter 's ties the returned future to the logger’s lifetime, ensuring the logger remains valid while the future is being polled.

Source

fn prepare_to_die(&self)

Prepares the logger for application shutdown.

This method is called when the application is about to exit, giving the logger a chance to flush any buffered data, close file handles, send final network packets, or perform other cleanup operations. This ensures no log data is lost during shutdown.

§When This Is Called
  • Before normal application exit
  • During panic handling (if possible)
  • When explicitly requested by the application
§Implementation Guidelines
  • Flush all buffered data to persistent storage
  • Close network connections gracefully
  • Release any system resources
  • This method may take longer than finish_log_record since it’s called rarely
  • Should not panic; handle errors gracefully
§Examples
use logwise::{Logger, LogRecord};
use std::sync::Mutex;

#[derive(Debug)]
struct BufferedLogger {
    buffer: Mutex<Vec<String>>,
}

impl BufferedLogger {
    fn new() -> Self {
        Self {
            buffer: Mutex::new(Vec::new()),
        }
    }
}

impl Logger for BufferedLogger {
    fn finish_log_record(&self, record: LogRecord) {
        let mut buffer = self.buffer.lock().unwrap();
        buffer.push(record.to_string());
    }

    fn finish_log_record_async<'s>(
        &'s self,
        record: LogRecord,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 's>> {
        Box::pin(async move {
            self.finish_log_record(record);
        })
    }

    fn prepare_to_die(&self) {
        // Flush buffer to stdout before exit
        let buffer = self.buffer.lock().unwrap();
        for message in buffer.iter() {
            println!("{}", message);
        }
    }
}

Implementors§

Source§

impl Logger for InMemoryLogger

Implementation of the Logger trait for InMemoryLogger.

This implementation captures all log records in memory by converting them to strings and storing them in the internal vector. Both synchronous and asynchronous logging are supported, with the async version being a simple wrapper around the synchronous implementation.