Skip to main content

rapidlog 🚀

High-performance JSON logging for Python — Pure Python, zero dependencies, designed for speed.

Tests Benchmarks Python 3.10+ License: MIT PyPI


The Problem

Python's logging module has lock contention under multi-threaded load. When your application logs from multiple threads, they compete for a shared lock, killing throughput:

# stdlib logging: 6,487 logs/sec with 4 threads
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Bottleneck: all threads compete for the lock
logger.info("msg", extra={"user_id": 123})

Result: Logging becomes a bottleneck in multi-threaded applications.


The Solution: rapidlog

3.1x faster structured JSON logging with a clean API and zero dependencies.

# rapidlog: 20,133 logs/sec with 4 threads (3.1x faster)
from rapidlog import get_logger

logger = get_logger()
logger.info("user login", user_id=123, ip="192.168.1.1")

That's 13.6K extra logs per second your application can handle.


Installation

pip install rapidlog

Quick Comparison: stdlib vs rapidlog

Before (stdlib logging)

import logging
from pythonjsonlogger import jsonlogger

handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter()
handler.setFormatter(formatter)

logger = logging.getLogger()
logger.addHandler(handler)

# Extra kwargs are awkward
logger.info("user action", extra={"user_id": 123, "action": "login"})

After (rapidlog)

from rapidlog import get_logger

logger = get_logger()
logger.info("user action", user_id=123, action="login")

That's it. Cleaner API, 3x faster, zero dependencies.


Key Features

✨ 3.1x faster than stdlib logging under multi-threaded load
⚡ Zero lock contention on the hot path (per-thread buffers)
🔧 Configuration presets for memory vs throughput trade-offs
🧵 Thread-safe multi-producer, single-consumer design
📦 Zero dependencies — pure Python stdlib only
🛡️ Battle-tested — 37 comprehensive tests covering edge cases


Quick Start

Basic Usage

from rapidlog import get_logger

# Create logger with default settings
logger = get_logger(level="INFO")

# Log with structured fields
logger.info("user action", user_id=123, action="login", ip="192.168.1.1")
logger.warning("cache miss", key="user:456", ttl=3600)
logger.error("database timeout", query="SELECT * FROM users", timeout_ms=5000)

# Always close when done
logger.close()

Migrating from stdlib logging

Step 1: Replace imports

# Before
import logging
logger = logging.getLogger(__name__)

# After
from rapidlog import get_logger
logger = get_logger()

Step 2: Update logging calls

# Before: Awkward extra= syntax
logger.info("user login", extra={"user_id": 123, "ip": "192.168.1.1"})

# After: Clean keyword arguments
logger.info("user login", user_id=123, ip="192.168.1.1")

Step 3: Remove JSON formatter setup

# Before: Complex setup
import logging
from pythonjsonlogger import jsonlogger

handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter()
handler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(handler)

# After: One line
from rapidlog import get_logger
logger = get_logger()

That's it!

  • Logs are now JSON by default
  • You get 3x the throughput
  • Zero dependencies
  • Same thread-safe behavior

Configuration Presets

Choose a preset based on your application's constraints:

# Low-memory mode (2-4 MiB peak, lower throughput)
logger = get_logger(preset="low-memory")

# Balanced mode (5-10 MiB peak, good throughput) - this is the default
logger = get_logger(preset="balanced")

# Throughput mode (10-20 MiB peak, maximum throughput)
logger = get_logger(preset="throughput")

Preset Comparison:

Preset Queue Size Batch Size Peak Memory Best For
low-memory 2,048 64 ~2-4 MiB Memory-constrained environments
balanced 32,768 256 ~5-10 MiB General-purpose applications (default)
throughput 131,072 1,024 ~10-20 MiB High-volume logging

Custom Configuration

logger = get_logger(
    level="DEBUG",
    queue_size=16384,        # Size of cross-thread queue
    batch_size=512,          # Records per write batch
    thread_buffer_size=64,   # Records per thread buffer
    flush_interval=0.02      # Seconds between auto-flushes
)

Performance Benchmarks

Detailed benchmark results from comprehensive comparison (Python 3.13, Windows):

Single-Threaded Performance (1 thread, 100K logs)

Library Throughput vs stdlib-json Peak Memory
rapidlog 21,922 logs/s 2.35x faster 23.9 MiB
fastlogging 26,527 logs/s 2.85x faster 0.02 MiB
structlog-json 13,763 logs/s 1.48x faster 0.02 MiB
stdlib-batching 11,955 logs/s 1.28x faster 0.04 MiB
stdlib-json 9,317 logs/s baseline 0.01 MiB
python-json-logger 8,344 logs/s 0.90x 0.01 MiB
loguru 3,737 logs/s 0.40x 0.03 MiB

Multi-Threaded Performance (4 threads, 100K logs each = 400K total)

Library Throughput vs stdlib-json Peak Memory
fastlogging 24,374 logs/s 3.76x faster 0.06 MiB
rapidlog 20,133 logs/s 3.10x faster 23.9 MiB
structlog-json 12,101 logs/s 1.86x faster 0.02 MiB
stdlib-batching 6,453 logs/s 0.99x 0.05 MiB
python-json-logger 6,527 logs/s 1.01x 0.02 MiB
stdlib-json 6,487 logs/s baseline 0.02 MiB
loguru 3,248 logs/s 0.50x 0.04 MiB

High-Contention Performance (8 threads, 50K logs each = 400K total)

