structlog
Structured logging library for Python — processes log events as dictionaries through a configurable processor pipeline before outputting. structlog features: bound loggers (log = structlog.get_logger(); log = log.bind(key=val)), processor pipeline (filter_by_level, add_log_level, add_logger_name, TimeStamper, JSONRenderer, ConsoleRenderer), stdlib integration (structlog.stdlib), async support, thread-local context, lazy evaluation, type-checked API, BoundLogger.new() for new instance, context variables, custom processors, and drop-in logging.getLogger() compatibility via structlog.stdlib.BoundLogger.
Score Breakdown
⚙ Agent Friendliness
🔒 Security
Structured logging library. All log values are serialized — do not log secrets, tokens, or PII. Add a redaction processor to filter sensitive keys. contextvars context may persist across async tasks — clear between requests.
⚡ Reliability
Best When
Production Python services needing structured JSON logs with processor pipelines — structlog is the standard for observability-focused Python services where every log field matters.
Avoid When
Quick scripts (use loguru), zero-config needs (use loguru), or when stdlib logging is sufficient.
Use Cases
- • Agent JSON logging — import structlog; structlog.configure(processors=[structlog.processors.JSONRenderer()]); log = structlog.get_logger(); log.info('processed', count=items, duration_ms=elapsed) — structured JSON; agent emits machine-parseable JSON logs with key=value pairs; every log call produces valid JSON with consistent fields
- • Agent context binding — log = structlog.get_logger(); log = log.bind(request_id=req_id, agent_id=agent_id); log.info('started'); log.debug('step', step=1); log.info('finished') — accumulated context; agent builds up log context progressively; all subsequent calls include bound fields automatically
- • Agent stdlib integration — structlog.configure(wrapper_class=structlog.stdlib.BoundLogger, logger_factory=structlog.stdlib.LoggerFactory()); log = structlog.get_logger('mymodule') — stdlib compat; agent integrates with existing logging.getLogger() infrastructure; routes through stdlib handlers for existing log aggregators
- • Agent async logging — log = structlog.get_logger(); async def process(): await log.ainfo('async event', key=val) — async native; agent uses awaitable logging in async contexts; structlog 21.5+ has native async support with ainfo/adebug/aerror methods; no event loop blocking
- • Agent custom processors — def add_hostname(logger, method, event_dict): event_dict['host'] = socket.gethostname(); return event_dict; structlog.configure(processors=[add_hostname, structlog.processors.JSONRenderer()]) — processor pipeline; agent adds custom fields to every log message via processor functions
Not For
- • Simple text logging — structlog's power is in structured data; if plain text logging suffices, stdlib logging or loguru is simpler
- • Zero-config quick start — structlog requires configure() setup; loguru is simpler for quick scripts
- • Non-Python environments — structlog is Python-only; for polyglot structured logging, use a log aggregator like ELK
Interface
Authentication
No auth — logging library.
Pricing
structlog is MIT licensed. Free for all use.
Agent Metadata
Known Gotchas
- ⚠ structlog.configure() must be called before first log — calling get_logger() before configure() uses default configuration (dev-friendly console output, not JSON); agent code: call structlog.configure() at application startup before any imports that might log; reconfiguring after first use may not affect cached loggers
- ⚠ bind() returns new logger, does not mutate — log = structlog.get_logger(); log.bind(key=val) — this is WRONG; must reassign: log = log.bind(key=val); agent code always reassign: log = log.bind(request_id=id); unbind() returns new logger without specified keys; new() clears all bound context
- ⚠ Processors receive (logger, method_name, event_dict) — custom processor signature must match exactly; event_dict is mutable dict; return event_dict to pass along; raise structlog.DropEvent() to suppress; agent code: def my_processor(logger, method, event_dict): event_dict['extra'] = compute(); return event_dict — must return event_dict
- ⚠ stdlib integration requires both configure() sides — structlog.stdlib integration needs configure(wrapper_class=structlog.stdlib.BoundLogger) AND stdlib logging.basicConfig(); both must be set up; agent code mixing structlog and stdlib: use structlog.stdlib.ProcessorFormatter as stdlib handler formatter for unified output
- ⚠ Thread-local context requires explicit reset — structlog.contextvars.bind_contextvars(key=val) binds to current async/thread context; must call clear_contextvars() between requests/tasks or context leaks between calls; agent code in web frameworks: clear_contextvars() in middleware before each request; asyncio tasks inherit parent context
- ⚠ ConsoleRenderer vs JSONRenderer — ConsoleRenderer produces human-readable colorized output; JSONRenderer produces JSON strings; use ConsoleRenderer in dev, JSONRenderer in production; cannot use both on same logger; agent code: configure based on environment: processors=[JSONRenderer()] if os.getenv('ENV')=='prod' else [ConsoleRenderer()]
Alternatives
Full Evaluation Report
Comprehensive deep-dive: security analysis, reliability audit, agent experience review, cost modeling, competitive positioning, and improvement roadmap for structlog.
AI-powered analysis · PDF + markdown · Delivered within 30 minutes
Package Brief
Quick verdict, integration guide, cost projections, gotchas with workarounds, and alternatives comparison.
Delivered within 10 minutes
Score Monitoring
Get alerted when this package's AF, security, or reliability scores change significantly. Stay ahead of regressions.
Continuous monitoring
Scores are editorial opinions as of 2026-03-06.