Skip to content

Usage Guide

Quick Start

from logxide import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger('myapp')
logger.info('Hello from LogXide!')

Basic Usage

LogXide provides a familiar API similar to Python's logging module:

from logxide import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger('myapp')
logger.info('Hello from LogXide!')
logger.warning('This is a warning')
logger.error('This is an error')

Handler Usage

from logxide import logging

# Console output (default: stderr)
logging.basicConfig(level=logging.INFO)

# File output
logging.basicConfig(
    filename='app.log',
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

Using addHandler

LogXide's Rust native handlers give the best throughput, but addHandler() also accepts standard Python logging.Handler subclasses, which run alongside the Rust pipeline:

from logxide import logging, FileHandler, StreamHandler, RotatingFileHandler

logger = logging.getLogger('myapp')

# File handler
handler = FileHandler('app.log')
handler.setLevel(logging.INFO)
logger.addHandler(handler)

# Rotating file handler
rotating = RotatingFileHandler(
    'app.log',
    maxBytes=10 * 1024 * 1024,  # 10MB
    backupCount=5
)
logger.addHandler(rotating)

# Stream handler (stdout/stderr)
stream = StreamHandler()
logger.addHandler(stream)

HTTP and OTLP Handlers

from logxide import HTTPHandler, OTLPHandler

# HTTP log shipping
http_handler = HTTPHandler(
    url="https://logs.example.com",
    global_context={"app": "myapp", "env": "production"}
)

# OpenTelemetry OTLP
otlp_handler = OTLPHandler(
    url="http://localhost:4318/v1/logs",
    service_name="my-service"
)

⚠️ Common Mistakes

1. Mixing Python stdlib handlers with Rust handlers

from logxide import logging, FileHandler
import logging as stdlib

logger = logging.getLogger('myapp')

# ⚠️ Accepted, but runs alongside the Rust pipeline (no zero-GIL path)
logger.addHandler(stdlib.FileHandler('app.log'))  # may cause duplicate processing

# ✅ PREFERRED — Use LogXide handlers for the fast path
logger.addHandler(FileHandler('app.log'))

2. StringIO capture doesn't work

# ❌ WRONG — Rust writes directly to OS stdout/stderr
import io
stream = io.StringIO()
handler = logging.StreamHandler(stream)  # Won't capture

# ✅ CORRECT — Use file-based testing
import tempfile
with tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.log') as f:
    logging.basicConfig(filename=f.name, level=logging.INFO, force=True)
    logger.info('Test message')
    logging.flush()
    with open(f.name) as log_file:
        assert 'Test message' in log_file.read()

3. pytest caplog — Use caplog_logxide instead

# ❌ caplog fixture is not compatible with LogXide
def test_with_caplog(caplog):
    ...  # Won't capture LogXide output

# ✅ Use caplog_logxide fixture
def test_logging(caplog_logxide):
    logger = logging.getLogger('test')
    logger.info('Test message')
    assert 'Test message' in caplog_logxide.text
    assert ('test', 20, 'Test message') in caplog_logxide.record_tuples

Advanced Formatting

Multi-threaded Format with Padding

logging.basicConfig(
    format='[%(asctime)s] %(threadName)-10s | %(name)-15s | %(levelname)-8s | %(message)s',
    datefmt='%H:%M:%S'
)

JSON-like Structured Logging

logging.basicConfig(
    format='{"timestamp":"%(asctime)s","level":"%(levelname)s","logger":"%(name)s","message":"%(message)s"}',
    datefmt='%Y-%m-%dT%H:%M:%S'
)

Production Format

logging.basicConfig(
    format='%(asctime)s [%(process)d:%(thread)d] %(levelname)s %(name)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

Thread Support

import threading
from logxide import logging

def worker(worker_id):
    logging.set_thread_name(f'Worker-{worker_id}')
    logger = logging.getLogger(f'worker.{worker_id}')
    logger.info(f'Worker {worker_id} starting')
    logger.info(f'Worker {worker_id} finished')

logging.basicConfig(
    format='%(threadName)-10s | %(name)s | %(message)s'
)

threads = [threading.Thread(target=worker, args=[i]) for i in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()

Supported Format Specifiers

Specifier Description
%(asctime)s Timestamp
%(name)s Logger name
%(levelname)s Log level (INFO, WARNING, etc.)
%(levelno)d Log level number
%(message)s Log message
%(thread)d Thread ID
%(threadName)s Thread name
%(process)d Process ID
%(msecs)d Milliseconds
%(pathname)s Full pathname (Triggers caller frame introspection)
%(filename)s Filename (Triggers caller frame introspection)
%(module)s Module name (Triggers caller frame introspection)
%(lineno)d Line number (Triggers caller frame introspection)
%(funcName)s Function name (Triggers caller frame introspection)

Caller-Info Frame Introspection

Using any of the caller-info fields (%(pathname)s, %(filename)s, %(module)s, %(lineno)d, %(funcName)s) requires CPython stack frame inspection. - Automatic Activation: LogXide dynamically detects these placeholders and enables optimized CPython frame extraction. - Compatibility Layer: When using standard library formatters via the compat_handlers.py path, caller-info context is automatically enabled and routed to the native backend via the activate_caller_info mechanism.

Tuple and List Serialization

To maintain uniform structured representation, passing Python tuple or list structures inside extra dictionaries or global_context will automatically serialize them as JSON arrays (e.g., (1, 2, 3) becomes [1, 2, 3]) inside native HTTP and structured output pipelines.

Advanced Formatting Features

  • Padding: %(levelname)-8s (left-align, 8 chars)
  • Zero padding: %(msecs)03d (3 digits with leading zeros)
  • Custom date format: datefmt='%Y-%m-%d %H:%M:%S'

Flush Support

Ensure all log messages are processed before program exit:

logger.info('Important message')
logging.flush()  # Wait for all logging to complete

Handler-specific flush behavior

  • FileHandler / RotatingFileHandler: flush() flushes the BufWriter buffer to disk (synchronous)
  • StreamHandler: flush() signals the background thread to drain all queued messages, then waits for completion
  • HTTPHandler / OTLPHandler: flush() triggers immediate batch send and waits for completion

Examples

Check out the examples/ directory for comprehensive usage examples:

python examples/minimal_dropin.py