Skip to content

Architecture

LogXide delivers high performance through its native Rust implementation, providing Python applications with fast logging while maintaining a familiar API.

Core Architecture

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   Python API    │    │   Rust Core      │    │   I/O Output    │
│                 │    │                  │    │                 │
│ ┌─────────────┐ │    │ ┌──────────────┐ │    │ ┌─────────────┐ │
│ │ PyLogger    │ │───▶│ │ LogRecord    │ │───▶│ │ Files       │ │
│ │ Methods     │ │    │ │ Creation     │ │    │ │ Streams     │ │
│ └─────────────┘ │    │ └──────────────┘ │    │ │ HTTP/OTLP   │ │
│                 │    │                  │    │ └─────────────┘ │
│ ┌─────────────┐ │    │ ┌──────────────┐ │    │                 │
│ │ basicConfig │ │───▶│ │ Direct       │ │    │                 │
│ │ flush()     │ │    │ │ Handler Call │ │    │                 │
│ └─────────────┘ │    │ └──────────────┘ │    │                 │
└─────────────────┘    └──────────────────┘    └─────────────────┘

Message Processing Flow

  1. Python Call → LogXide PyLogger methods via PyO3
  2. Record Creation → Rust LogRecord with full metadata (logger name, level, timestamp, thread info)
  3. Handler Dispatch → Each handler's emit() is called (non-blocking for stream/HTTP/OTLP, synchronous for file handlers)
  4. Output → Formatted messages written to files/streams/HTTP endpoints

Key Components

PyO3 Integration (src/lib.rs, src/py_logger.rs, src/py_handlers.rs)

  • Python bindings exposing Logger, Handler, and Formatter types
  • addHandler() accepts LogXide's Rust native handlers (fast path) and standard Python logging.Handler subclasses (run once on the Python side, without the fast-path GIL release). As of 0.2.0, Rust-backed handlers are dispatched exactly once and never leak to unrelated loggers.

Core Types (src/core.rs)

  • LogRecord — Rust struct holding log metadata (name, level, message, timestamp, thread info, extras)
  • Logger — Core logger with level filtering and handler dispatch
  • LoggerManager — Hierarchical logger registry with parent-child relationships

Fast Logger (src/fast_logger.rs)

  • Lock-free implementation using atomic operations
  • Optimized for high-performance scenarios where mutex contention is a concern
  • Uses AtomicU8 for fast level checking

FastLoggerWrapper (logxide/fast_logger_wrapper.py)

Python-side optimization wrapper that intercepts logging calls before they cross the PyO3 boundary:

Python call → FastLoggerWrapper.info() → level check (Python) → [skip if disabled]
                                                               → [delegate to Rust if enabled]
  • 2-5x speedup for disabled log calls by avoiding:
    • PyObject creation for messages
    • PyTuple/PyDict packaging for args/kwargs
    • PyO3 boundary crossing overhead
  • Caches getEffectiveLevel() on the Python side and invalidates on setLevel()/addHandler()/removeHandler()
  • Transparent delegation: all non-hot-path attributes fall through to the underlying Rust PyLogger via __getattr__

Handlers (src/handler.rs)

All handlers implement the synchronous Handler trait:

pub trait Handler: Send + Sync {
    fn emit(&self, record: &LogRecord);
    fn flush(&self);
}
Handler Description I/O Strategy
StreamHandler stdout/stderr output crossbeam 채널 + 백그라운드 스레드 (논블로킹)
FileHandler File output 동기 직접 write (Mutex<BufWriter>)
RotatingFileHandler Auto-rotating files 동기 직접 write + size-based rotation
HTTPHandler HTTP log shipping crossbeam 채널 + 백그라운드 스레드 (배치)
OTLPHandler OpenTelemetry OTLP crossbeam 채널 + 백그라운드 스레드 (Protobuf)
MemoryHandler In-memory capture 동기 Vec::push (Mutex)
NullHandler Discards all logs Zero overhead

Non-blocking Handlers (Stream/HTTP/OTLP)

StreamHandler, HTTPHandler, OTLPHandler use the channel + background thread pattern:

Logger → emit() → crossbeam-channel sender → Background thread → I/O output
  • emit() formats the message and sends it to a bounded crossbeam-channel (non-blocking)
  • A dedicated background thread performs actual I/O
  • HTTP/OTLP handlers additionally batch records before sending

Synchronous Handlers (File/RotatingFile)

FileHandler, RotatingFileHandler use direct synchronous writes:

Logger → emit() → Mutex<BufWriter<File>> → write + conditional flush
  • emit() acquires a Mutex lock and writes directly to BufWriter
  • Level-based flush: records at ERROR or above trigger immediate flush()
  • Simpler and faster for single-thread-dominant workloads