Library Throughput vs stdlib-json Peak Memory
fastlogging 25,674 logs/s 3.99x faster 0.10 MiB
rapidlog 19,685 logs/s 3.06x faster 24.0 MiB
structlog-json 10,152 logs/s 1.58x faster 0.04 MiB
stdlib-batching 7,231 logs/s 1.12x faster 0.07 MiB
stdlib-json 6,441 logs/s baseline 0.04 MiB
python-json-logger 6,079 logs/s 0.94x 0.04 MiB
loguru 3,030 logs/s 0.47x 0.09 MiB

Key Takeaways

  1. rapidlog excels in multi-threaded scenarios — 3.1x faster than stdlib-json with 4+ threads
  2. fastlogging is fastest but lacks structured logging API (manual JSON encoding required)
  3. Memory trade-off is intentional — rapidlog uses ~24 MiB for pre-allocated buffers to eliminate lock contention
  4. Throughput scales linearly with threads due to per-thread buffer architecture

Benchmark Notes

Output format considerations:

  • rapidlog, stdlib-json, structlog, python-json-logger, loguru: All output minimal structured JSON (~100 bytes per log)

    {"ts_ns": 1739462130123456789, "level": "INFO", "msg": "hello", "user_id": 1, "i": 0, "thread": 12345}
    
  • fastlogging: Does NOT output structured JSON

    • Outputs text format: 2026-02-13 10:15:30.123 INFO: {"msg": "hello", ...}
    • Requires manual JSON encoding in application code
    • Not comparable as a structured logging solution

All benchmarks use comparable output formats except fastlogging, ensuring fair throughput comparisons.


Design & Architecture

Design goals

  • Faster than structlog and logging + json-logger
  • Treat JSON as an output format, not the internal hot-path format
  • Avoid dict creation on the hot path where possible
  • Avoid per-log allocation where possible
  • Defer JSON serialization
  • Batch writes
  • Stdout sink only (v1)

Constraints (v1)

  • Python 3.10+
  • No external runtime dependencies
  • No async user-facing API
  • Single writer thread allowed
  • Thread-safe logging from multiple producer threads

Current architecture

The implementation in rapidlog.py currently follows this shape:

  1. Hot path (Logger._log)
    • Level check first.
    • Build a compact array/list record with fixed slots.
    • Append to a per-thread buffer.
  2. Cross-thread handoff
    • Per-thread buffers flush into a bounded RingQueue.
    • Queue is multi-producer / single-consumer.
  3. Writer thread
    • Background writer drains records in batches.
    • JSON serialization happens only in writer thread.
    • Batch is written to sys.stdout.buffer.

Repository layout

  • rapidlog.py — core logger + queue + writer thread with presets
  • benchmark_logging.py — original in-memory benchmark (legacy)
  • benchmark_persisted_logging.py — production-style benchmark with logs persisted to files
  • benchmark_enhanced.py — comprehensive comparison against stdlib, structlog, loguru, python-json-logger, and fastlogging
  • demo_presets.py — demonstration of memory/throughput trade-offs across presets
  • test_rapidlog.py — comprehensive test suite (37 tests covering edge cases)
  • TEST_COVERAGE.md — detailed documentation of all test scenarios

Running benchmarks

1) Preset comparison

python demo_presets.py

Shows memory and throughput characteristics of each preset.

2) Comprehensive comparison

python benchmark_enhanced.py

Compares rapidlog against:

  • stdlib logging (basic, JSON formatter, batching)
  • python-json-logger (optional)
  • structlog (optional)
  • loguru (optional)
  • fastlogging (optional - similar-named library for comparison)

All benchmarks use actual file I/O to measure real-world performance.

3) Legacy benchmarks

# In-memory benchmark
python benchmark_logging.py

# Persisted benchmark
python benchmark_persisted_logging.py

Running tests

1) Full test suite

pytest test_rapidlog.py -v

2) Quick test

pytest test_rapidlog.py -q

3) Specific test class

pytest test_rapidlog.py::TestRingQueue -v

Project status

This is intentionally minimal and focused on validating core architecture and performance trade-offs.

Metadata

Release files for rapidlog 1.0.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rapidlog 1.0.8
File Size Uploaded Provenance Actions
rapidlog-1.0.8.tar.gz 10.9 kB Without provenance Details for rapidlog-1.0.8.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for rapidlog 1.0.8
File Interpreter ABI Platform Actions
rapidlog-1.0.8-py3-none-any.whl Python 3 none any Details for rapidlog-1.0.8-py3-none-any.whl

Total release size: 21.9 kB

Release files / rapidlog-1.0.8.tar.gz

Download URL rapidlog-1.0.8.tar.gz
Size 10.9 kB
Tags Source
SHA-256 checksum
How to use checksums
61442ccc19aa05876d7d190fa7d59da57663757c1c84ae390297a3435337cef7
BLAKE2b-256 checksum
How to use checksums
c94630ef9725f95ce0ee1e9c2a96abd6b7b79d28c6ad87b47c13e36a541e6201
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.12

Release files / rapidlog-1.0.8-py3-none-any.whl

Download URL rapidlog-1.0.8-py3-none-any.whl
Size 11.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2253f2607b4fafb1d9450d4aeb158a0bee9f075209153f84fb5f1c0793a3b339
BLAKE2b-256 checksum
How to use checksums
1f8c9791649bbbf2c50c64b7160b78eefea5296c8efee8fc545f85dfa663e601
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.12

Security for rapidlog 1.0.8

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page