Skip to content

kamlesh-ks/embedded-logger

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

embedded-logger

Production-grade, low-latency structured logging library for automotive / embedded / robotics software on POSIX platforms (Linux, QNX).


Features

Feature Description
Lock-free async pipeline Log calls return in < 200 ns via MPSC ring buffer; drain thread writes to sinks
AUTOSAR DLT compatible ECU ID, App ID, Context ID per message; optional DLT binary format
Multiple sinks Console (stdout/stderr/split), rotating file, POSIX syslog, custom
Per-module filtering Global + per-module level overrides, runtime-changeable
Zero heap alloc (hot path) 4 KiB stack-allocated message buffer; no Box/String in log path
Monotonic timestamps Nanosecond precision via CLOCK_MONOTONIC_RAW (Linux) / CLOCK_MONOTONIC (QNX)
Overflow policies Drop newest, drop oldest, or block — configurable per deployment
Graceful shutdown Drain thread flushes all pending records before process exit
File rotation Size-based rotation with configurable backup count
Optional JSON output NDJSON format for log aggregation pipelines

Architecture

 Thread 1 ──┐
 Thread 2 ──┤── push(LogRecord) ──► [ Lock-free Ring Buffer ] ──► drain thread ──► Sink 1 (Console)
 Thread 3 ──┘       (~150 ns)           (8192 slots default)                   ├──► Sink 2 (Rotating File)
                                                                               └──► Sink N (Syslog)

Quick Start

Add to your Cargo.toml:

[dependencies]
embedded-logger = { path = "../embedded-logger" }

Basic Usage

use embedded_logger::prelude::*;
use embedded_logger::sink::console::{ConsoleSink, ConsoleStream};
use embedded_logger::logger::{LoggerBuilder, init_global};

fn main() {
    // 1. Build the logger
    let logger = LoggerBuilder::new()
        .sink(Box::new(ConsoleSink::new(ConsoleStream::Stderr)))
        .filter_spec("info,myapp::nav=debug")
        .build();

    // 2. Install as global
    init_global(logger).expect("logger already initialized");

    // 3. Define per-module context (DLT-style)
    const CTX: LogContext = LogContext::new("ECU1", "NAVI", "ROUT");

    // 4. Log!
    elog_info!(CTX, "Navigation module initialized");
    elog_debug!(CTX, "Using map version {}", "2025.Q4");
    elog_warn!(CTX, "GPS signal degraded: accuracy={}m", 15);
    elog_error!(CTX, "Route calculation failed: {}", "no path found");
}

With File Rotation

use embedded_logger::sink::file::{FileSink, FileSinkConfig};
use std::path::PathBuf;

let file_config = FileSinkConfig {
    path: PathBuf::from("/var/log/myapp/application.log"),
    max_file_size: 50 * 1024 * 1024,  // 50 MiB
    max_backups: 10,
    buffer_size: 64 * 1024,
    fsync_interval: 100,  // fsync every 100 records
};

let file_sink = FileSink::new(file_config).expect("failed to open log file");

let logger = LoggerBuilder::new()
    .sink(Box::new(ConsoleSink::new(ConsoleStream::Split)))
    .sink(Box::new(file_sink))
    .filter_spec("info")
    .build();

With Syslog

use embedded_logger::sink::syslog::{SyslogSink, SyslogFacility};

let syslog = SyslogSink::new("myapp", SyslogFacility::Daemon)
    .expect("failed to open syslog");

let logger = LoggerBuilder::new()
    .sink(Box::new(syslog))
    .level(LogLevel::Warn)
    .build();

Structured Key-Value Logging

use embedded_logger::prelude::*;

const CTX: LogContext = LogContext::app("SENS", "LIDR");

fn process_lidar_scan() {
    elog_kv!(LogLevel::Info, CTX, "scan complete";
        "points" => 64000,
        "range_m" => 120.5,
        "fps" => 20,
    );
    // Output: ... scan complete points=64000 range_m=120.5 fps=20
}

Runtime Level Changes

use embedded_logger::logger::global;

// Change global level at runtime (e.g., from a diagnostic command)
global().filter().set_default_level(LogLevel::Debug);

// Enable verbose logging for a specific module
global().filter().set_module_level("myapp::perception", LogLevel::Verbose);

Context Identifiers (DLT)

Following the AUTOSAR DLT standard, each log message carries three 4-character identifiers:

Field Description Example
ecu_id Electronic Control Unit "HPC1"
app_id Application / Process "NAVI"
ctx_id Context / Module "ROUT"
// Full context
const CTX: LogContext = LogContext::new("HPC1", "NAVI", "ROUT");

// Minimal (no ECU, auto-detected)
const CTX2: LogContext = LogContext::app("SENS", "CAM0");

Feature Flags

Flag Default Description
std yes Standard library support
console-sink yes Console (stdout/stderr) sink
file-sink yes File sink with rotation
syslog-sink yes POSIX syslog sink
async-logging yes Background drain thread (disable for synchronous mode)
dlt-format no AUTOSAR DLT binary formatter
json-format no NDJSON structured output
no-color no Disable ANSI color codes

Overflow Policies

Policy Behavior Use When
DropNewest Discard the message being pushed Default; zero blocking, no data corruption
DropOldest Overwrite oldest unread message Prefer latest data (sensor streams)
Block Spin-wait for space Message loss is unacceptable (safety logs)

Performance

Measured on ARM Cortex-A76 (automotive SoC), release build:

Operation Latency
elog_info!() (filtered out) ~5 ns
elog_info!() (push to ring) ~150 ns
Full pipeline (push + drain + file write) ~2 μs

Platform Support

Platform Status
Linux (x86_64, aarch64) Fully supported
QNX 7.1+ (aarch64) Fully supported (POSIX)
QNX 8.0 (aarch64) Fully supported (POSIX)

Graceful Shutdown

Always shut down the logger before process exit to avoid losing buffered messages:

use embedded_logger::logger::global;

// Option 1: Explicit flush (if you don't own the logger)
global().flush();

// Option 2: Full shutdown (if you own the Logger instance)
// logger.shutdown();

About

Production-grade, low-latency structured logging library

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages