structlog Python
Produce one version-grounded event pipeline whose context lifetime, processor
order, output ownership, event schema, and tests are explicit.
Boundary
Use this skill when the project uses structlog or the user explicitly requests
it. Do not introduce structlog into a standard-library-only or Loguru-only task.
Metrics, traces, OpenTelemetry collector pipelines, and logging-platform queries
are outside scope unless the requested change is the Python structlog boundary.
Preserve the application's established logging owner and event schema unless the
task explicitly changes them.
Know the runtime model
| Object |
Runtime meaning |
Decision consequence |
BoundLogger |
A proxy holding immutable-style bound context, a wrapped output logger, and a processor chain. |
.bind() returns a logger with added context; retain that return value. |
| Event dictionary |
A fresh mapping made from bound context, call fields, and the event value for one log call. |
Processors transform this contract; renderers consume it. |
| Processor |
A callable (wrapped_logger, method_name, event_dict) that returns an event dictionary until the terminal step, or raises DropEvent. |
Order is behavior. Enrich and redact before rendering. |
| Terminal processor |
The one final renderer or adapter that returns what the wrapped logger accepts. |
Never append processors after it or combine incompatible terminals. |
| Wrapped logger / factory |
The output implementation and the callable that creates it: print/write/bytes or stdlib logging. |
Choose from output ownership and interoperability, not familiarity. |
| Context variables |
Execution-context-local fields merged by merge_contextvars. |
Clear at each request/job start, then bind; test real sync/async boundaries. |
| Global configuration |
Defaults used when lazy logger proxies are first assembled. |
Configure once before first use; caching can freeze earlier choices. |
A call follows this path:
bound context + call fields + event
-> fresh event_dict
-> processor 1 -> ... -> processor N
-> exactly one terminal renderer/adapter
-> wrapped logger / handler / stream
Read the object and event model whenever .bind(),
processor return values, configuration caching, or output ownership is unclear.
Ordered workflow
- Recover the event contract: stable event names, required keys, levels,
timestamps, correlation fields, exceptions, redaction, and output consumer.
- Inventory existing
logging handlers/formatters, framework initialization,
and foreign stdlib emitters. Choose one integration architecture.
- Build shared processors in semantic order. Put one renderer or handoff last.
- Configure at the composition root before application work or cached loggers.
- Bind long-lived component data on returned loggers; clear and bind
request/job context at its boundary.
- Emit named events with structured keyword fields. Do not duplicate values in
interpolated prose.
- Test event dictionaries separately from final transport/rendering.
Choose by intent
| Intent |
Use |
| Obtain a lazy logger proxy |
structlog.get_logger(...) |
| Add component/object fields |
log = log.bind(...) |
| Remove known bound keys |
.unbind(...); use .try_unbind(...) only when absence is allowed |
| Replace a bound logger's local context |
.new(...); do not confuse this with contextvars cleanup |
| Add request/task context |
clear_contextvars() then bind_contextvars(...), with merge_contextvars first in the chain |
| Bind context for one nested scope |
with bound_contextvars(...): |
| Filter a non-stdlib pipeline cheaply |
make_filtering_bound_logger(level) |
| Filter through stdlib logger levels |
structlog.stdlib.filter_by_level with a stdlib logger factory |
| Add standard fields |
built-in processors such as add_log_level, add_logger_name, and TimeStamper |
| Render exception text |
format_exc_info before the renderer |
| Emit structured traceback data |
ExceptionRenderer(ExceptionDictTransformer(show_locals=False)) after installed-version inspection; enable locals only by an explicit safe contract |
| Production structured output |
JSONRenderer or an established terminal adapter |
| Human development output |
ConsoleRenderer, selected by configuration, not event call sites |
| Drop an event in a processor |
raise structlog.DropEvent deliberately |
| Assert event semantics |
LogCapture or carefully scoped capture_logs() |
| Assert wrapped call/rendering |
CapturingLoggerFactory, a real handler, or parsed output |
Read the intent-to-API map before reaching for an
unfamiliar helper or relying on an exact signature.
Canonical standalone pipeline
import logging
import structlog
def configure_logging(*, development: bool = False) -> None:
renderer = (
structlog.dev.ConsoleRenderer() if development else structlog.processors.JSONRenderer()
)
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso", utc=True),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
renderer,
],
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
logger_factory=structlog.WriteLoggerFactory(),
cache_logger_on_first_use=True,
)
log = structlog.get_logger("payments").bind(component="authorizer")
log.info("payment_authorized", payment_id="pay_123", amount_cents=2500)
get_logger() returns a proxy; the event dictionary is assembled and processed
when a log method runs. WriteLoggerFactory writes a whole line atomically.
Choose the factory that matches the application stream and renderer return type.
Choose exactly one integration architecture
| Condition |
Architecture |
| Application events use structlog; foreign stdlib output need not share its schema |
Render in structlog and let the selected wrapped logger transport the result. |
| structlog and foreign stdlib records must share one final renderer |
Use ProcessorFormatter: structlog ends with wrap_for_formatter; formatter-side processors remove metadata then render. |
| Existing stdlib handlers and formatters must receive event fields |
Use the installed supported render-to-logging adapter and test LogRecord extras. |
| Conventional stdlib-backed defaults are sufficient |
Inspect structlog.stdlib.recreate_defaults() before custom configuration. |
Do not put JSONRenderer before ProcessorFormatter.wrap_for_formatter. Do not
render once in structlog and again in a formatter. Read stdlib integration.
Processor and context rules
- Order filters before expensive enrichment when the selected logger makes that
valid. Merge context before processors that consume those fields.
- A normal custom processor must return the event dictionary on every retained
path. It may mutate it because structlog copied the call context first.
- Redact or omit secrets before any renderer or stdlib handoff. Never bind raw
tokens, credentials, authorization headers, full request bodies, or unbounded
objects.
- Keep one stable machine event name such as
inventory_reservation_failed;
put changing values in fields. Treat field names as an external contract.
- Use logger
.bind() for data scoped to a returned logger. Use contextvars for
data that follows the current execution context. These are different stores.
- In hybrid sync/async frameworks, test propagation across the actual boundary;
contextvars can be isolated between execution mechanisms.
Read processor contracts and context lifecycle.
Failure routing
- Duplicate lines: inspect propagation, root/child handlers, repeated startup,
and whether two layers render the same record.
- Missing foreign fields: verify the chosen formatter/adapter path and its
foreign_pre_chain; do not assume all LogRecord extras survive.
- Missing exception detail: pass
exc_info via .exception(...) or the intended
signal and run an exception processor before rendering.
- Leaked request IDs: clear at unit-of-work start and test two sequential units.
- Changed configuration has no effect: inspect cached loggers and initialization
order; do not repeatedly reset production global state as a workaround.
- Slow async code: measure the actual processor and I/O cost, then inspect the
installed async methods and executor behavior before redesigning.
Version grounding and completion
Inspect project locks and run python scripts/inspect_structlog.py from the
installed skill directory when names, signatures, async methods, renderer byte
contracts, or integration helpers can drift. Read API grounding.
Do not declare completion until one output owner is evident; processor ordering
and the terminal step are coherent; event keys, level filtering, exception
shape, context cleanup, and secret exclusion are tested; foreign records and
duplicate initialization are tested when applicable; and project checks pass or
skipped evidence and consequences are reported.
References
- Event pipeline recipes
- Integration and test recipes
- Object and event model
- Intent-to-API map
- Processor contracts
- Standard-library integration
- Context lifecycle
- Testing structlog behavior
- API grounding
1---2name: structlog-python3description: Use for writing, configuring, integrating, reviewing, debugging, or testing Python structured logging with structlog. Trigger for bound loggers, event dictionaries, processor chains, JSON or console rendering, standard-library logging integration, contextvars, request correlation, exception rendering, and structlog test capture. Do not use for stdlib-logging-only, Loguru-only, metrics-only, tracing-only, or collector configuration tasks that do not use structlog.4---56# structlog Python78Produce one version-grounded event pipeline whose context lifetime, processor9order, output ownership, event schema, and tests are explicit.1011## Boundary1213Use this skill when the project uses structlog or the user explicitly requests14it. Do not introduce structlog into a standard-library-only or Loguru-only task.15Metrics, traces, OpenTelemetry collector pipelines, and logging-platform queries16are outside scope unless the requested change is the Python structlog boundary.17Preserve the application's established logging owner and event schema unless the18task explicitly changes them.1920## Know the runtime model2122| Object | Runtime meaning | Decision consequence |23|---|---|---|24| `BoundLogger` | A proxy holding immutable-style bound context, a wrapped output logger, and a processor chain. | `.bind()` returns a logger with added context; retain that return value. |25| Event dictionary | A fresh mapping made from bound context, call fields, and the `event` value for one log call. | Processors transform this contract; renderers consume it. |26| Processor | A callable `(wrapped_logger, method_name, event_dict)` that returns an event dictionary until the terminal step, or raises `DropEvent`. | Order is behavior. Enrich and redact before rendering. |27| Terminal processor | The one final renderer or adapter that returns what the wrapped logger accepts. | Never append processors after it or combine incompatible terminals. |28| Wrapped logger / factory | The output implementation and the callable that creates it: print/write/bytes or stdlib logging. | Choose from output ownership and interoperability, not familiarity. |29| Context variables | Execution-context-local fields merged by `merge_contextvars`. | Clear at each request/job start, then bind; test real sync/async boundaries. |30| Global configuration | Defaults used when lazy logger proxies are first assembled. | Configure once before first use; caching can freeze earlier choices. |3132A call follows this path:3334```text35bound context + call fields + event36 -> fresh event_dict37 -> processor 1 -> ... -> processor N38 -> exactly one terminal renderer/adapter39 -> wrapped logger / handler / stream40```4142Read [the object and event model](references/object-model.md) whenever `.bind()`,43processor return values, configuration caching, or output ownership is unclear.4445## Ordered workflow46471. Recover the event contract: stable event names, required keys, levels,48 timestamps, correlation fields, exceptions, redaction, and output consumer.492. Inventory existing `logging` handlers/formatters, framework initialization,50 and foreign stdlib emitters. Choose one integration architecture.513. Build shared processors in semantic order. Put one renderer or handoff last.524. Configure at the composition root before application work or cached loggers.535. Bind long-lived component data on returned loggers; clear and bind54 request/job context at its boundary.556. Emit named events with structured keyword fields. Do not duplicate values in56 interpolated prose.577. Test event dictionaries separately from final transport/rendering.5859## Choose by intent6061| Intent | Use |62|---|---|63| Obtain a lazy logger proxy | `structlog.get_logger(...)` |64| Add component/object fields | `log = log.bind(...)` |65| Remove known bound keys | `.unbind(...)`; use `.try_unbind(...)` only when absence is allowed |66| Replace a bound logger's local context | `.new(...)`; do not confuse this with contextvars cleanup |67| Add request/task context | `clear_contextvars()` then `bind_contextvars(...)`, with `merge_contextvars` first in the chain |68| Bind context for one nested scope | `with bound_contextvars(...):` |69| Filter a non-stdlib pipeline cheaply | `make_filtering_bound_logger(level)` |70| Filter through stdlib logger levels | `structlog.stdlib.filter_by_level` with a stdlib logger factory |71| Add standard fields | built-in processors such as `add_log_level`, `add_logger_name`, and `TimeStamper` |72| Render exception text | `format_exc_info` before the renderer |73| Emit structured traceback data | `ExceptionRenderer(ExceptionDictTransformer(show_locals=False))` after installed-version inspection; enable locals only by an explicit safe contract |74| Production structured output | `JSONRenderer` or an established terminal adapter |75| Human development output | `ConsoleRenderer`, selected by configuration, not event call sites |76| Drop an event in a processor | raise `structlog.DropEvent` deliberately |77| Assert event semantics | `LogCapture` or carefully scoped `capture_logs()` |78| Assert wrapped call/rendering | `CapturingLoggerFactory`, a real handler, or parsed output |7980Read [the intent-to-API map](references/api-map.md) before reaching for an81unfamiliar helper or relying on an exact signature.8283## Canonical standalone pipeline8485```python86import logging8788import structlog899091def configure_logging(*, development: bool = False) -> None:92 renderer = (93 structlog.dev.ConsoleRenderer() if development else structlog.processors.JSONRenderer()94 )95 structlog.configure(96 processors=[97 structlog.contextvars.merge_contextvars,98 structlog.stdlib.add_log_level,99 structlog.processors.TimeStamper(fmt="iso", utc=True),100 structlog.processors.StackInfoRenderer(),101 structlog.processors.format_exc_info,102 renderer,103 ],104 wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),105 logger_factory=structlog.WriteLoggerFactory(),106 cache_logger_on_first_use=True,107 )108109110log = structlog.get_logger("payments").bind(component="authorizer")111log.info("payment_authorized", payment_id="pay_123", amount_cents=2500)112```113114`get_logger()` returns a proxy; the event dictionary is assembled and processed115when a log method runs. `WriteLoggerFactory` writes a whole line atomically.116Choose the factory that matches the application stream and renderer return type.117118## Choose exactly one integration architecture119120| Condition | Architecture |121|---|---|122| Application events use structlog; foreign stdlib output need not share its schema | Render in structlog and let the selected wrapped logger transport the result. |123| structlog and foreign stdlib records must share one final renderer | Use `ProcessorFormatter`: structlog ends with `wrap_for_formatter`; formatter-side processors remove metadata then render. |124| Existing stdlib handlers and formatters must receive event fields | Use the installed supported render-to-logging adapter and test `LogRecord` extras. |125| Conventional stdlib-backed defaults are sufficient | Inspect `structlog.stdlib.recreate_defaults()` before custom configuration. |126127Do not put `JSONRenderer` before `ProcessorFormatter.wrap_for_formatter`. Do not128render once in structlog and again in a formatter. Read [stdlib integration](references/integration.md).129130## Processor and context rules131132- Order filters before expensive enrichment when the selected logger makes that133 valid. Merge context before processors that consume those fields.134- A normal custom processor must return the event dictionary on every retained135 path. It may mutate it because structlog copied the call context first.136- Redact or omit secrets before any renderer or stdlib handoff. Never bind raw137 tokens, credentials, authorization headers, full request bodies, or unbounded138 objects.139- Keep one stable machine event name such as `inventory_reservation_failed`;140 put changing values in fields. Treat field names as an external contract.141- Use logger `.bind()` for data scoped to a returned logger. Use contextvars for142 data that follows the current execution context. These are different stores.143- In hybrid sync/async frameworks, test propagation across the actual boundary;144 contextvars can be isolated between execution mechanisms.145146Read [processor contracts](references/processors.md) and [context lifecycle](references/context.md).147148## Failure routing149150- Duplicate lines: inspect propagation, root/child handlers, repeated startup,151 and whether two layers render the same record.152- Missing foreign fields: verify the chosen formatter/adapter path and its153 `foreign_pre_chain`; do not assume all `LogRecord` extras survive.154- Missing exception detail: pass `exc_info` via `.exception(...)` or the intended155 signal and run an exception processor before rendering.156- Leaked request IDs: clear at unit-of-work start and test two sequential units.157- Changed configuration has no effect: inspect cached loggers and initialization158 order; do not repeatedly reset production global state as a workaround.159- Slow async code: measure the actual processor and I/O cost, then inspect the160 installed async methods and executor behavior before redesigning.161162## Version grounding and completion163164Inspect project locks and run `python scripts/inspect_structlog.py` from the165installed skill directory when names, signatures, async methods, renderer byte166contracts, or integration helpers can drift. Read [API grounding](references/api-grounding.md).167168Do not declare completion until one output owner is evident; processor ordering169and the terminal step are coherent; event keys, level filtering, exception170shape, context cleanup, and secret exclusion are tested; foreign records and171duplicate initialization are tested when applicable; and project checks pass or172skipped evidence and consequences are reported.173174## References175176- [Event pipeline recipes](references/recipes-events.md)177- [Integration and test recipes](references/recipes-integration.md)178- [Object and event model](references/object-model.md)179- [Intent-to-API map](references/api-map.md)180- [Processor contracts](references/processors.md)181- [Standard-library integration](references/integration.md)182- [Context lifecycle](references/context.md)183- [Testing structlog behavior](references/testing.md)184- [API grounding](references/api-grounding.md)