LogXide vs Structlog¶
This page provides a detailed deep-dive comparing LogXide to Structlog. For a high-level compatibility overview, see the Compatibility Overview.
Structlog focuses on structured/context-rich logging, while LogXide focuses on raw performance with stdlib compatibility. They serve different use cases but overlap in production Python logging.
Architecture¶
| Aspect | LogXide | Structlog |
|---|---|---|
| Implementation | Rust native core via PyO3 | Pure Python |
| GIL Strategy | Releases GIL for Rust dispatch on the fast path; %-args & Python handlers/filters hold it |
Holds GIL for entire pipeline |
| Core concept | stdlib logging compatible |
Processor pipeline |
| Log Record | Rust Arc<LogRecord> |
Python dict (event dict) |
| Thread Safety | Rust RwLock |
Thread-local context |
Performance¶
LogXide moves formatting and I/O into a Rust core and releases the GIL for the Rust dispatch on the fast path (no Python filter/handler/caller-info); %-args formatting and Python handlers/filters still hold the GIL. See Compatibility.
Cross-library numbers are corrected and sink-verified
An earlier revision withdrew the per-handler tables because the old benchmark/basic_handlers_benchmark.py was defective (closed output stream, async drops counted as delivered, a mislabeled "RotatingFileHandler", and stdlib plus LogXide in the same patched process, which also contaminated the stdlib-backed Structlog column). The harness has been rebuilt: each library runs in its own subprocess, the sink is verified after flush, and durable throughput is reported separately from producer latency. The corrected numbers below replace the withdrawn ones.
Durable throughput vs Structlog (corrected, sink-verified)¶
Measured with benchmark/basic_handlers_benchmark.py on macOS M4 Max, release build, -n 20000, subprocess-isolated per library, re-run this session on both CPython 3.12.11 and 3.14.2. Durable = records the sink confirmed after flush (every row verified at 20,200 / 20,200). Numbers are machine-specific and rounded:
| Sink | LogXide vs stdlib | Structlog durable (p50) | stdlib durable |
|---|---|---|---|
| FILE | ~6–11× | 41,364 rec/s (5,583 ns) | 74,605 rec/s |
| STREAM | ~5× (async, see note) | 116,796 rec/s (5,208 ns) | 53,292 rec/s |
Structlog is genuinely fast on the stream sink here, beating stdlib at ~2.2× (116,796 vs 53,292 rec/s) and outrunning both stdlib and Loguru. On the file sink it trails stdlib. LogXide leads all libraries on both sinks — ~6–11× stdlib on file and ~5× on the async stream sink when it fully drains, comparable on Python 3.12 and 3.14. STREAM is best-effort under sustained bursts: confirm delivery with flush() and get_metrics(). See benchmarks.md for the full both-version tables and async accounting.
Architectural advantages (independent of any single benchmark)¶
- Structlog relies on Python's stdlib for actual file output, so its throughput is bounded by stdlib plus its JSON/Console render processors. LogXide writes files through a Rust
BufWriterand does background async I/O for stream/HTTP/OTLP. - Explicit async accounting:
get_metrics()reportsemitted/sink_acknowledged/queue_dropped/delivery_failed/in_flight, so async "throughput" always counts records the sink confirmed — never records that were merely enqueued.
Feature Comparison Matrix¶
| Feature | LogXide | Structlog |
|---|---|---|
| stdlib API compatible | ⚠️ (common patterns; subclassing/custom formatters limited) | ❌ (wrapper layer) |
basicConfig() |
✅ | ❌ |
dictConfig() |
✅ | ❌ (requires stdlib bridge) |
| Processor pipeline | ❌ | ✅ (core feature) |
Context binding (bind()) |
❌ | ✅ |
| JSON rendering | ✅ (HTTPHandler) | ✅ (JSONRenderer) |
| Console rendering | ✅ (ColorFormatter) |
✅ (ConsoleRenderer) |
| FileHandler | ✅ (Rust BufWriter) | ⚠️ (via stdlib) |
| HTTPHandler (async batch) | ✅ | ❌ |
| OTLPHandler | ✅ | ❌ |
| TimedRotatingFileHandler | ✅ (Rust + gzip) | ⚠️ (via stdlib) |
| Sentry integration | ✅ (native) | ⚠️ (stdlib bridge) |
⚠️ Compatibility Caveats¶
LogXide prioritizes performance over full stdlib compatibility. Before adopting, note:
- Custom Python formatters:
logging.Formattersubclasses are not called; format strings are processed natively in Rust - Subclassing:
LogRecordandLoggerare Rust types and cannot be subclassed - Custom Python handlers: Accepted via
addHandler(); a foreign Python handler runs once on the Python side, without the fast-path GIL release. Rust-backed handlers are dispatched once and no longer double-emit or leak to unrelated loggers (fixed in 0.2.0) - pytest
caplog: LogXide provides a custom plugin (auto-registered via entry point); requires explicitlogger.addHandler(caplog.handler)— see Testing Guide
For the complete compatibility matrix, see Compatibility.
When to Use Which¶
Choose LogXide when:¶
- Performance is the priority — LogXide runs formatting and file I/O in a Rust core rather than through Python's stdlib output path. On the corrected, sink-verified harness it leads Structlog on every sink (~6–11× stdlib on file, ~5× on the async stream sink; comparable on Python 3.12 and 3.14), even though Structlog itself beats stdlib on the stream sink.
- You have existing stdlib code — Minimal migration cost; API-compatible for common patterns.
- You need native production handlers — Async HTTP batching, OTLP export, and native Sentry integration without Python-side overhead.
- Framework integration — Transparently hooks into Django/FastAPI
dictConfig.
Choose Structlog when:¶
- Structured data is core — A chainable processor pipeline for field transformation/filtering is essential.
- Context binding is a must — You heavily rely on
.bind()or thread-local context variables to track requests. - Custom processors — You need extreme flexibility in mutating log records before they hit the output.
- JSON-first architecture — Every single output must be rigidly structured JSON.