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¶
- Python Call → LogXide PyLogger methods via PyO3
- Record Creation → Rust
LogRecordwith full metadata (logger name, level, timestamp, thread info) - Handler Dispatch → Each handler's
emit()is called (non-blocking for stream/HTTP/OTLP, synchronous for file handlers) - 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 Pythonlogging.Handlersubclasses (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 dispatchLoggerManager— 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
AtomicU8for 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:
PyObjectcreation for messagesPyTuple/PyDictpackaging for args/kwargs- PyO3 boundary crossing overhead
- Caches
getEffectiveLevel()on the Python side and invalidates onsetLevel()/addHandler()/removeHandler() - Transparent delegation: all non-hot-path attributes fall through to the underlying Rust
PyLoggervia__getattr__
Handlers (src/handler.rs)¶
All handlers implement the synchronous Handler trait:
| 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:
emit()formats the message and sends it to a boundedcrossbeam-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:
emit()acquires aMutexlock and writes directly toBufWriter- Level-based flush: records at
ERRORor above trigger immediateflush() - Simpler and faster for single-thread-dominant workloads
Formatters (src/formatter.rs)¶
PercentStyle—%(name)sformat (default)StrFormatStyle—{name}formatStringTemplateStyle—$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 intoPercentStyle(RustFormatter/Formatter), eliminating the runtime overhead of delegating to a separateColorFormatterwrapper.
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), soextrafields are preserved. - Text-sink wrappers (
FileHandler,StreamHandler,RotatingFileHandler,MemoryHandler) format the line via their Pythonemit()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 handlers —
parking_lot::Mutexfor handler state - Thread-safe logger registry —
DashMapfor concurrent logger access - Atomic level checks —
AtomicU8for 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 loggersBufWriterwith 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.