Formatters (src/formatter.rs)

  • PercentStyle%(name)s format (default)
  • StrFormatStyle{name} format
  • StringTemplateStyle$name / ${name} format
  • Full support for padding, alignment, and date formatting
  • Direct ANSI color support: Support for terminal coloring placeholders (%(ansi_level_color)s, %(ansi_reset_color)s) is natively absorbed into PercentStyle (RustFormatter/Formatter), eliminating the runtime overhead of delegating to a separate ColorFormatter wrapper.

Filters (src/filter.rs)

  • Name-based filtering matching Python's logging.Filter
  • Hierarchical name matching (e.g., "myapp" matches "myapp.database")

String Cache (src/string_cache.rs)

  • Arc<str>-based interning specifically for static logger names and level names.
  • No message-text caching: To avoid memory overhead and allocation degradation, the dynamic log message body (text) is not cached. Dead string caching states and redundant message-text cache checks have been completely removed.
  • Reduces allocation overhead for frequently used static strings.

Handler Architecture

┌─────────────────────┐
│   PyLogger          │
│   (per-logger       │
│    handler list)    │
├─────────────────────┤
│ ┌─────────────────┐ │
│ │ FileHandler     │ │─── Mutex<BufWriter> → 동기 직접 write
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ StreamHandler   │ │─── crossbeam-channel → Background thread → stderr/stdout
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ HTTPHandler     │ │─── crossbeam-channel → Background thread → HTTP batch
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ OTLPHandler     │ │─── crossbeam-channel → Background thread → OTLP Protobuf
│ └─────────────────┘ │
└─────────────────────┘

Each logger maintains its own handler list. When logger.addHandler() is called with a Rust handler, it is stored in the logger's local handler list. Global handlers configured via basicConfig() are also supported.

Handler routing and formatting (0.2.0)

Handlers route by backend kind, and each is dispatched exactly once:

  • Structured sinks (HTTPHandler, OTLPHandler) serialize the record in Rust (JSON / protobuf), so extra fields are preserved.
  • Text-sink wrappers (FileHandler, StreamHandler, RotatingFileHandler, MemoryHandler) format the line via their Python emit() override, which is what makes formatted output and pytest capture behave correctly.
  • Foreign Python handlers run once on the Python side.

A Rust-backed handler attached to one logger no longer double-emits or leaks records to unrelated loggers, and removeHandler() / clear_handlers() / close() tear down routing and the background worker together.

GIL Scope

LogXide releases the GIL for the Rust dispatch only on the fast path: a record that hits no Python filter, no Python handler, and no caller-info field. On that path, field extraction happens under the GIL and the subsequent Rust dispatch (formatting + I/O for Rust-native handlers) runs with the GIL released. A %-args call re-acquires the GIL inside emit() to run % formatting, and any Python handler/filter or caller-info collection also holds the GIL. On current CPython GIL builds, producer throughput does not scale linearly across threads because the fast path shares a handler mutex and sink I/O is serialized; free-threaded builds need separate verification.

Thread Safety

  • Mutex-protected handlersparking_lot::Mutex for handler state
  • Thread-safe logger registryDashMap for concurrent logger access
  • Atomic level checksAtomicU8 for fast level filtering without locks
  • Background-thread I/O — stream/HTTP/OTLP writes run on a worker thread that does not hold Python's GIL

Memory Management

  • Rust ownership prevents memory leaks
  • Arc-based sharing for handlers and formatters across loggers
  • BufWriter with 64KB buffers reduces syscall overhead for file handlers
  • String interning via Arc<str> for repeated logger/level names

Comparison with Standard Logging

Aspect Python logging LogXide
Implementation Pure Python Native Rust via PyO3
Handler calls Python method dispatch Direct Rust function calls
String formatting Python string operations Rust native formatting
Thread safety Global lock (_lock) Per-handler mutexes
I/O Python file objects Direct OS I/O (bypasses GIL)
Custom handlers Unlimited (Python subclasses) Rust native handlers (fast path); Python subclasses also accepted, run once on the Python side
subclassing Full support Not supported

Dependencies

Crate Purpose
pyo3 Python-Rust bindings
chrono Timestamp formatting
parking_lot Fast mutexes
dashmap Concurrent logger map
crossbeam-channel Stream/HTTP/OTLP handler channels
ureq HTTP requests (HTTPHandler)
serde / serde_json JSON serialization
prost / opentelemetry-proto OTLP Protobuf encoding

Format string parsing

The active formatter hot path uses a single-pass O(N) parser; it does not invoke the regex crate for %(field)s field extraction. The regex crate may still appear in Cargo.toml for non-hot-path uses, but it is not part of the per-record formatting path.