structlog-25.1.0/.git_archival.txt 0000644 0000000 0000000 00000000146 14742155321 014033 0 ustar 00 node: $Format:%H$
node-date: $Format:%cI$
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
structlog-25.1.0/.gitattributes 0000644 0000000 0000000 00000000217 14742155321 013452 0 ustar 00 # Force LF line endings for text files
* text=auto eol=lf
# Needed for hatch-vcs / setuptools-scm-git-archive
.git_archival.txt export-subst
structlog-25.1.0/.pre-commit-config.yaml 0000644 0000000 0000000 00000001267 14742155321 015046 0 ustar 00 ---
ci:
autoupdate_schedule: monthly
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.1
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/econchick/interrogate
rev: 1.7.0
hooks:
- id: interrogate
args: [tests]
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
args: [-L, alog]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
exclude: docs/_static
- id: check-toml
- id: check-yaml
structlog-25.1.0/.python-version-default 0000644 0000000 0000000 00000000005 14742155321 015201 0 ustar 00 3.13
structlog-25.1.0/.readthedocs.yaml 0000644 0000000 0000000 00000000755 14742155321 014015 0 ustar 00 ---
version: 2
build:
os: ubuntu-lts-latest
tools:
# Keep version in sync with tox.ini/docs and ci.yml/docs.
python: "3.13"
jobs:
create_environment:
# Need the tags to calculate the version (sometimes).
- git fetch --tags
- asdf plugin add uv
- asdf install uv latest
- asdf global uv latest
build:
html:
- uvx --with tox-uv tox run -e docs-sponsors
- uvx --with tox-uv tox run -e docs-build -- $READTHEDOCS_OUTPUT
structlog-25.1.0/CHANGELOG.md 0000644 0000000 0000000 00000132674 14742155321 012405 0 ustar 00 # Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Calendar Versioning](https://calver.org/).
The **first number** of the version is the year.
The **second number** is incremented with each release, starting at 1 for each year.
The **third number** is for emergencies when we need to start branches for older releases.
You can find our backwards-compatibility policy [here](https://github.com/hynek/structlog/blob/main/.github/SECURITY.md).
## [25.1.0](https://github.com/hynek/structlog/compare/24.4.0...25.1.0) - 2025-01-16
### Added
- Add `structlog.stdlib.render_to_log_args_and_kwargs` processor.
Same as `structlog.stdlib.render_to_log_kwargs`, but also allows to pass positional arguments to `logging`.
With it, you do not need to add `structlog.stdlib.PositionalArgumentsFormatter` processor to format positional arguments from *structlog* loggers.
[#668](https://github.com/hynek/structlog/pull/668)
- Native loggers now have `is_enabled_for()` and `get_effective_level()` methods that mirror the behavior of the standard library's `logging.Logger.isEnabledFor()` and `logging.Logger.getEffectiveLevel()`.
[#689](https://github.com/hynek/structlog/pull/689)
### Changed
- `structlog.typing.BindableLogger` protocol now returns `Self` instead of `BindableLogger`.
This adds a dependency on [*typing-extensions*](https://pypi.org/project/typing-extensions/) for Pythons older than 3.11.
[#642](https://github.com/hynek/structlog/pull/642)
[#659](https://github.com/hynek/structlog/pull/659)
- `structlog.dev.ConsoleRenderer` will quote string value with special characters.
[#647](https://github.com/hynek/structlog/pull/647)
- `structlog.stdlib.recreate_defaults()` now also adds `structlog.stdlib.PositionalArgumentsFormatter`.
In default native mode, this is done by the loggers at the edge.
- `structlog.make_filtering_bound_logger()` now also accepts a string for *min_level*.
### Fixed
- Fix handling calls to `{logger}.exception()` outside of exception blocks.
Depending on the structlog configuration,
this either resulted in an event dict key `exception: "MISSING"` or lead to an error.
Now, an invalid or missing `exc_info` will just be ignored.
This means, that calling `{logger}.exception()` outside of an exception block is basically the same as calling `{logger}.error()`.
[#634](https://github.com/hynek/structlog/issues/634)
[#680](https://github.com/hynek/structlog/pull/680)
- Instantiating `structlog.dev.ConsoleRenderer` does not mutate the passed *styles* dict anymore.
[#669](https://github.com/hynek/structlog/pull/669)
- The native `FilteringBoundLogger.fatal()` method now maps to the critical level, as it does in the standard library.
Note that the level is discouraged to use there, so we recommend to stick to `error()` or `critical()`.
[#677](https://github.com/hynek/structlog/pull/677)
- `structlog.tracebacks.ExceptionDictTransformer` now actually accepts `None` for `locals_max_length` and `locals_max_string`.
[#675](https://github.com/hynek/structlog/pull/675)
## [24.4.0](https://github.com/hynek/structlog/compare/24.3.0...24.4.0) - 2024-07-17
### Changed
No code changes since 24.3.0
## [24.3.0](https://github.com/hynek/structlog/compare/24.2.0...24.3.0) - 2024-07-17
### Added
- Restore feature parity between `structlog.traceback.ExceptionDictTransformer` and Rich's traceback extractor:
- When displaying locals, use Rich for formatting if it is available.
- When displaying locals, call `repr()` on strings, too (improves handling of `SecretStr` implementations).
- Add `locals_max_length` config option
- Add `locals_hide_sunder` config option
- Add `locals_hide_dunder` config option
- Add `suppress` config option
[#627](https://github.com/hynek/structlog/pull/627)
### Changed
- `structlog.testing.capture_logs()` now maps the `exception` log level to `error` (as it's elsewhere).
[#628](https://github.com/hynek/structlog/pull/628)
## [24.2.0](https://github.com/hynek/structlog/compare/24.1.0...24.2.0) - 2024-05-27
### Added
- It is now possible to disable log level-padding in `structlog.dev.LogLevelColumnFormatter` and `structlog.dev.ConsoleRenderer`.
[#599](https://github.com/hynek/structlog/pull/599)
- The `structlog.processors.CallsiteParameterAdder` can now be pickled.
[#603](https://github.com/hynek/structlog/pull/603)
- `structlog.processors.CallsiteParameterAdder` now also works with `structlog.stdlib.BoundLogger`'s non-standard async methods (`ainfo()`, and so forth)
[#618](https://github.com/hynek/structlog/pull/618)
### Changed
- `structlog.processors.LogfmtRenderer` now escapes newlines.
[#592](https://github.com/hynek/structlog/pull/592)
- `structlog.processors.LogfmtRenderer` now escapes backslashes and double quotes.
[#594](https://github.com/hynek/structlog/pull/594)
- `structlog.processors.CallsiteParameterAdder` has been optimized to be about 2x faster.
[#606](https://github.com/hynek/structlog/pull/606)
### Fixed
- `structlog.stdlib.render_to_log_kwargs` now correctly passes stacklevel as a kwarg to stdlib logging.
[#619](https://github.com/hynek/structlog/pull/620)
## [24.1.0](https://github.com/hynek/structlog/compare/23.3.0...24.1.0) - 2024-01-08
### Fixed
- The lazy logger proxy returned by `structlog.get_logger()` now returns its initial values when asked for context.
When asked for context before binding for the first time, it returned an empty dictionary in 23.3.0.
- The displayed level name when using `structlog.stdlib.BoundLogger.exception()` is `"error"` instead of `"exception"`.
Fixes regression in 23.3.0.
[#584](https://github.com/hynek/structlog/issues/584)
- Don't ignore the `width` argument of `RichTracebackFormatter`.
[#587](https://github.com/hynek/structlog/issues/587)
## [23.3.0](https://github.com/hynek/structlog/compare/23.2.0...23.3.0) - 2023-12-29
### Added
- The colorful development logger is now even more configurable!
Choose freely your colors and the order of the key-value pairs!
Implement your own formatters for certain keys!
Implementing the output on top of the new columns API has changed the default very slightly, but shouldn't be noticeable.
[#577](https://github.com/hynek/structlog/issues/577)
- Async log methods (those starting with an `a`) now also support the collection of callsite information using `structlog.processors.CallsiteParameterAdder`.
[#565](https://github.com/hynek/structlog/issues/565)
### Changed
- `structlog.stdlib.recreate_defaults()` now also adds `structlog.stdlib.add_logger_name` to the processors.
Check out the [updated screenshot](https://raw.githubusercontent.com/hynek/structlog/main/docs/_static/console_renderer.png)!
### Fixed
- The return value from `get_logger()` (a `BoundLoggerLazyProxy`) now passes `isinstance`-checks against `structlog.typing.BindableLogger` on Python 3.12.
[#561](https://github.com/hynek/structlog/issues/561)
- `structlog.threadlocal.tmp_bind()` now also works with `BoundLoggerLazyProxy` (in other words: before anything is bound to a bound logger).
- stdlib: `ProcessorFormatter` can now be told to not render the log record message using `getMessage` and just `str(record.msg)` instead.
[#550](https://github.com/hynek/structlog/issues/550)
- stdlib: `structlog.stdlib.BoundLogger.exception()`'s handling of`LogRecord.exc_info` is now set consistent with `logging`.
[#571](https://github.com/hynek/structlog/issues/571)
[#572](https://github.com/hynek/structlog/issues/572)
## [23.2.0](https://github.com/hynek/structlog/compare/23.1.0...23.2.0) - 2023-10-09
### Removed
- Support for Python 3.7.
### Added
- Official support for Python 3.12.
[#515](https://github.com/hynek/structlog/issues/515)
- `structlog.processors.MaybeTimeStamper` that only adds a timestamp if there isn't one already.
[#81](https://github.com/hynek/structlog/issues/81)
- `structlog.dev.ConsoleRenderer` now supports renamed timestamp keys using the *timestamp_key* parameter.
[#541](https://github.com/hynek/structlog/issues/541)
- `structlog.dev.RichTracebackFormatter` that allows to configure the traceback formatting.
[#542](https://github.com/hynek/structlog/issues/542)
### Fixed
- `FilteringBoundLogger.exception()` and `FilteringBoundLogger.aexception()` now support positional argument formatting like the rest of the methods.
[#531](https://github.com/hynek/structlog/issues/531)
- `structlog.processors.format_exc_info()` and `structlog.dev.ConsoleRenderer` do not crash anymore when told to format a non-existent exception.
[#533](https://github.com/hynek/structlog/issues/533)
## [23.1.0](https://github.com/hynek/structlog/compare/22.3.0...23.1.0) - 2023-04-06
### Added
- `structlog.stdlib.BoundLogger` now has, analogously to our native logger, a full set of async log methods prefixed with an `a`: `await log.ainfo("event!")`
[#502](https://github.com/hynek/structlog/issues/502)
- The default configuration now respects the presence of `FORCE_COLOR` (regardless of its value, unless an empty string).
This disables all heuristics whether it makes sense to use colors.
[#503](https://github.com/hynek/structlog/issues/503)
- The default configuration now respects the presence of [`NO_COLOR`](https://no-color.org) (regardless of its value, unless an empty string).
This disables all heuristics whether it makes sense to use colors and overrides `FORCE_COLOR`.
[#504](https://github.com/hynek/structlog/issues/504)
### Fixed
- ConsoleRenderer now reuses the `_figure_out_exc_info` to process the `exc_info` argument like `ExceptionRenderer` does.
This prevents crashes if the actual Exception is passed for the *exc_info* argument instead of a tuple or `True`.
[#482](https://github.com/hynek/structlog/issues/482)
- `FilteringBoundLogger.aexception()` now extracts the exception info using `sys.exc_info()` before passing control to the asyncio executor (where original exception info is no longer available).
[#488](https://github.com/hynek/structlog/issues/488)
## [22.3.0](https://github.com/hynek/structlog/compare/22.2.0...22.3.0) - 2022-11-24
### Changed
- String interpolation in `FilteringBoundLogger` (used by default) is now only attempted if positional arguments are passed.
This prevents crashes if something different than a string is passed for the *event* argument.
[#475](https://github.com/hynek/structlog/pull/475)
### Fixed
- String interpolation doesn't cause crashes in filtered log call anymore.
[#478](https://github.com/hynek/structlog/pull/478)
## [22.2.0](https://github.com/hynek/structlog/compare/22.1.0...22.2.0) - 2022-11-19
### Deprecated
- Accessing package metadata as attributes on the *structlog* module is deprecated (for example, `structlog.__version__`).
Please use [`importlib.metadata`](https://docs.python.org/3.10/library/importlib.metadata.html) instead (for Python 3.7: the [*importlib-metadata*](https://pypi.org/project/importlib-metadata/) PyPI package).
- The `structlog.types` module is now deprecated in favor of the `structlog.typing` module.
It seems like the Python typing community is settling on this name.
### Added
- `FilteringBoundLogger` (used by default) now allows for string interpolation using positional arguments:
```pycon
>>> log.info("Hello %s! The answer is %d.", "World", 42, x=1)
2022-10-07 10:04.31 [info ] Hello World! The answer is 42. x=1
```
[#454](https://github.com/hynek/structlog/pull/454)
- `FilteringBoundLogger` now also has support for *asyncio*-based logging.
Instead of a wrapper class like `structlog.stdlib.AsyncBoundLogger`, async equivalents have been added for all logging methods.
So instead of `log.info("hello")` you can also write `await log.ainfo("hello")` in async functions and methods.
This seems like the better approach and if it's liked by the community, `structlog.stdlib.BoundLogger` will get those methods too.
[#457](https://github.com/hynek/structlog/pull/457)
### Changed
- The documentation has been **heavily** overhauled.
Have a look if you haven't lately!
Especially the graphs in the [standard library chapter](https://www.structlog.org/en/latest/standard-library.html) have proven valuable to many.
- The build backend has been switched to [*Hatch*](https://hatch.pypa.io/).
### Fixed
- The timestamps in the default configuration now use the correct separator (`:`) for seconds.
## [22.1.0](https://github.com/hynek/structlog/compare/21.5.0...22.1.0) - 2022-07-20
### Removed
- Python 3.6 is not supported anymore.
- Pickling is now only possible with protocol version 3 and newer.
### Deprecated
- The entire `structlog.threadlocal` module is deprecated.
Please use the primitives from `structlog.contextvars` instead.
If you're using the modern APIs (`bind_threadlocal()` / `merge_threadlocal()`) it's enough to replace them 1:1 with their `contextvars` counterparts.
The old approach around `wrap_dict()` has been discouraged for a while.
Currently there are no concrete plans to remove the module, but no patches against it will be accepted from now on.
[#409](https://github.com/hynek/structlog/pull/409)
### Added
- `structlog.processors.StackInfoRenderer` now has an *additional_ignores* parameter that allows you to filter out your own logging layer.
[#396](https://github.com/hynek/structlog/issues/396)
- Added `structlog.WriteLogger`, a faster – but more low-level – alternative to `structlog.PrintLogger`.
It works the way `PrintLogger` used to work in previous versions.
[#403](https://github.com/hynek/structlog/pull/403)
[#404](https://github.com/hynek/structlog/pull/404)
- `structlog.make_filtering_bound_logger()`-returned loggers now also have a `log()` method to match the `structlog.stdlib.BoundLogger` signature closer.
[#413](https://github.com/hynek/structlog/pull/413)
- Added structured logging of tracebacks via the `structlog.tracebacks` module,
and most notably the `structlog.tracebacks.ExceptionDictTransformer` which can be used with the new `structlog.processors.ExceptionRenderer` to render JSON tracebacks.
[#407](https://github.com/hynek/structlog/pull/407)
- `structlog.stdlib.recreate_defaults(log_level=logging.NOTSET)` that recreates *structlog*'s defaults on top of standard library's `logging`.
It optionally also configures `logging` to log to standard out at the passed log level.
[#428](https://github.com/hynek/structlog/pull/428)
- `structlog.processors.EventRenamer` allows you to rename the hitherto hard-coded event dict key `event` to something else.
Optionally, you can rename another key to `event` at the same time, too.
So adding `EventRenamer(to="msg", replace_by="_event")` to your processor pipeline will rename the standard `event` key to `msg` and then rename the `_event` key to `event`.
This allows you to use the `event` key in your own log files and to have consistent log message keys across languages.
- `structlog.dev.ConsoleRenderer(event_key="event")` now allows to customize the name of the key that is used for the log message.
### Changed
- `structlog.make_filtering_bound_logger()` now returns a method with the same signature for all log levels, whether they are active or not.
This ensures that invalid calls to inactive log levels are caught immediately and don't explode once the log level changes.
[#401](https://github.com/hynek/structlog/pull/401)
- `structlog.PrintLogger` – that is used by default – now uses `print()` for printing, making it a better citizen for interactive terminal applications.
[#399](https://github.com/hynek/structlog/pull/399)
- `structlog.testing.capture_logs` now works for already initialized bound loggers.
[#408](https://github.com/hynek/structlog/pull/412)
- `structlog.processors.format_exc_info()` is no longer a function, but an instance of `structlog.processors.ExceptionRenderer`.
Its behavior has not changed.
[#407](https://github.com/hynek/structlog/pull/407)
- The default configuration now includes the `structlog.contextvars.merge_contextvars` processor.
That means you can use [`structlog.contextvars`](https://www.structlog.org/en/stable/contextvars.html) features without configuring *structlog*.
### Fixed
- Overloaded the `bind`, `unbind`, `try_unbind` and `new` methods in the `FilteringBoundLogger` [Protocol](https://docs.python.org/3/library/typing.html#typing.Protocol).
This makes it easier to use objects of type `FilteringBoundLogger` in a typed context.
[#392](https://github.com/hynek/structlog/pull/392)
- Monkeypatched `sys.stdout`s are now handled more gracefully by `ConsoleRenderer` (that's used by default).
[#404](https://github.com/hynek/structlog/pull/404)
- `structlog.stdlib.render_to_log_kwargs()` now correctly handles the presence of `exc_info`, `stack_info`, and `stackLevel` in the event dictionary.
They are transformed into proper keyword arguments instead of putting them into the `extra` dictionary.
[#424](https://github.com/hynek/structlog/issues/424),
[#427](https://github.com/hynek/structlog/issues/427)
## [21.5.0](https://github.com/hynek/structlog/compare/21.4.0...21.5.0) - 2021-12-16
### Added
- Added the `structlog.processors.LogfmtRenderer` processor to render log lines using the [*logfmt*](https://brandur.org/logfmt) format.
[#376](https://github.com/hynek/structlog/pull/376)
- Added the `structlog.stdlib.ExtraAdder` processor that adds extra attributes of `logging.LogRecord` objects to the event dictionary.
This processor can be used for adding data passed in the `extra` parameter of the `logging` module's log methods to the event dictionary.
[#209](https://github.com/hynek/structlog/pull/209),
[#377](https://github.com/hynek/structlog/pull/377)
- Added the `structlog.processor.CallsiteParameterAdder` processor that adds parameters of the callsite that an event dictionary originated from to the event dictionary.
This processor can be used to enrich events dictionaries with information such as the function name, line number and filename that an event dictionary originated from.
[#380](https://github.com/hynek/structlog/pull/380)
## [21.4.0](https://github.com/hynek/structlog/compare/21.3.0...21.4.0) - 2021-11-25
### Added
- Added the `structlog.threadlocal.bound_threadlocal` and `structlog.contextvars.bound_contextvars` decorator/context managers to temporarily bind key-value pairs to a thread-local and context-local context.
[#371](https://github.com/hynek/structlog/pull/371)
### Fixed
- Fixed import when running in optimized mode (`PYTHONOPTIMIZE=2` or `python -OO`)
.
[#373](https://github.com/hynek/structlog/pull/373)
## [21.3.0](https://github.com/hynek/structlog/compare/21.2.0...21.3.0) - 2021-11-20
### Added
- `structlog.dev.ConsoleRenderer` now has `sort_keys` boolean parameter that allows to disable the sorting of keys on output.
[#358](https://github.com/hynek/structlog/pull/358)
### Changed
- *structlog* switched its packaging to [*flit*](https://flit.pypa.io/).
Users shouldn't notice a difference, but (re-)packagers might.
- `structlog.stdlib.AsyncBoundLogger` now determines the running loop when logging, not on instantiation.
That has a minor performance impact, but makes it more robust when loops change (for example, `aiohttp.web.run_app()`), or you want to use `sync_bl` *before* a loop has started.
### Fixed
- `structlog.processors.TimeStamper` now works well with [*FreezeGun*](https://github.com/spulec/freezegun) even when it gets applied before the loggers are configured.
[#364](https://github.com/hynek/structlog/pull/364)
- `structlog.stdlib.ProcessorFormatter` now has a *processors* argument that allows to define a processor chain to run over *all* log entries.
Before running the chain, two additional keys are added to the event dictionary: `_record` and `_from_structlog`.
With them it's possible to extract information from `logging.LogRecord`s and differentiate between *structlog* and `logging` log entries while processing them.
The old *processor* (singular) parameter is now deprecated, but no plans exist to remove it.
[#365](https://github.com/hynek/structlog/pull/365)
## [21.2.0](https://github.com/hynek/structlog/compare/21.1.0...21.2.0) - 2021-10-12
### Added
- `structlog.threadlocal.get_threadlocal()` and `structlog.contextvars.get_contextvars()` can now be used to get a copy of the current thread-local/context-local context that has been bound using `structlog.threadlocal.bind_threadlocal()` and `structlog.contextvars.bind_contextvars()`.
[#331](https://github.com/hynek/structlog/pull/331),
[#337](https://github.com/hynek/structlog/pull/337)
- `structlog.threadlocal.get_merged_threadlocal(bl)` and `structlog.contextvars.get_merged_contextvars(bl)` do the same, but also merge the context from a bound logger *bl*.
Same pull requests as previous change.
- `structlog.contextvars.bind_contextvars()` now returns a mapping of keys to `contextvars.Token`s, allowing you to reset values using the new `structlog.contextvars.reset_contextvars()`.
[#339](https://github.com/hynek/structlog/pull/339)
- Exception rendering in `structlog.dev.ConsoleLogger` is now configurable using the `exception_formatter` setting.
If either the [Rich](https://github.com/Textualize/rich) or the [*better-exceptions*](https://github.com/qix-/better-exceptions) package is present, *structlog* will use them for pretty-printing tracebacks.
Rich takes precedence over *better-exceptions* if both are present.
This only works if `format_exc_info` is **absent** in the processor chain.
[#330](https://github.com/hynek/structlog/pull/330),
[#349](https://github.com/hynek/structlog/pull/349)
- The final processor can now return a `bytearray` (additionally to `str` and `bytes`).
[#344](https://github.com/hynek/structlog/issues/344)
### Changed
- To implement pretty exceptions (see Changes below), `structlog.dev.ConsoleRenderer` now formats exceptions itself.
Make sure to remove `format_exc_info` from your processor chain if you configure *structlog* manually.
This change is not really breaking, because the old use-case will keep working as before.
However if you pass `pretty_exceptions=True` (which is the default if either `rich` or `better-exceptions` is installed), a warning will be raised and the exception will be rendered without prettification.
- All use of [Colorama](https://github.com/tartley/colorama) on non-Windows systems has been excised.
Thus, colors are now enabled by default in `structlog.dev.ConsoleRenderer` on non-Windows systems.
You can keep using Colorama to customize colors, of course.
[#345](https://github.com/hynek/structlog/pull/345)
### Fixed
- *structlog* is now importable if `sys.stdout` is `None` (for example, when running using `pythonw`). [#313](https://github.com/hynek/structlog/issues/313)
## [21.1.0](https://github.com/hynek/structlog/compare/20.2.0...21.1.0) - 2021-02-18
### Changed
- `structlog.dev.ConsoleRenderer` will now look for a `logger_name` key if no `logger` key is set.
[#295](https://github.com/hynek/structlog/pull/295)
### Fixed
- `structlog.threadlocal.wrap_dict()` now has a correct type annotation.
[#290](https://github.com/hynek/structlog/pull/290)
- Fix isolation in `structlog.contextvars`.
[#302](https://github.com/hynek/structlog/pull/302)
- The default configuration and loggers are pickleable again.
[#301](https://github.com/hynek/structlog/pull/301)
## [20.2.0](https://github.com/hynek/structlog/compare/20.1.0...20.2.0) - 2020-12-31
### Removed
- Python 2.7 and 3.5 aren't supported anymore.
The package meta data should ensure that you keep getting 20.1.0 on those versions.
[#244](https://github.com/hynek/structlog/pull/244)
### Deprecated
- Accessing the `_context` attribute of a bound logger is now deprecated.
Please use the new `structlog.get_context()`.
### Added
- *structlog* has now type hints for all of its APIs!
Since *structlog* is highly dynamic and configurable, this led to a few concessions like a specialized `structlog.stdlib.get_logger()` whose only difference to `structlog.get_logger()` is that it has the correct type hints.
We consider them provisional for the time being – that means the backwards-compatibility does not apply to them in its full strength until we feel we got it right.
Please feel free to provide feedback!
[#223](https://github.com/hynek/structlog/issues/223),
[#282](https://github.com/hynek/structlog/issues/282)
- Added `structlog.make_filtering_logger` that can be used like `configure(wrapper_class=make_filtering_bound_logger(logging.INFO))`.
It creates a highly optimized bound logger whose inactive methods only consist of a `return None`.
This is now also the default logger.
- As a complement, `structlog.stdlib.add_log_level()` can now additionally be imported as `structlog.processors.add_log_level` since it just adds the method name to the event dict.
- Added `structlog.BytesLogger` to avoid unnecessary encoding round trips.
Concretely this is useful with *orjson* which returns bytes.
[#271](https://github.com/hynek/structlog/issues/271)
- The final processor now also may return bytes that are passed untouched to the wrapped logger.
- `structlog.get_context()` allows you to retrieve the original context of a bound logger. [#266](https://github.com/hynek/structlog/issues/266),
- Added `structlog.testing.CapturingLogger` for more unit testing goodness.
- Added `structlog.stdlib.AsyncBoundLogger` that executes logging calls in a thread executor and therefore doesn't block.
[#245](https://github.com/hynek/structlog/pull/245)
### Changed
- The default bound logger (`wrapper_class`) if you don't configure *structlog* has changed.
It's mostly compatible with the old one but a few uncommon methods like `log`, `failure`, or `err` don't exist anymore.
You can regain the old behavior by using `structlog.configure(wrapper_class=structlog.BoundLogger)`.
Please note that due to the various interactions between settings, it's possible that you encounter even more errors.
We **strongly** urge you to always configure all possible settings since the default configuration is *not* covered by our backwards-compatibility policy.
- `structlog.processors.add_log_level()` is now part of the default configuration.
- `structlog.stdlib.ProcessorFormatter` no longer uses exceptions for control flow, allowing `foreign_pre_chain` processors to use `sys.exc_info()` to access the real exception.
### Fixed
- `structlog.PrintLogger` now supports `copy.deepcopy()`.
[#268](https://github.com/hynek/structlog/issues/268)
## [20.1.0](https://github.com/hynek/structlog/compare/19.2.0...20.1.0) - 2020-01-28
### Deprecated
- This is the last version to support Python 2.7 (including PyPy) and 3.5.
All following versions will only support Python 3.6 or later.
### Added
- Added a new module `structlog.contextvars` that allows to have a global but context-local *structlog* context the same way as with `structlog.threadlocal` since 19.2.0.
[#201](https://github.com/hynek/structlog/issues/201),
[#236](https://github.com/hynek/structlog/pull/236)
- Added a new module `structlog.testing` for first class testing support.
The first entry is the context manager `capture_logs()` that allows to make assertions about structured log calls.
[#14](https://github.com/hynek/structlog/issues/14),
[#234](https://github.com/hynek/structlog/pull/234)
- Added `structlog.threadlocal.unbind_threadlocal()`.
[#239](https://github.com/hynek/structlog/pull/239)
### Fixed
- The logger created by `structlog.get_logger()` is not detected as an abstract method anymore, when attached to an abstract base class.
[#229](https://github.com/hynek/structlog/issues/229)
- Colorama isn't initialized lazily on Windows anymore because it breaks rendering.
[#232](https://github.com/hynek/structlog/issues/232),
[#242](https://github.com/hynek/structlog/pull/242)
## [19.2.0](https://github.com/hynek/structlog/compare/19.1.0...19.2.0) - 2019-10-16
### Removed
- Python 3.4 is not supported anymore.
It has been unsupported by the Python core team for a while now and its PyPI downloads are negligible.
It's very unlikely that *structlog* will break under 3.4 anytime soon, but we don't test it anymore.
### Added
- Full Python 3.8 support for `structlog.stdlib`.
- Added more pass-through properties to `structlog.stdlib.BoundLogger`. To makes it easier to use it as a drop-in replacement for `logging.Logger`.
[#198](https://github.com/hynek/structlog/issues/198)
- Added new processor `structlog.dev.set_exc_info()` that will set `exc_info=True` if the method's name is `exception` and `exc_info` isn't set at all.
*This is only necessary when the standard library integration is not used*.
It fixes the problem that in the default configuration, `structlog.get_logger().exception("hi")` in an `except` block would not print the exception without passing `exc_info=True` to it explicitly.
[#130](https://github.com/hynek/structlog/issues/130),
[#173](https://github.com/hynek/structlog/issues/173),
[#200](https://github.com/hynek/structlog/issues/200),
[#204](https://github.com/hynek/structlog/issues/204)
- Added a new thread-local API that allows binding values to a thread-local context explicitly without affecting the default behavior of `bind()`.
[#222](https://github.com/hynek/structlog/issues/222),
[#225](https://github.com/hynek/structlog/issues/225)
- Added *pass_foreign_args* argument to `structlog.stdlib.ProcessorFormatter`. It allows to pass a foreign log record's *args* attribute to the event dictionary under the `positional_args` key.
[#228](https://github.com/hynek/structlog/issues/228)
### Changed
- `structlog.stdlib.ProcessorFormatter` now takes a logger object as an optional keyword argument.
This makes `ProcessorFormatter` work properly with `stuctlog.stdlib.filter_by_level()`.
[#219](https://github.com/hynek/structlog/issues/219)
- `structlog.dev.ConsoleRenderer` now calls `str()` on the event value. [#221](https://github.com/hynek/structlog/issues/221)
### Fixed
- `structlog.dev.ConsoleRenderer` now uses no colors by default, if Colorama is not available.
[#215](https://github.com/hynek/structlog/issues/215)
- `structlog.dev.ConsoleRenderer` now initializes Colorama lazily, to prevent accidental side-effects just by importing *structlog*.
[#210](https://github.com/hynek/structlog/issues/210)
- A best effort has been made to make as much of *structlog* pickleable as possible to make it friendlier with `multiprocessing` and similar libraries.
Some classes can only be pickled on Python 3 or using the [dill](https://pypi.org/project/dill/) library though and that is very unlikely to change.
So far, the configuration proxy, `structlog.processor.TimeStamper`, `structlog.BoundLogger`, `structlog.PrintLogger` and `structlog.dev.ConsoleRenderer` have been made pickleable.
Please report if you need any another class fixed.
[#126](https://github.com/hynek/structlog/issues/126)
## [19.1.0](https://github.com/hynek/structlog/compare/18.2.0...19.1.0) - 2019-02-02
### Added
- `structlog.ReturnLogger` and `structlog.PrintLogger` now have a `fatal()` log method.
[#181](https://github.com/hynek/structlog/issues/181)
### Changed
- As announced in 18.1.0, `pip install -e .[dev]` now installs all development dependencies.
Sorry for the inconveniences this undoubtedly will cause!
- *structlog* now tolerates passing through `dict`s to stdlib logging.
[#187](https://github.com/hynek/structlog/issues/187),
[#188](https://github.com/hynek/structlog/pull/188),
[#189](https://github.com/hynek/structlog/pull/189)
### Fixed
- Under certain (rather unclear) circumstances, the frame extraction could throw an `SystemError: error return without exception set`.
A workaround has been added.
[#174](https://github.com/hynek/structlog/issues/174)
## [18.2.0](https://github.com/hynek/structlog/compare/18.1.0...18.2.0) - 2018-09-05
### Added
- Added `structlog.stdlib.add_log_level_number()` processor that adds the level *number* to the event dictionary.
Can be used to simplify log filtering.
[#151](https://github.com/hynek/structlog/pull/151)
- `structlog.processors.JSONRenderer` now allows for overwriting the *default* argument of its serializer.
[#77](https://github.com/hynek/structlog/pull/77),
[#163](https://github.com/hynek/structlog/pull/163)
- Added `try_unbind()` that works like `unbind()` but doesn't raise a `KeyError` if one of the keys is missing.
[#171](https://github.com/hynek/structlog/pull/171)
## [18.1.0](https://github.com/hynek/structlog/compare/17.2.0...18.1.0) - 2018-01-27
### Deprecated
- The meaning of the `structlog[dev]` installation target will change from "colorful output" to "dependencies to develop *structlog*" in 19.1.0.
The main reason behind this decision is that it's impossible to have a *structlog* in your normal dependencies and additionally a `structlog[dev]` for development (`pip` will report an error).
### Added
- `structlog.dev.ConsoleRenderer` now accepts a *force_colors* argument to output colored logs even if the destination is not a tty.
Use this option if your logs are stored in files that are intended to be streamed to the console.
- `structlog.dev.ConsoleRenderer` now accepts a *level_styles* argument for overriding the colors for individual levels, as well as to add new levels.
See the docs for `ConsoleRenderer.get_default_level_styles()` for usage.
[#139](https://github.com/hynek/structlog/pull/139)
- Added `structlog.is_configured()` to check whether or not *structlog* has been configured.
- Added `structlog.get_config()` to introspect current configuration.
### Changed
- Empty strings are valid events now.
[#110](https://github.com/hynek/structlog/issues/110)
- `structlog.stdlib.BoundLogger.exception()` now uses the `exc_info` argument if it has been passed instead of setting it unconditionally to `True`. [#149](https://github.com/hynek/structlog/pull/149)
- Default configuration now uses plain `dict`s on Python 3.6+ and PyPy since they are ordered by default.
### Fixed
- Do not encapsulate Twisted failures twice with newer versions of Twisted.
[#144](https://github.com/hynek/structlog/issues/144)
## [17.2.0](https://github.com/hynek/structlog/compare/17.1.0...17.2.0) - 2017-05-15
### Added
- `structlog.stdlib.ProcessorFormatter` now accepts *keep_exc_info* and *keep_stack_info* arguments to control what to do with this information on log records.
Most likely you want them both to be `False` therefore it's the default.
[#109](https://github.com/hynek/structlog/issues/109)
### Fixed
- `structlog.stdlib.add_logger_name()` now works in `structlog.stdlib.ProcessorFormatter`'s `foreign_pre_chain`.
[#112](https://github.com/hynek/structlog/issues/112)
- Clear log record args in `structlog.stdlib.ProcessorFormatter` after rendering.
This fix is for you if you tried to use it and got `TypeError: not all arguments converted during string formatting` exceptions.
[#116](https://github.com/hynek/structlog/issues/116),
[#117](https://github.com/hynek/structlog/issues/117)
## [17.1.0](https://github.com/hynek/structlog/compare/16.1.0...17.1.0) - 2017-04-24
The main features of this release are massive improvements in standard library's `logging` integration.
Have a look at the updated [standard library chapter](https://www.structlog.org/en/stable/standard-library.html) on how to use them!
Special thanks go to [Fabian Büchler](https://github.com/fabianbuechler), [Gilbert Gilb's](https://github.com/gilbsgilbs), [Iva Kaneva](https://github.com/if-fi), [insolite](https://github.com/insolite), and [sky-code](https://github.com/sky-code), that made them possible.
### Added
- Added `structlog.stdlib.render_to_log_kwargs()`.
This allows you to use `logging`-based formatters to take care of rendering your entries.
[#98](https://github.com/hynek/structlog/issues/98)
- Added `structlog.stdlib.ProcessorFormatter` which does the opposite: This allows you to run *structlog* processors on arbitrary `logging.LogRecords`.
[#79](https://github.com/hynek/structlog/issues/79),
[#105](https://github.com/hynek/structlog/issues/105)
- Added *repr_native_str* to `structlog.processors.KeyValueRenderer` and `structlog.dev.ConsoleRenderer`.
This allows for human-readable non-ASCII output on Python 2 (`repr()` on Python 2 behaves like `ascii()` on Python 3 in that regard).
As per compatibility policy, it's on (original behavior) in `KeyValueRenderer` and off (human-friendly behavior) in `ConsoleRenderer`.
[#94](https://github.com/hynek/structlog/issues/94)
- Added *colors* argument to `structlog.dev.ConsoleRenderer` and made it the default renderer.
[#78](https://github.com/hynek/structlog/pull/78)
### Changed
- The default renderer now is `structlog.dev.ConsoleRenderer` if you don't configure *structlog*.
Colors are used if available and human-friendly timestamps are prepended.
This is in line with our backwards-compatibility policy that explicitly excludes default settings.
- UNIX epoch timestamps from `structlog.processors.TimeStamper` are more precise now.
- Positional arguments are now removed even if they are empty.
[#82](https://github.com/hynek/structlog/pull/82)
## Fixed
- Fixed bug with Python 3 and `structlog.stdlib.BoundLogger.log()`.
Error log level was not reproducible and was logged as exception one time out of two.
[#92](https://github.com/hynek/structlog/pull/92)
## [16.1.0](https://github.com/hynek/structlog/compare/16.0.0...16.1.0) - 2016-05-24
### Removed
- Python 3.3 and 2.6 aren't supported anymore.
They may work by chance but any effort to keep them working has ceased.
The last Python 2.6 release was on October 29, 2013 and isn't supported by the CPython core team anymore.
Major Python packages like Django and Twisted dropped Python 2.6 a while ago already.
Python 3.3 never had a significant user base and wasn't part of any distribution's LTS release.
### Added
- Added a `drop_missing` argument to `KeyValueRenderer`.
If `key_order` is used and a key is missing a value, it's not rendered at all instead of being rendered as `None`.
[#67](https://github.com/hynek/structlog/pull/67)
### Fixed
- Exceptions without a `__traceback__` are now also rendered on Python 3.
- Don't cache loggers in lazy proxies returned from `get_logger()`.
This lead to in-place mutation of them if used before configuration which in turn lead to the problem that configuration was applied only partially to them later.
[#72](https://github.com/hynek/structlog/pull/72)
## [16.0.0](https://github.com/hynek/structlog/compare/15.3.0...16.0.0) - 2016-01-28
### Added
- Added `structlog.dev.ConsoleRenderer` that renders the event dictionary aligned and with colors.
- Added `structlog.processors.UnicodeDecoder` that will decode all byte string values in an event dictionary to Unicode.
- Added `serializer` parameter to `structlog.processors.JSONRenderer` which allows for using different (possibly faster) JSON encoders than the standard library.
### Changed
- `structlog.processors.ExceptionPrettyPrinter` and `structlog.processors.format_exc_info` now support passing of Exceptions on Python 3.
- [*six*](https://six.readthedocs.io/) is now used for compatibility.
### Fixed
- The context is now cleaned up when exiting `structlog.threadlocal.tmp_bind` in case of exceptions.
[#64](https://github.com/hynek/structlog/issues/64)
- Be more more lenient about missing `__name__`s.
[#62](https://github.com/hynek/structlog/pull/62)
## [15.3.0](https://github.com/hynek/structlog/compare/15.2.0...15.3.0) - 2015-09-25
### Added
- Officially support Python 3.5.
- Added `structlog.ReturnLogger.failure` and `structlog.PrintLogger.failure` as preparation for the new Twisted logging system.
### Fixed
- Tolerate frames without a `__name__`, better.
[#58](https://github.com/hynek/structlog/pull/58)
## [15.2.0](https://github.com/hynek/structlog/compare/15.1.0...15.2.0) - 2015-06-10
### Added
- Added option to specify target key in `structlog.processors.TimeStamper` processor.
[#51](https://github.com/hynek/structlog/pull/51)
### Changed
- Allow empty lists of processors.
This is a valid use case since [#26](https://github.com/hynek/structlog/issues/26) has been merged.
Before, supplying an empty list resulted in the defaults being used.
- Better support of `logging.Logger.exception` within *structlog*.
[#52](https://github.com/hynek/structlog/pull/52)
### Fixed
- Prevent Twisted's `log.err` from quoting strings rendered by `structlog.twisted.JSONRenderer`.
## [15.1.0](https://github.com/hynek/structlog/compare/15.0.0...15.1.0) - 2015-02-24
### Fixed
- Tolerate frames without a `__name__` when guessing callsite names.
## [15.0.0](https://github.com/hynek/structlog/compare/0.4.2...15.0.0) - 2015-01-23
### Added
- Added `structlog.stdlib.add_log_level` and `structlog.stdlib.add_logger_name` processors.
[#44](https://github.com/hynek/structlog/pull/44)
- Added `structlog.stdlib.BoundLogger.log`.
[#42](https://github.com/hynek/structlog/pull/42)
- Added `structlog.stdlib.BoundLogger.exception`.
[#22](https://github.com/hynek/structlog/pull/22)
### Changed
- Pass positional arguments to stdlib wrapped loggers that use string formatting.
[#19](https://github.com/hynek/structlog/pull/19)
- *structlog* is now dually licensed under the [Apache License, Version 2](https://choosealicense.com/licenses/apache/) and the [MIT](https://choosealicense.com/licenses/mit/) license.
Therefore it is now legal to use *structlog* with [GPLv2](https://choosealicense.com/licenses/gpl-2.0/)-licensed projects.
[#28](https://github.com/hynek/structlog/pull/28)
## [0.4.2](https://github.com/hynek/structlog/compare/0.4.1...0.4.2) - 2014-07-26
### Removed
- Drop support for Python 3.2.
There is no justification to add complexity for a Python version that nobody uses.
If you are one of the [0.350%](https://alexgaynor.net/2014/jan/03/pypi-download-statistics/) that use Python 3.2, please stick to the 0.4 branch; critical bugs will still be fixed.
### Added
- Officially support Python 3.4.
- Allow final processor to return a dictionary.
See the adapting chapter.
[#26](https://github.com/hynek/structlog/issues/26)
- Test Twisted-related code on Python 3 (with some caveats).
### Fixed
- Fixed a memory leak in greenlet code that emulates thread locals.
It shouldn't matter in practice unless you use multiple wrapped dicts within one program that is rather unlikely.
[#8](https://github.com/hynek/structlog/pull/8)
- `structlog.PrintLogger` now is thread-safe.
- `from structlog import *` works now (but you still shouldn't use it).
## [0.4.1](https://github.com/hynek/structlog/compare/0.4.0...0.4.1) - 2013-12-19
### Changed
- Don't cache proxied methods in `structlog.threadlocal._ThreadLocalDictWrapper`. This doesn't affect regular users.
### Fixed
- Various doc fixes.
## [0.4.0](https://github.com/hynek/structlog/compare/0.3.2...0.4.0) - 2013-11-10
### Added
- Added `structlog.processors.StackInfoRenderer` for adding stack information to log entries without involving exceptions.
Also added it to default processor chain.
[#6](https://github.com/hynek/structlog/pull/6)
- Allow optional positional arguments for `structlog.get_logger` that are passed to logger factories.
The standard library factory uses this for explicit logger naming.
[#12](https://github.com/hynek/structlog/pull/12)
- Add `structlog.processors.ExceptionPrettyPrinter` for development and testing when multiline log entries aren't just acceptable but even helpful.
- Allow the standard library name guesser to ignore certain frame names.
This is useful together with frameworks.
- Add meta data (for example, function names, line numbers) extraction for wrapped stdlib loggers. [#5](https://github.com/hynek/structlog/pull/5)
## [0.3.2](https://github.com/hynek/structlog/compare/0.3.1...0.3.2) - 2013-09-27
### Fixed
- Fix stdlib's name guessing.
## [0.3.1](https://github.com/hynek/structlog/compare/0.3.0...0.3.1) - 2013-09-26
### Fixed
- Added forgotten `structlog.processors.TimeStamper` to API documentation.
## [0.3.0](https://github.com/hynek/structlog/compare/0.2.0...0.3.0) - 2013-09-23
### Changes:
- Greatly enhanced and polished the documentation and added a new theme based on Write The Docs, requests, and Flask.
- Add Python Standard Library-specific BoundLogger that has an explicit API instead of intercepting unknown method calls.
See `structlog.stdlib.BoundLogger`.
- `structlog.ReturnLogger` now allows arbitrary positional and keyword arguments.
- Add Twisted-specific BoundLogger that has an explicit API instead of intercepting unknown method calls.
See `structlog.twisted.BoundLogger`.
- Allow logger proxies that are returned by `structlog.get_logger` and `structlog.wrap_logger` to cache the BoundLogger they assemble according to configuration on first use.
See the chapter on performance and the `cache_logger_on_first_use` argument of `structlog.configure` and `structlog.wrap_logger`.
- Extract a common base class for loggers that does nothing except keeping the context state.
This makes writing custom loggers much easier and more straight-forward. See `structlog.BoundLoggerBase`.
## [0.2.0](https://github.com/hynek/structlog/compare/0.1.0...0.2.0) - 2013-09-17
### Added
- Add `key_order` option to `structlog.processors.KeyValueRenderer` for more predictable log entries with any `dict` class.
- Enhance Twisted support by offering JSONification of non-structlog log entries.
- Allow for custom serialization in `structlog.twisted.JSONRenderer` without abusing `__repr__`.
### Changed
- Promote to stable, thus henceforth a strict backwards-compatibility policy is put into effect.
- `structlog.PrintLogger` now uses proper I/O routines and is thus viable not only for examples but also for production.
## [0.1.0](https://github.com/hynek/structlog/tree/0.1.0) - 2013-09-16
Initial release.
structlog-25.1.0/COPYRIGHT 0000644 0000000 0000000 00000000627 14742155321 012057 0 ustar 00 Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or )
- or MIT license (LICENSE-MIT or )
at your option.
Any contribution intentionally submitted for inclusion in the work by you, as
defined in the Apache-2.0 license, shall be dual-licensed as above, without any
additional terms or conditions.
structlog-25.1.0/README.md 0000644 0000000 0000000 00000015422 14742155321 012042 0 ustar 00 # *structlog*: Structured Logging for Python
Simple. Powerful. Fast. Pick three.
*structlog* is *the* production-ready logging solution for Python:
- **Simple**: Everything is about **functions** that take and return **dictionaries** – all hidden behind **familiar APIs**.
- **Powerful**: Functions and dictionaries aren’t just simple but also powerful.
*structlog* leaves *you* in control.
- **Fast**: *structlog* is not hamstrung by designs of yore.
Its flexibility comes not at the price of performance.
Thanks to its flexible design, *you* choose whether you want *structlog* to take care of the **output** of your log entries or whether you prefer to **forward** them to an existing logging system like the standard library's `logging` module.
The output format is just as flexible and *structlog* comes with support for JSON, [*logfmt*](https://brandur.org/logfmt), as well as pretty console output out-of-the-box:
[](https://github.com/hynek/structlog/blob/main/docs/_static/console_renderer.png?raw=true)
## Sponsors
*structlog* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek).
Especially those generously supporting us at the *The Organization* tier and higher:
Please consider joining them to help make structlog’s maintenance more sustainable!
## Introduction
*structlog* has been successfully used in production at every scale since **2013**, while embracing cutting-edge technologies like *asyncio*, context variables, or type hints as they emerged.
Its paradigms proved influential enough to [help design](https://twitter.com/sirupsen/status/638330548361019392) structured logging [packages across ecosystems](https://github.com/sirupsen/logrus).
A short explanation on *why* structured logging is good for you, and why *structlog* is the right tool for the job can be found in the [Why chapter](https://www.structlog.org/en/stable/why.html) of our documentation.
Once you feel inspired to try it out, check out our friendly [Getting Started tutorial](https://www.structlog.org/en/stable/getting-started.html).
For a fully-fledged zero-to-hero tutorial, check out [*A Comprehensive Guide to Python Logging with structlog*](https://betterstack.com/community/guides/logging/structlog/).
If you prefer videos over reading, check out [Markus Holtermann](https://chaos.social/@markush)'s talk *Logging Rethought 2: The Actions of Frank Taylor Jr.*:
## Credits
*structlog* is written and maintained by [Hynek Schlawack](https://hynek.me/).
The idea of bound loggers is inspired by previous work by [Jean-Paul Calderone](https://github.com/exarkun) and [David Reid](https://github.com/dreid).
The development is kindly supported by my employer [Variomedia AG](https://www.variomedia.de/), *structlog*’s [Tidelift subscribers](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek), and all my amazing [GitHub Sponsors](https://github.com/sponsors/hynek).
The logs-loving beaver logo has been contributed by [Lynn Root](https://www.roguelynn.com).
## Project Links
- [**Get Help**](https://stackoverflow.com/questions/tagged/structlog) (use the *structlog* tag on Stack Overflow)
- [**PyPI**](https://pypi.org/project/structlog/)
- [**GitHub**](https://github.com/hynek/structlog)
- [**Documentation**](https://www.structlog.org/)
- [**Changelog**](https://github.com/hynek/structlog/tree/main/CHANGELOG.md)
- [**Third-party Extensions**](https://github.com/hynek/structlog/wiki/Third-party-Extensions)
## *structlog* for Enterprise
Available as part of the [Tidelift Subscription](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek).
The maintainers of *structlog* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications.
Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
structlog-25.1.0/show_off.py 0000644 0000000 0000000 00000001562 14742155321 012747 0 ustar 00 """
Show how console logging looks like.
This is used for the screenshot in the readme and
.
"""
from dataclasses import dataclass
import structlog
@dataclass
class SomeClass:
x: int
y: str
structlog.stdlib.recreate_defaults() # so we have logger names
log = structlog.get_logger("some_logger")
log.debug("debugging is hard", a_list=[1, 2, 3])
log.info("informative!", some_key="some_value")
log.warning("uh-uh!")
log.error("omg", a_dict={"a": 42, "b": "foo"})
log.critical("wtf", what=SomeClass(x=1, y="z"))
log2 = structlog.get_logger("another_logger")
def make_call_stack_more_impressive():
try:
d = {"x": 42}
print(SomeClass(d["y"], "foo"))
except Exception:
log2.exception("poor me")
log.info("all better now!", stack_info=True)
make_call_stack_more_impressive()
structlog-25.1.0/tox.ini 0000644 0000000 0000000 00000005563 14742155321 012103 0 ustar 00 [tox]
min_version = 4
env_list =
pre-commit,
mypy-pkg,
py3{8,9,10,11,12,13}-{tests,mypy}
py3{8,13}-tests-{colorama,be,rich},
docs-{sponsors,doctests},
coverage-report
[testenv]
package = wheel
wheel_build_env = .pkg
extras =
tests: tests
mypy: typing
commands =
tests: pytest {posargs}
mypy: mypy tests/typing
# Run oldest and latest under Coverage.
# Keep in-sync with coverage `depends below.
[testenv:py3{8,13}-tests{,-colorama,-be,-rich}]
deps =
coverage[toml]
py313: twisted
colorama: colorama
rich: rich
be: better-exceptions
commands = coverage run -m pytest {posargs}
[testenv:coverage-report]
deps = coverage[toml]
skip_install = true
parallel_show_output = true
# Keep in-sync with test env definition above.
depends = py3{8,13}-{tests,colorama,be,rich}
commands =
coverage combine
coverage report
[testenv:docs-{build,doctests,linkcheck}]
# Keep base_python in sync with ci.yml/docs and .readthedocs.yaml.
base_python = py313
extras = docs
commands =
build: sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs {posargs:docs/_build/}html
doctests: sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs {posargs:docs/_build/}html
linkcheck: sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/html
[testenv:docs-watch]
package = editable
base_python = {[testenv:docs-build]base_python}
extras = {[testenv:docs-build]extras}
deps = watchfiles
commands =
watchfiles \
--ignore-paths docs/_build/ \
'sphinx-build -W -n --jobs auto -b html -d {envtmpdir}/doctrees docs docs/_build/html' \
src \
docs
[testenv:docs-sponsors]
description = Ensure sponsor logos are up to date.
deps = cogapp
commands = cog -rP README.md docs/index.md
[testenv:pre-commit]
skip_install = true
deps = pre-commit
commands = pre-commit run --all-files
[testenv:mypy-pkg]
extras = typing
commands = mypy src
[testenv:pyright]
deps = pyright
extras = typing
commands = pyright tests/typing
[testenv:color-force]
help = A visual check that FORCE_COLOR is working.
set_env = FORCE_COLOR=1
commands = python -c "import structlog; structlog.get_logger().warning('should be colorful')"
[testenv:color-no]
help = A visual check that NO_COLOR is working.
set_env = NO_COLOR=1
commands = python -c "import structlog; structlog.get_logger().warning('should be plain')"
[testenv:docset]
deps = doc2dash
extras = docs
allowlist_externals =
rm
cp
tar
commands =
rm -rf structlog.docset structlog.tgz docs/_build
sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
doc2dash --index-page index.html --icon docs/_static/docset-icon.png --online-redirect-url https://www.structlog.org/en/latest/ docs/_build/html
cp docs/_static/docset-icon@2x.png structlog.docset/icon@2x.png
tar --exclude='.DS_Store' -cvzf structlog.tgz structlog.docset
structlog-25.1.0/.github/CODE_OF_CONDUCT.md 0000644 0000000 0000000 00000012552 14742155321 014723 0 ustar 00
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
structlog-25.1.0/.github/CONTRIBUTING.md 0000644 0000000 0000000 00000021544 14742155321 014356 0 ustar 00 # How To Contribute
> [!IMPORTANT]
> This document is mainly to help you to get started by codifying tribal knowledge and expectations and make it more accessible to everyone.
> But don't be afraid to open half-finished PRs and ask questions if something is unclear!
## Support
In case you'd like to help out but don't want to deal with GitHub, there's a great opportunity:
help your fellow developers on [Stack Overflow](https://stackoverflow.com/questions/tagged/structlog)!
The official tag is `structlog` and helping out in support frees us up to improve *structlog* instead!
## Workflow
First off, thank you for considering to contribute!
It's people like *you* who make this project such a great tool for everyone.
- No contribution is too small!
Please submit as many fixes for typos and grammar bloopers as you can!
- Try to limit each pull request to *one* change only.
- Since we squash on merge, it's up to you how you handle updates to the `main` branch.
Whether you prefer to rebase on `main` or merge `main` into your branch, do whatever is more comfortable for you.
Just remember to [not use your own `main` branch for the pull request](https://hynek.me/articles/pull-requests-branch/).
- *Always* add tests and docs for your code.
This is a hard rule; patches with missing tests or documentation won't be merged.
- Consider updating [`CHANGELOG.md`](../CHANGELOG.md) to reflect the changes as observed by people *using* this library.
- Make sure your changes pass our [CI](https://github.com/hynek/structlog/actions).
You won't get any feedback until it's green unless you ask for it.
For the CI to pass, the coverage must be 100%.
If you have problems to test something, open anyway and ask for advice.
In some situations, we may agree to add an `# pragma: no cover`.
- Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done.
- Don't break [backwards-compatibility](SECURITY.md).
## Local development environment
First, **fork** the repository on GitHub and **clone** it using one of the alternatives that you can copy-paste by pressing the big green button labeled `<> Code`.
You can (and should) run our test suite using [*tox*](https://tox.wiki/).
However, you'll probably want a more traditional environment as well.
We recommend using the Python version from the `.python-version-default` file in the project's root directory, because that's the one that is used in the CI by default, too.
If you're using [*direnv*](https://direnv.net), you can automate the creation of the project virtual environment with the correct Python version by adding the following `.envrc` to the project root:
```bash
layout python python$(cat .python-version-default)
```
or, if you like [*uv*](https://github.com/astral-sh/uv):
```bash
test -d .venv || uv venv --python python$(cat .python-version-default)
. .venv/bin/activate
```
> [!WARNING]
> - **Before** you start working on a new pull request, use the "*Sync fork*" button in GitHub's web UI to ensure your fork is up to date.
> - **Always create a new branch off `main` for each new pull request.**
> Yes, you can work on `main` in your fork and submit pull requests.
> But this will *inevitably* lead to you not being able to synchronize your fork with upstream and having to start over.
Change into the newly created directory and after activating a virtual environment, install an editable version of this project along with its tests requirements:
```console
$ pip install -e .[dev] # or `uv pip install -e .[dev]`
```
Now you can run the test suite:
```console
$ python -Im pytest
```
When working on the documentation, use:
```console
$ tox run -e docs-watch
```
This will build the documentation, watch for changes, and rebuild it whenever you save a file.
To just build the documentation and exit immediately use:
```console
$ tox run -e docs-build
```
You will find the built documentation in `docs/_build/html`.
To run doctests:
```console
$ tox run -e docs-doctests
```
## Code
- Obey [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/).
We use the `"""`-on-separate-lines style for docstrings with [Napoleon](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html)-style API documentation:
```python
def func(x: str, y: int) -> str:
"""
Do something.
Args:
x: A very important argument.
y:
Another very important argument, but its description is so long
that it doesn't fit on one line. So, we start the whole block on a
fresh new line to keep the block together.
Returns:
The result of doing something.
"""
```
Please note that the API docstrings are still reStructuredText.
- If you add or change public APIs, tag the docstring using `.. versionadded:: 24.1.0 WHAT` or `.. versionchanged:: 24.1.0 WHAT`.
We follow CalVer, so the next version will be the current with with the middle number incremented (for example, `24.1.0` -> `24.2.0`).
- We use [Ruff](https://ruff.rs/) to sort our imports and format our code with a line length of 79 characters.
As long as you run our full *tox* suite before committing, or install our [*pre-commit*](https://pre-commit.com/) hooks (ideally you'll do both -- see [*Local Development Environment*](#local-development-environment) above), you won't have to spend any time on formatting your code at all.
If you don't, CI will catch it for you -- but that seems like a waste of your time!
## Tests
- Write your asserts as `expected == actual` to line them up nicely, and leave an empty line before them:
```python
x = f()
assert 42 == x.some_attribute
assert "foo" == x._a_private_attribute
```
- You can run the test suite runs with all (optional) dependencies against all supported Python versions -- just as it will in our CI -- by running `tox`.
- Write [good test docstrings](https://jml.io/test-docstrings/).
## Documentation
- Use [semantic newlines] in [reStructuredText](https://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html) (`*.rst`) and [Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) (`*.md`) files:
```markdown
This is a sentence.
This is another sentence.
This is a new paragraph.
```
- If you start a new section, add two blank lines before and one blank line after the header except if two headers follow immediately after each other:
```markdown
# Main Header
Last line of previous section.
## Header of New Top Section
### Header of New Section
First line of new section.
```
### Changelog
If your change is interesting to end-users, there needs to be an entry in our `CHANGELOG.md`, so they can learn about it.
- The changelog follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) standard.
Add the best-fitting section if it's missing for the current release.
We use the following order: `Security`, `Removed`, `Deprecated`, `Added`, `Changed`, `Fixed`.
- As with other docs, please use [semantic newlines] in the changelog.
- Make the last line a link to your pull request.
You probably have to open it first to know the number.
- Leave an empty line between entries, so it doesn't look like a wall of text.
- Refer to all symbols by their fully-qualified names.
For example, `structlog.Foo` -- not just `Foo`.
- Wrap symbols like modules, functions, or classes into backticks, so they are rendered in a `monospace font`.
- Wrap arguments into asterisks so they are *italicized* like in API documentation:
`Added new argument *an_argument*.`
- If you mention functions or methods, add parentheses at the end of their names:
`structlog.func()` or `structlog.Class.method()`.
This makes the changelog a lot more readable.
- Prefer simple past tense or constructions with "now".
In the `Added` section, you can leave out the "Added" prefix:
```markdown
### Added
- `structlog.func()` that does foo.
It's pretty cool.
[#1](https://github.com/hynek/structlog/pull/1)
### Fixed
- `structlog.func()` now doesn't crash the Large Hadron Collider anymore.
That was a nasty bug!
[#2](https://github.com/hynek/structlog/pull/2)
```
## See you on GitHub!
Again, this whole file is mainly to help you to get started by codifying tribal knowledge and expectations to save you time and turnarounds.
It is **not** meant to be a barrier to entry, so don't be afraid to open half-finished PRs and ask questions if something is unclear!
Please note that this project is released with a Contributor [Code of Conduct](CODE_OF_CONDUCT.md).
By participating in this project you agree to abide by its terms.
Please report any harm to [Hynek Schlawack](https://hynek.me/about/) in any way you find appropriate.
[semantic newlines]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/
structlog-25.1.0/.github/FUNDING.yml 0000644 0000000 0000000 00000000053 14742155321 013732 0 ustar 00 ---
github: hynek
tidelift: pypi/structlog
structlog-25.1.0/.github/PULL_REQUEST_TEMPLATE.md 0000644 0000000 0000000 00000004645 14742155321 015731 0 ustar 00 # Summary
# Pull Request Check List
- [ ] Do **not** open pull requests from your `main` branch – **use a separate branch**!
- There's a ton of footguns waiting if you don't heed this warning. You can still go back to your project, create a branch from your main branch, push it, and open the pull request from the new branch.
- This is not a pre-requisite for your pull request to be accepted, but **you have been warned**.
- [ ] Added **tests** for changed code.
- The CI fails with less than 100% coverage.
- [ ] **New APIs** are added to our typing tests in [`api.py`](https://github.com/hynek/structlog/blob/main/tests/typing/api.py).
- [ ] Updated **documentation** for changed code.
- [ ] New functions/classes have to be added to `docs/api.rst` by hand.
- [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded).
- The next version is the second number in the current release + 1. The first number represents the current year. So if the current version on PyPI is 23.1.0, the next version is gonna be 23.2.0. If the next version is the first in the new year, it'll be 24.1.0.
- [ ] Documentation in `.rst` and `.md` files is written using [**semantic newlines**](https://rhodesmill.org/brandon/2012/one-sentence-per-line/).
- [ ] Changes (and possible deprecations) are documented in the [**changelog**](https://github.com/hynek/structlog/blob/main/CHANGELOG.md).
- [ ] Consider granting [push permissions to the PR branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork), so maintainers can fix minor issues themselves without pestering you.
structlog-25.1.0/.github/SECURITY.md 0000644 0000000 0000000 00000001631 14742155321 013711 0 ustar 00 # Security Policy
## Supported versions
We are following [Calendar Versioning](https://calver.org) with generous backwards-compatibility guarantees.
Therefore we only support the latest version.
Put simply, you shouldn't ever be afraid to upgrade as long as you're only using our public APIs.
Whenever there is a need to break compatibility, it is announced in the changelog, and raises a `DeprecationWarning` for a year (if possible) before it's finally really broken.
You **can't** rely on the default settings and the `structlog.dev` module, though.
They may be adjusted in the future to provide a better experience when starting to use *structlog*.
So please make sure to **always** properly configure your applications.
## Reporting a vulnerability
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure.
structlog-25.1.0/.github/dependabot.yml 0000644 0000000 0000000 00000000173 14742155321 014750 0 ustar 00 ---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
structlog-25.1.0/.github/workflows/build-docset.yml 0000644 0000000 0000000 00000001256 14742155321 017261 0 ustar 00 ---
name: Build docset
on:
push:
tags: ["*"]
workflow_dispatch:
env:
PIP_DISABLE_PIP_VERSION_CHECK: 1
PIP_NO_PYTHON_VERSION_WARNING: 1
permissions: {}
jobs:
docset:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # get correct version
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install tox
- run: tox run -e docset
- run: tar --exclude='.DS_Store' -cvzf structlog.tgz structlog.docset
- uses: actions/upload-artifact@v4
with:
name: docset
path: structlog.tgz
structlog-25.1.0/.github/workflows/ci.yml 0000644 0000000 0000000 00000014311 14742155321 015272 0 ustar 00 ---
name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
env:
FORCE_COLOR: "1" # Make tools pretty.
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PIP_NO_PYTHON_VERSION_WARNING: "1"
permissions: {}
jobs:
build-package:
name: Build & verify package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: hynek/build-and-inspect-python-package@v2
id: baipp
outputs:
# Used to define the matrix for tests below. The value is based on
# packaging metadata (trove classifiers).
python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }}
tests:
name: Tests & Mypy API on ${{ matrix.python-version }}
runs-on: ubuntu-latest
needs: build-package
strategy:
fail-fast: false
matrix:
# Created by the build-and-inspect-python-package action above.
python-version: ${{ fromJson(needs.build-package.outputs.python-versions) }}
env:
PYTHON: ${{ matrix.python-version }}
steps:
- name: Download pre-built packages
uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- run: |
tar xf dist/*.tar.gz --strip-components=1
rm -rf src
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- uses: hynek/setup-cached-uv@v2
- name: Run tests
run: >
uvx --with tox-uv tox run
--installpkg dist/*.whl
-f py${PYTHON//./}-tests
- name: Upload coverage data
uses: actions/upload-artifact@v4
with:
name: coverage-data-${{ matrix.python-version }}
path: .coverage.*
include-hidden-files: true
if-no-files-found: ignore
- name: Check public API with Mypy
run: >
uvx --with tox-uv tox run
--installpkg dist/*.whl
-e py${PYTHON//./}-mypy
coverage:
name: Ensure 100% test coverage
runs-on: ubuntu-latest
needs: tests
if: always()
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version-file: .python-version-default
- uses: hynek/setup-cached-uv@v2
- name: Download coverage data
uses: actions/download-artifact@v4
with:
pattern: coverage-data-*
merge-multiple: true
- name: Combine coverage and fail if it's <100%.
run: |
uv tool install coverage
coverage combine
coverage html --skip-covered --skip-empty
# Report and write to summary.
coverage report --format=markdown >> $GITHUB_STEP_SUMMARY
# Report again and fail if under 100%.
coverage report --fail-under=100
- name: Upload HTML report if check failed.
uses: actions/upload-artifact@v4
with:
name: html-report
path: htmlcov
if: ${{ failure() }}
mypy-pkg:
name: Mypy Codebase
runs-on: ubuntu-latest
needs: build-package
steps:
- name: Download pre-built packages
uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- run: tar xf dist/*.tar.gz --strip-components=1
- uses: actions/setup-python@v5
with:
python-version-file: .python-version-default
- uses: hynek/setup-cached-uv@v2
- run: >
uvx --with tox-uv
tox run -e mypy-pkg
pyright:
name: Pyright Codebase
runs-on: ubuntu-latest
needs: build-package
steps:
- name: Download pre-built packages
uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- run: tar xf dist/*.tar.gz --strip-components=1
- uses: actions/setup-python@v5
with:
python-version-file: .python-version-default
- uses: hynek/setup-cached-uv@v2
- run: >
uvx --with tox-uv
tox run -e pyright
docs:
name: Run doctests
needs: build-package
runs-on: ubuntu-latest
steps:
- name: Download pre-built packages
uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- run: tar xf dist/*.tar.gz --strip-components=1
- uses: actions/setup-python@v5
with:
# Keep in sync with tox.ini/docs & .readthedocs.yaml
python-version: "3.13"
- uses: hynek/setup-cached-uv@v2
- run: >
uvx --with tox-uv
tox run -e docs-doctests
install-dev:
name: Verify dev env
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version-file: .python-version-default
- uses: hynek/setup-cached-uv@v2
- run: uv venv
- run: uv pip install -e .[dev]
- run: .venv/bin/python -Ic 'import structlog; print(structlog.__version__)'
if: runner.os != 'Windows'
- run: .\.venv\Scripts\python.exe -Ic 'import structlog; print(structlog.__version__)'
if: runner.os == 'Windows'
required-checks-pass:
name: Ensure everything required is passing for branch protection
if: always()
needs:
- coverage
- install-dev
- mypy-pkg
- pyright
- docs
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
colors:
name: Visual check for color settings using env variables
needs: build-package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version-file: .python-version-default
- uses: hynek/setup-cached-uv@v2
- run: >
uvx --with=tox-uv
tox run
-f color
structlog-25.1.0/.github/workflows/codeql-analysis.yml 0000644 0000000 0000000 00000001346 14742155321 017773 0 ustar 00 ---
name: CodeQL
on:
schedule:
- cron: "41 3 * * 6"
permissions:
contents: read
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [python]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
structlog-25.1.0/.github/workflows/pypi-package.yml 0000644 0000000 0000000 00000003525 14742155321 017256 0 ustar 00 ---
name: Build & upload PyPI package
on:
push:
branches: [main]
tags: ["*"]
release:
types:
- published
workflow_dispatch:
jobs:
# Always build & lint package.
build-package:
name: Build & verify package
runs-on: ubuntu-latest
permissions:
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: hynek/build-and-inspect-python-package@v2
with:
attest-build-provenance-github: 'true'
# Upload to Test PyPI on every commit on main.
release-test-pypi:
name: Publish in-dev package to test.pypi.org
environment: release-test-pypi
if: github.repository_owner == 'hynek' && github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: build-package
permissions:
id-token: write
steps:
- name: Download packages built by build-and-inspect-python-package
uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- name: Upload package to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
# Upload to real PyPI on GitHub Releases.
release-pypi:
name: Publish released package to pypi.org
environment: release-pypi
if: github.repository_owner == 'hynek' && github.event.action == 'published'
runs-on: ubuntu-latest
needs: build-package
permissions:
id-token: write
steps:
- name: Download packages built by build-and-inspect-python-package
uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
structlog-25.1.0/.github/workflows/zizmor.yml 0000644 0000000 0000000 00000001653 14742155321 016236 0 ustar 00 # https://github.com/woodruffw/zizmor
name: Zizmor
on:
push:
branches: ["main"]
pull_request:
branches: ["*"]
permissions:
contents: read
jobs:
zizmor:
name: Zizmor latest via PyPI
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- uses: hynek/setup-cached-uv@v2
- name: Run zizmor 🌈
run: uvx zizmor --format sarif . > results.sarif
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v3
with:
# Path to SARIF file relative to the root of the repository
sarif_file: results.sarif
# Optional category for the results
# Used to differentiate multiple results for one commit
category: zizmor
structlog-25.1.0/docs/Makefile 0000644 0000000 0000000 00000012713 14742155321 013153 0 ustar 00 # Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make ' where is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -n -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/structlog.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/structlog.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/structlog"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/structlog"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
structlog-25.1.0/docs/api.rst 0000644 0000000 0000000 00000025211 14742155321 013013 0 ustar 00 .. _api:
API Reference
=============
.. note::
The examples here use a very simplified configuration using the minimalist `structlog.processors.KeyValueRenderer` for brevity and to enable doctests.
The output is going to be different (nicer!) with the default configuration.
.. testsetup:: *
import structlog
structlog.configure(
processors=[structlog.processors.KeyValueRenderer()],
)
.. testcleanup:: *
import structlog
structlog.reset_defaults()
.. module:: structlog
`structlog` Package
-------------------
.. autofunction:: get_logger
.. autofunction:: getLogger
.. autofunction:: wrap_logger
.. autofunction:: configure
.. autofunction:: configure_once
.. autofunction:: reset_defaults
.. autofunction:: is_configured
.. autofunction:: get_config
.. autoclass:: BoundLogger
:members: new, bind, unbind
.. autofunction:: make_filtering_bound_logger
.. autofunction:: get_context
.. autoclass:: PrintLogger
:members: msg, err, debug, info, warning, error, critical, log, failure, fatal
.. autoclass:: PrintLoggerFactory
.. autoclass:: WriteLogger
:members: msg, err, debug, info, warning, error, critical, log, failure, fatal
.. autoclass:: WriteLoggerFactory
.. autoclass:: BytesLogger
:members: msg, err, debug, info, warning, error, critical, log, failure, fatal
.. autoclass:: BytesLoggerFactory
.. autoexception:: DropEvent
.. autoclass:: BoundLoggerBase
:members: new, bind, unbind, try_unbind, _logger, _process_event, _proxy_to_logger
`structlog.dev` Module
----------------------
.. automodule:: structlog.dev
.. autoclass:: ConsoleRenderer
:members: get_default_level_styles
.. autoclass:: Column
.. autoclass:: ColumnFormatter(typing.Protocol)
:members: __call__
.. autoclass:: KeyValueColumnFormatter
.. autoclass:: LogLevelColumnFormatter
.. autofunction:: plain_traceback
.. autoclass:: RichTracebackFormatter
.. autofunction:: rich_traceback
.. autofunction:: better_traceback
.. autofunction:: set_exc_info
`structlog.testing` Module
--------------------------
.. automodule:: structlog.testing
.. autofunction:: capture_logs
.. autoclass:: LogCapture
.. autoclass:: CapturingLogger
>>> from pprint import pprint
>>> cl = structlog.testing.CapturingLogger()
>>> cl.info("hello")
>>> cl.info("hello", when="again")
>>> pprint(cl.calls)
[CapturedCall(method_name='info', args=('hello',), kwargs={}),
CapturedCall(method_name='info', args=('hello',), kwargs={'when': 'again'})]
.. autoclass:: CapturingLoggerFactory
.. autoclass:: CapturedCall
.. autoclass:: ReturnLogger
:members: msg, err, debug, info, warning, error, critical, log, failure, fatal
.. autoclass:: ReturnLoggerFactory
`structlog.contextvars` Module
------------------------------
.. automodule:: structlog.contextvars
.. autofunction:: bind_contextvars
.. autofunction:: bound_contextvars
.. autofunction:: get_contextvars
.. autofunction:: get_merged_contextvars
.. autofunction:: merge_contextvars
.. autofunction:: clear_contextvars
.. autofunction:: unbind_contextvars
.. autofunction:: reset_contextvars
`structlog.threadlocal` Module
------------------------------
.. automodule:: structlog.threadlocal
:noindex:
.. _procs:
`structlog.processors` Module
-----------------------------
.. automodule:: structlog.processors
.. autoclass:: JSONRenderer
.. doctest::
>>> from structlog.processors import JSONRenderer
>>> JSONRenderer(sort_keys=True)(None, "", {"a": 42, "b": [1, 2, 3]})
'{"a": 42, "b": [1, 2, 3]}'
Bound objects are attempted to be serialize using a ``__structlog__`` method.
If none is defined, ``repr()`` is used:
.. doctest::
>>> class C1:
... def __structlog__(self):
... return ["C1!"]
... def __repr__(self):
... return "__structlog__ took precedence"
>>> class C2:
... def __repr__(self):
... return "No __structlog__, so this is used."
>>> from structlog.processors import JSONRenderer
>>> JSONRenderer(sort_keys=True)(None, "", {"c1": C1(), "c2": C2()})
'{"c1": ["C1!"], "c2": "No __structlog__, so this is used."}'
Please note that additionally to strings, you can also return any type the standard library JSON module knows about -- like in this example a list.
If you choose to pass a *default* parameter as part of *dumps_kw*, support for ``__structlog__`` is disabled.
That can be useful with more elegant serialization methods like `functools.singledispatch`: `Better Python Object Serialization `_.
It can also be helpful if you are using *orjson* and want to rely on it to serialize `datetime.datetime` and other objects natively.
.. tip::
If you use this processor, you may also wish to add structured tracebacks for exceptions.
You can do this by adding the :class:`~structlog.processors.dict_tracebacks` to your list of processors:
.. doctest::
>>> structlog.configure(
... processors=[
... structlog.processors.dict_tracebacks,
... structlog.processors.JSONRenderer(),
... ],
... )
>>> log = structlog.get_logger()
>>> var = "spam"
>>> try:
... 1 / 0
... except ZeroDivisionError:
... log.exception("Cannot compute!")
{"event": "Cannot compute!", "exception": [{"exc_type": "ZeroDivisionError", "exc_value": "division by zero", "syntax_error": null, "is_cause": false, "frames": [{"filename": "", "lineno": 2, "name": "", "locals": {..., "var": "'spam'"}}]}]}
.. autoclass:: KeyValueRenderer
.. doctest::
>>> from structlog.processors import KeyValueRenderer
>>> KeyValueRenderer(sort_keys=True)(None, "", {"a": 42, "b": [1, 2, 3]})
'a=42 b=[1, 2, 3]'
>>> KeyValueRenderer(key_order=["b", "a"])(None, "",
... {"a": 42, "b": [1, 2, 3]})
'b=[1, 2, 3] a=42'
.. autoclass:: LogfmtRenderer
.. doctest::
>>> from structlog.processors import LogfmtRenderer
>>> event_dict = {"a": 42, "b": [1, 2, 3], "flag": True}
>>> LogfmtRenderer(sort_keys=True)(None, "", event_dict)
'a=42 b="[1, 2, 3]" flag'
>>> LogfmtRenderer(key_order=["b", "a"], bool_as_flag=False)(None, "", event_dict)
'b="[1, 2, 3]" a=42 flag=true'
.. autoclass:: EventRenamer
.. autofunction:: add_log_level
.. autoclass:: UnicodeDecoder
.. autoclass:: UnicodeEncoder
.. autoclass:: ExceptionRenderer
.. autofunction:: format_exc_info
.. doctest::
>>> from structlog.processors import format_exc_info
>>> try:
... raise ValueError
... except ValueError:
... format_exc_info(None, "", {"exc_info": True}) # doctest: +ELLIPSIS
{'exception': 'Traceback (most recent call last):...
.. autofunction:: dict_tracebacks
.. doctest::
>>> from structlog.processors import dict_tracebacks
>>> try:
... raise ValueError("onoes")
... except ValueError:
... dict_tracebacks(None, "", {"exc_info": True}) # doctest: +ELLIPSIS
{'exception': [{'exc_type': 'ValueError', 'exc_value': 'onoes', ..., 'frames': [{'filename': ...
.. autoclass:: StackInfoRenderer
.. autoclass:: ExceptionPrettyPrinter
.. autoclass:: TimeStamper
.. doctest::
>>> from structlog.processors import TimeStamper
>>> TimeStamper()(None, "", {}) # doctest: +SKIP
{'timestamp': 1378994017}
>>> TimeStamper(fmt="iso")(None, "", {}) # doctest: +SKIP
{'timestamp': '2013-09-12T13:54:26.996778Z'}
>>> TimeStamper(fmt="%Y", key="year")(None, "", {}) # doctest: +SKIP
{'year': '2013'}
.. autoclass:: MaybeTimeStamper
.. doctest::
>>> from structlog.processors import MaybeTimeStamper
>>> MaybeTimeStamper()(None, "", {}) # doctest: +SKIP
{'timestamp': 1690036074.494428}
>>> MaybeTimeStamper()(None, "", {"timestamp": 42})
{'timestamp': 42}
.. autoclass:: CallsiteParameter
:members:
.. autoclass:: CallsiteParameterAdder
`structlog.stdlib` Module
-------------------------
.. automodule:: structlog.stdlib
.. autofunction:: recreate_defaults
.. autofunction:: get_logger
.. autoclass:: BoundLogger
:members: bind, unbind, try_unbind, new, debug, info, warning, warn, error, critical, exception, log, adebug, ainfo, awarning, aerror, acritical, aexception, alog
.. autoclass:: AsyncBoundLogger
:members: sync_bl
.. autoclass:: LoggerFactory
:members: __call__
.. autofunction:: render_to_log_args_and_kwargs
.. autofunction:: render_to_log_kwargs
.. autofunction:: filter_by_level
.. autofunction:: add_log_level
.. autofunction:: add_log_level_number
.. autofunction:: add_logger_name
.. autoclass:: ExtraAdder
.. autoclass:: PositionalArgumentsFormatter
.. autoclass:: ProcessorFormatter
:members: wrap_for_formatter, remove_processors_meta
`structlog.tracebacks` Module
-----------------------------
.. automodule:: structlog.tracebacks
.. autofunction:: extract
.. autoclass:: ExceptionDictTransformer
.. autoclass:: Trace
.. autoclass:: Stack
.. autoclass:: Frame
.. autoclass:: SyntaxError_
`structlog.typing` Module
-------------------------
.. automodule:: structlog.typing
.. autoclass:: BindableLogger
Additionally to the methods listed below, bound loggers **must** have a ``__init__`` method with the following signature:
.. method:: __init__(self, wrapped_logger: WrappedLogger, processors: Iterable[Processor], context: Context) -> None
:noindex:
Unfortunately it's impossible to define initializers using :pep:`544` Protocols.
They currently also have to carry a `Context` as a ``_context`` attribute.
.. note::
Currently Sphinx has no support for Protocols, so please click ``[source]`` for this entry to see the full definition.
.. autoclass:: FilteringBoundLogger
.. note::
Currently Sphinx has no support for Protocols, so please click ``[source]`` for this entry to see the full definition.
.. autoclass:: ExceptionTransformer
.. note::
Currently Sphinx has no support for Protocols, so please click ``[source]`` for this entry to see the full definition.
.. autodata:: EventDict
.. autodata:: WrappedLogger
.. autodata:: Processor
.. autodata:: Context
.. autodata:: ExcInfo
.. autodata:: ExceptionRenderer
`structlog.twisted` Module
--------------------------
.. automodule:: structlog.twisted
.. autoclass:: BoundLogger
:members: bind, unbind, new, msg, err
.. autoclass:: LoggerFactory
:members: __call__
.. autoclass:: EventAdapter
.. autoclass:: JSONRenderer
.. autofunction:: plainJSONStdOutLogger
.. autofunction:: JSONLogObserverWrapper
.. autoclass:: PlainFileLogObserver
structlog-25.1.0/docs/bound-loggers.md 0000644 0000000 0000000 00000020645 14742155321 014607 0 ustar 00 # Bound Loggers
The centerpiece of *structlog* that you will interact with most is called a *bound logger*.
It's what you get back from {func}`structlog.get_logger()` and it's called a *bound logger* because you can *bind* key-value pairs to it.
As far as *structlog* is concerned, it consists of three parts:
```{image} _static/BoundLogger.svg
```
1. A *context dictionary* that you can *bind* key-value pairs to.
This dictionary is *merged* into each log entry that is logged from *this logger specifically*.
You can inspect a context of a *bound logger* by calling {func}`structlog.get_context()` on it.
2. A list of {doc}`processors ` that are called on every log entry.
Each processor receives the return value of its predecessor passed as an argument.
This list is usually set using {doc}`configuration`.
3. And finally a *logger* that it's wrapping.
This wrapped logger is responsible for the *output* of the log entry that has been returned by the last processor.
This *can* be standard library's {class}`logging.Logger` like in the image above, but absolutely doesn't have to:
By default it's *structlog*'s {class}`~structlog.PrintLogger`.
This wrapped logger also is usually set using {doc}`configuration`.
:::{important}
Bound loggers themselves do *not* do any I/O themselves.
All they do is manage the *context* and proxy log calls to a *wrapped logger*.
:::
## Context
To manipulate the context dictionary, a *bound logger* can:
- Recreate itself with (optional) *additional* context data: {func}`~structlog.BoundLoggerBase.bind` and {func}`~structlog.BoundLoggerBase.new`.
- Recreate itself with *less* context data: {func}`~structlog.BoundLoggerBase.unbind` and {func}`~structlog.BoundLoggerBase.try_unbind`.
In any case, the original bound logger or its context are never mutated.
They always return a *copy* of the bound logger with a *new* context that reflects your changes.
This part of the API is defined in the {class}`typing.Protocol` called {class}`structlog.typing.BindableLogger`.
The protocol is marked {func}`typing.runtime_checkable` which means that you can check an object for being a *bound logger* using `isinstance(obj, structlog.typing.BindableLogger)`.
## Output
Finally, a *bound logger* also **indirectly** exposes the logging methods of the *wrapped logger*.
By default, that's a {class}`~structlog.typing.FilteringBoundLogger` that is wrapping a {class}`~structlog.PrintLogger`.
They both share the set of log methods that's present in the standard library: `debug()`, `info()`, `warning()`, `error()`, and `critical()`.
Whenever you call one of those methods on the *bound logger*, it will:
1. Make a copy of its context -- now it becomes the *event dictionary*,
2. Add the keyword arguments of the method call to the event dict.
3. Add a new key `event` with the value of the first positional argument of the method call to the event dict.
4. Run the processors successively on the event dict.
Each processor receives the result of its predecessor.
5. Finally, it takes the result of the final processor and calls the method with the same name – that got called on the *bound logger* – on the wrapped logger.
For flexibility, the final processor can return either a string[^str] that is passed directly as a positional parameter, or a tuple `(args, kwargs)` that are passed as `wrapped_logger.log_method(*args, **kwargs)`.
[^str]: {any}`str`, {any}`bytes`, or {any}`bytearray` to be exact.
### Step-by-Step example
Assuming you've left the default configuration and have:
```python
import structlog
logger = structlog.get_logger()
log = logger.bind(foo="bar")
```
Now, `log` is a *bound logger* of type {class}`~structlog.typing.FilteringBoundLogger` (but in the default config there's no filtering).
`log`'s context is `{"foo": "bar"}` and its wrapped logger is a {class}`structlog.PrintLogger`.
If you call `log.info("Hello, %s!", "world", number=42)` now, the following happens:
1. `"world"` gets interpolated into `"Hello, %s!"`, making the event "Hello, world!"[^interpolation].
2. The *bound logger*'s context gets copied and the key-value pairs from the `info` call are added to it.
It becomes an *event dict* and is `{"foo": "bar", "number": 42}` now.
3. The event from step 1 is added too.
The *event dict* is `{"foo": "bar", "number": 42, "event": "Hello, world!"}` now.
4. The *event dict* is fed into the [processor chain](processors.md).
In this case the processors add a timestamp and the log level name to the *event dict*.
Before it hits the last processor, the *event dict* looks something like `{"foo": "bar", "number": 42, "event": "Hello, world!", "level": "info", "timestamp": "2022-10-13 16:29:27"}`.
The last processor is {class}`structlog.dev.ConsoleRenderer` and renders the *event dict* into a colorful string[^json].
5. Finally, the *wrapped logger*'s (a {class}`~structlog.PrintLogger`) `info()` method is called with that string.
[^json]: Until this very step, the *event dict* was a dictionary.
By replacing the last processor, you decide on the **format** of your logs.
For example, if you wanted JSON logs, you just have to replace the last processor with {class}`structlog.processors.JSONRenderer`.
[^interpolation]: String interpolation only takes place if you pass positional arguments.
(filtering)=
## Filtering by log levels
Filtering based on log levels can be done in a processor very easily[^stdlib], however that means unnecessary performance overhead through function calls.
We care a lot about performance and that's why *structlog*'s default *bound logger* class implements level-filtering as close to the users as possible: in the *bound logger*'s logging methods *before* even creating an *event dict* and starting the processor chain.
{func}`structlog.make_filtering_bound_logger` allows you to create a *bound logger* whose log methods with a log level beneath the configured one consist of a plain `return None`.
Here's an example:
```pycon
>>> import structlog
>>> logger = structlog.get_logger()
>>> logger.debug("hi!")
2022-10-15 11:39:03 [debug ] hi!
>>> import logging
>>> structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(logging.INFO))
>>> logger.debug("hi!")
# no output!
```
In this example, we first log out using the default logger that doesn't filter at all.
Then we change the configuration to filtering at the info level and try again:
no log output!
Let's have a look at the `debug` method:
```pycon
>>> import inspect
>>> print(inspect.getsource(logger.debug))
def _nop(self: Any, event: str, **kw: Any) -> Any:
return None
```
This is as effective as it gets and usually as flexible as the vast majority of users need.
:::{important}
*structlog* uses the constants from {mod}`logging`, but does **not** share any code.
Passing `20` instead of `logging.INFO` would have worked too.
:::
[^stdlib]: And it's in fact supported for standard library logging with the {func}`structlog.stdlib.filter_by_level` processor.
## Wrapping loggers manually
In practice, you won't be instantiating bound loggers yourself.
You will configure *structlog* as explained in the {doc}`next chapter ` and then just call {func}`structlog.get_logger`.
However, in some rare cases you may not want to do that.
For example because you don't control how you get the logger that you would like to wrap (famous example: Celery).
For that times there is the {func}`structlog.wrap_logger` function that can be used to wrap a logger -- optionally without any global state (in other words, configuration):
(proc)=
```{doctest}
>>> import structlog
>>> class CustomPrintLogger:
... def msg(self, message):
... print(message)
>>> def proc(logger, method_name, event_dict):
... print("I got called with", event_dict)
... return repr(event_dict)
>>> log = structlog.wrap_logger(
... CustomPrintLogger(),
... wrapper_class=structlog.BoundLogger,
... processors=[proc],
... )
>>> log2 = log.bind(x=42)
>>> log == log2
False
>>> log.msg("hello world")
I got called with {'event': 'hello world'}
{'event': 'hello world'}
>>> log2.msg("hello world")
I got called with {'x': 42, 'event': 'hello world'}
{'x': 42, 'event': 'hello world'}
>>> log3 = log2.unbind("x")
>>> log == log3
True
>>> log3.msg("nothing bound anymore", foo="but you can structure the event too")
I got called with {'foo': 'but you can structure the event too', 'event': 'nothing bound anymore'}
{'foo': 'but you can structure the event too', 'event': 'nothing bound anymore'}
```
structlog-25.1.0/docs/conf.py 0000644 0000000 0000000 00000011035 14742155321 013006 0 ustar 00 # SPDX-License-Identifier: MIT OR Apache-2.0
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the MIT License. See the LICENSE file in the root of this
# repository for complete details.
import os
from importlib import metadata
# Set canonical URL from the Read the Docs Domain
html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "")
# Tell Jinja2 templates the build is running on Read the Docs
if os.environ.get("READTHEDOCS", "") == "True":
html_context = {"READTHEDOCS": True}
# We want an image in the README and include the README in the docs.
suppress_warnings = ["image.nonlocal_uri"]
# -- General configuration ----------------------------------------------------
extensions = [
"myst_parser",
"notfound.extension",
"sphinx.ext.autodoc",
"sphinx.ext.autodoc.typehints",
"sphinx.ext.napoleon",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinxcontrib.mermaid",
"sphinxext.opengraph",
]
myst_enable_extensions = [
"colon_fence",
"smartquotes",
"deflist",
]
mermaid_init_js = "mermaid.initialize({startOnLoad:true,theme:'neutral'});"
ogp_image = "_static/structlog_logo.png"
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = [".rst", ".md"]
# The master toctree document.
master_doc = "index"
# General information about the project.
project = "structlog"
author = "Hynek Schlawack"
copyright = f"2013, {author}"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
# The full version, including alpha/beta/rc tags.
release = metadata.version("structlog")
# The short X.Y version.
version = release.rsplit(".", 1)[0]
if "dev" in release:
release = version = "UNRELEASED"
exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
default_role = "any"
nitpick_ignore = [
("py:class", "Context"),
("py:class", "EventDict"),
("py:class", "ILogObserver"),
("py:class", "PlainFileObserver"),
("py:class", "Processor"),
("py:class", "Styles"),
("py:class", "WrappedLogger"),
("py:class", "structlog.threadlocal.TLLogger"),
("py:class", "structlog.typing.EventDict"),
("py:class", "ModuleType"),
]
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# Move type hints into the description block, instead of the func definition.
autodoc_typehints = "description"
autodoc_typehints_description_target = "documented"
# -- Options for HTML output --------------------------------------------------
html_theme = "furo"
html_theme_options = {
"top_of_page_buttons": [],
"light_css_variables": {
"font-stack": "B612, sans-serif",
"font-stack--monospace": "BerkeleyMono, MonoLisa, ui-monospace, "
"SFMono-Regular, Menlo, Consolas, Liberation Mono, monospace",
},
}
html_logo = "_static/structlog_logo.svg"
html_static_path = ["_static"]
html_css_files = ["custom.css"]
htmlhelp_basename = "structlogdoc"
latex_documents = [
("index", "structlog.tex", "structlog Documentation", "Author", "manual")
]
# -- Options for manual page output -------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [("index", "structlog", "structlog Documentation", ["Author"], 1)]
# -- Options for Texinfo output -----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
"index",
"structlog",
"structlog Documentation",
"Author",
"structlog",
"One line description of project.",
"Miscellaneous",
)
]
# -- Options for Epub output --------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
epub_author = author
epub_publisher = author
epub_copyright = copyright
# GitHub has rate limits
linkcheck_ignore = [
r"https://github.com/.*/(issues|pull|compare)/\d+",
r"https://twitter.com/.*",
]
# Twisted's trac tends to be slow
linkcheck_timeout = 300
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"rich": ("https://rich.readthedocs.io/en/stable/", None),
}
structlog-25.1.0/docs/configuration.md 0000644 0000000 0000000 00000011470 14742155321 014703 0 ustar 00 # Configuration
The focus of *structlog* has always been to be flexible to a fault.
The goal is that a user can use it with *any* logger of their own that is wrapped by *structlog*.
That's the reason why there's an overwhelming amount of knobs to tweak, but
– ideally – once you find your configuration, you don't touch it ever again and, more importantly:
don't see any of it in your application code.
---
Let's start at the end and introduce the ultimate convenience function that relies purely on configuration: {func}`structlog.get_logger`.
The goal is to reduce your per-file application logging boilerplate to:
```
import structlog
logger = structlog.get_logger()
```
To that end, you'll have to call {func}`structlog.configure` on app initialization.
You can call {func}`structlog.configure` repeatedly and only set one or more settings -- the rest will not be affected.
If necessary, you can always reset your global configuration back to default values using {func}`structlog.reset_defaults`.
That can be handy in tests.
At any time, you can check whether and how *structlog* is configured using {func}`structlog.is_configured` and {func}`structlog.get_config`}:
```pycon
>>> structlog.is_configured()
False
>>> structlog.configure(logger_factory=structlog.stdlib.LoggerFactory())
>>> structlog.is_configured()
True
>>> cfg = structlog.get_config()
>>> cfg["logger_factory"]
```
:::{important}
Since you'll call {func}`structlog.get_logger` in module scope, it runs at import time *before* you had a chance to configure *structlog*.
Therefore it returns a **lazy proxy** that returns a correctly configured *bound logger* on its first call to one of the context-managing methods like `bind()`.
Thus, you must never call `new()` or `bind()` in module or class scope because , you will receive a logger configured with *structlog*'s default values.
Use {func}`~structlog.get_logger`'s `initial_values` to achieve pre-populated contexts.
To enable you to log with the module-global logger, it will create a temporary *bound logger* **on each call**.
Therefore if you have nothing to bind but intend to do lots of log calls in a function, it makes sense performance-wise to create a local logger by calling `bind()` or `new()` without any parameters.
See also {doc}`performance`.
:::
## What to configure
You can find the details in the API documentation of {func}`structlog.configure`, but let's introduce the most important ones at a high level first.
### Wrapper classes
You've met {doc}`bound-loggers` in the last chapter.
They're the objects returned by {func}`~structlog.get_logger` and allow to bind key-value pairs into their private context.
You can configure their type using the `wrapper_class` keyword.
Whenever you bind or unbind data to a *bound logger*, this class is instantiated with the new context and returned.
### Logger factories
We've already talked about wrapped loggers responsible for the output, but we haven't explained where they come from until now.
Unlike with *bound loggers*, you often need more flexibility when instantiating them.
Therefore you don't configure a class; you configure a *factory* using the `logger_factory` keyword.
It's a callable that returns the logger that gets wrapped and returned.
In the simplest case, it's a function that returns a logger -- or just a class.
But you can also pass in an instance of a class with a `__call__` method for more complicated setups.
These will be passed to the logger factories.
For example, if you use `structlog.get_logger("a name")` and configure *structlog* to use the standard library {class}`~structlog.stdlib.LoggerFactory`, which has support for positional parameters, the returned logger will have the name `"a name"`.
For the common cases of standard library logging and Twisted logging, *structlog* comes with two factories built right in:
- {class}`structlog.stdlib.LoggerFactory`
- {class}`structlog.twisted.LoggerFactory`
So all it takes to use standard library {mod}`logging` for output is:
```
>>> from structlog import get_logger, configure
>>> from structlog.stdlib import LoggerFactory
>>> configure(logger_factory=LoggerFactory())
>>> log = get_logger()
>>> log.critical("this is too easy!")
event='this is too easy!'
```
By using *structlog*'s {class}`structlog.stdlib.LoggerFactory`, it is also ensured that variables like function names and line numbers are expanded correctly in your log format.
See {doc}`standard-library` for more details.
Calling {func}`structlog.get_logger` without configuration gives you a perfectly useful {class}`structlog.PrintLogger`.
We don't believe silent loggers are a sensible default.
### Processors
You will meet {doc}`processors` in the next chapter.
They are configured using the `processors` keyword that takes an {class}`~collections.abc.Iterable` of callables that act as processors.
structlog-25.1.0/docs/console-output.md 0000644 0000000 0000000 00000012272 14742155321 015035 0 ustar 00 # Console Output
To make development a more pleasurable experience, *structlog* comes with the {mod}`structlog.dev` module.
The highlight is {class}`structlog.dev.ConsoleRenderer` that offers nicely aligned and colorful[^win] console output.
[^win]: Requires the [Colorama package](https://pypi.org/project/colorama/) on Windows.
If either of the [Rich](https://rich.readthedocs.io/) or [*better-exceptions*](https://github.com/Qix-/better-exceptions) packages is installed, it will also pretty-print exceptions with helpful contextual data.
Rich takes precedence over *better-exceptions*, but you can configure it by passing {func}`structlog.dev.plain_traceback` or {func}`structlog.dev.better_traceback` for the `exception_formatter` parameter of {class}`~structlog.dev.ConsoleRenderer`.
The following output is rendered using Rich:
```{figure} _static/console_renderer.png
Colorful console output by ConsoleRenderer.
```
You can find the code for the output above [in the repo](https://github.com/hynek/structlog/blob/main/show_off.py).
To use it, just add it as a renderer to your processor chain.
It will recognize logger names, log levels, time stamps, stack infos, and `exc_info` as produced by *structlog*'s processors and render them in special ways.
:::{warning}
For pretty exceptions to work, {func}`~structlog.processors.format_exc_info` must be **absent** from the processors chain.
:::
*structlog*'s default configuration already uses {class}`~structlog.dev.ConsoleRenderer`, therefore if you want nice colorful output on the console, you don't have to do anything except installing Rich or *better-exceptions* (and Colorama on Windows).
If you want to use it along with standard library logging, there's the {func}`structlog.stdlib.recreate_defaults` helper.
:::{seealso}
{doc}`exceptions` for more information on how to configure exception rendering.
For the console and beyond.
:::
(columns-config)=
## Console output configuration
:::{versionadded} 23.3.0
:::
You can freely configure how the key-value pairs are formatted: colors, order, and how values are stringified.
For that {class}`~structlog.dev.ConsoleRenderer` accepts the *columns* parameter that takes a list of {class}`~structlog.dev.Column`s.
It allows you to assign a formatter to each key and a default formatter for the rest (by passing an empty key name).
The order of the column definitions is the order in which the columns are rendered;
the rest is -- depending on the *sort_keys* argument to {class}`~structlog.dev.ConsoleRenderer` -- either sorted alphabetically or in the order of the keys in the event dictionary.
You can use a column definition to drop a key-value pair from the output by returning an empty string from the formatter.
When the API talks about "styles", it means ANSI control strings.
You can find them, for example, in [Colorama](https://github.com/tartley/colorama).
It's best demonstrated by an example:
```python
import structlog
import colorama
cr = structlog.dev.ConsoleRenderer(
columns=[
# Render the timestamp without the key name in yellow.
structlog.dev.Column(
"timestamp",
structlog.dev.KeyValueColumnFormatter(
key_style=None,
value_style=colorama.Fore.YELLOW,
reset_style=colorama.Style.RESET_ALL,
value_repr=str,
),
),
# Render the event without the key name in bright magenta.
structlog.dev.Column(
"event",
structlog.dev.KeyValueColumnFormatter(
key_style=None,
value_style=colorama.Style.BRIGHT + colorama.Fore.MAGENTA,
reset_style=colorama.Style.RESET_ALL,
value_repr=str,
),
),
# Default formatter for all keys not explicitly mentioned. The key is
# cyan, the value is green.
structlog.dev.Column(
"",
structlog.dev.KeyValueColumnFormatter(
key_style=colorama.Fore.CYAN,
value_style=colorama.Fore.GREEN,
reset_style=colorama.Style.RESET_ALL,
value_repr=str,
),
),
]
)
structlog.configure(processors=structlog.get_config()["processors"][:-1]+[cr])
```
:::{hint}
You can replace only the last processor using:
```python
structlog.configure(processors=structlog.get_config()["processors"][:-1]+[cr])
```
:::
## Standard environment variables
*structlog*'s default configuration uses colors if standard out is a TTY (that is, an interactive session).
It's possible to override this behavior by setting two standard environment variables to any value except an empty string:
- `FORCE_COLOR` *activates* colors, regardless of where output is going.
- [`NO_COLOR`](https://no-color.org) *disables* colors, regardless of where the output is going and regardless the value of `FORCE_COLOR`.
Please note that `NO_COLOR` disables _all_ styling, including bold and italics.
## Disabling exception pretty-printing
If you prefer the default terse Exception rendering, but still want Rich installed, you can disable the pretty-printing by instantiating {class}`structlog.dev.ConsoleRenderer()` yourself and passing `exception_formatter=structlog.dev.plain_traceback`.
structlog-25.1.0/docs/contextvars.md 0000644 0000000 0000000 00000015171 14742155321 014416 0 ustar 00 (contextvars)=
# Context Variables
```{testsetup}
import structlog
```
```{testcleanup}
import structlog
structlog.reset_defaults()
```
The {mod}`contextvars` module in the Python standard library allows having a global *structlog* context that is local to the current execution context.
The execution context can be thread-local if using threads, stored in the {mod}`asyncio` event loop, or [*greenlet*](https://greenlet.readthedocs.io/) respectively.
For example, you may want to bind certain values like a request ID or the peer's IP address at the beginning of a web request and have them logged out along with the local contexts you build within our views.
For that *structlog* provides the {mod}`structlog.contextvars` module with a set of functions to bind variables to a context-local context.
This context is safe to be used both in threaded as well as asynchronous code.
:::{warning}
Since the storage mechanics of your context variables is different for each concurrency method, they are _isolated_ from each other.
This can be a problem in hybrid applications like those based on [*starlette*](https://www.starlette.io) (this [includes FastAPI](https://github.com/tiangolo/fastapi/discussions/5999)) where context variables set in a synchronous context don't appear in logs from an async context and vice versa.
:::
The general flow is:
- Use {func}`structlog.configure` with {func}`structlog.contextvars.merge_contextvars` as your first processor (part of default configuration).
- Call {func}`structlog.contextvars.clear_contextvars` at the beginning of your request handler (or whenever you want to reset the context-local context).
- Call {func}`structlog.contextvars.bind_contextvars` and {func}`structlog.contextvars.unbind_contextvars` instead of your bound logger's `bind()` and `unbind()` when you want to bind and unbind key-value pairs to the context-local context.
You can also use the {func}`structlog.contextvars.bound_contextvars` context manager / decorator.
- Use *structlog* as normal.
Loggers act as they always do, but the {func}`structlog.contextvars.merge_contextvars` processor ensures that any context-local binds get included in all of your log messages.
- If you want to access the context-local storage, you use {func}`structlog.contextvars.get_contextvars` and {func}`structlog.contextvars.get_merged_contextvars`.
We're sorry the word *context* means three different things in this itemization depending on ... context.
```{doctest}
>>> from structlog.contextvars import (
... bind_contextvars,
... bound_contextvars,
... clear_contextvars,
... merge_contextvars,
... unbind_contextvars,
... )
>>> from structlog import configure
>>> configure(
... processors=[
... merge_contextvars,
... structlog.processors.KeyValueRenderer(key_order=["event", "a"]),
... ]
... )
>>> log = structlog.get_logger()
>>> # At the top of your request handler (or, ideally, some general
>>> # middleware), clear the contextvars-local context and bind some common
>>> # values:
>>> clear_contextvars()
>>> bind_contextvars(a=1, b=2)
{'a': at ...>, 'b': at ...>}
>>> # Then use loggers as per normal
>>> # (perhaps by using structlog.get_logger() to create them).
>>> log.info("hello")
event='hello' a=1 b=2
>>> # Use unbind_contextvars to remove a variable from the context.
>>> unbind_contextvars("b")
>>> log.info("world")
event='world' a=1
>>> # You can also bind key-value pairs temporarily.
>>> with bound_contextvars(b=2):
... log.info("hi")
event='hi' a=1 b=2
>>> # Now it's gone again.
>>> log.info("hi")
event='hi' a=1
>>> # And when we clear the contextvars state again, it goes away.
>>> # a=None is printed due to the key_order argument passed to
>>> # KeyValueRenderer, but it is NOT present anymore.
>>> clear_contextvars()
>>> log.info("hi there")
event='hi there' a=None
```
## Support for `contextvars.Token`
If, for example, your request handler calls a helper function that needs to temporarily override some contextvars before restoring them back to their original values, you can use the {class}`~contextvars.Token`s returned by {func}`~structlog.contextvars.bind_contextvars` along with {func}`~structlog.contextvars.reset_contextvars` to accomplish this (much like how {meth}`contextvars.ContextVar.reset` works):
```python
def foo():
bind_contextvars(a=1)
_helper()
log.info("a is restored!") # a=1
def _helper():
tokens = bind_contextvars(a=2)
log.info("a is overridden") # a=2
reset_contextvars(**tokens)
```
(flask-example)=
## Example: Flask and thread-local data
Let's assume you want to bind a unique request ID, the URL path, and the peer's IP to every log entry by storing it in thread-local storage that is managed by context variables:
```python
import logging
import sys
import uuid
import flask
from .some_module import some_function
import structlog
logger = structlog.get_logger()
app = flask.Flask(__name__)
@app.route("/login", methods=["POST", "GET"])
def some_route():
# You would put this into some kind of middleware or processor so it's set
# automatically for all requests in all views.
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(
view=flask.request.path,
request_id=str(uuid.uuid4()),
peer=flask.request.access_route[0],
)
# End of belongs-to-middleware.
log = logger.bind()
# do something
# ...
log.info("user logged in", user="test-user")
# ...
some_function()
# ...
return "logged in!"
if __name__ == "__main__":
logging.basicConfig(
format="%(message)s", stream=sys.stdout, level=logging.INFO
)
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars, # <--!!!
structlog.processors.KeyValueRenderer(
key_order=["event", "view", "peer"]
),
],
logger_factory=structlog.stdlib.LoggerFactory(),
)
app.run()
```
`some_module.py`:
```python
from structlog import get_logger
logger = get_logger()
def some_function():
# ...
logger.error("user did something", something="shot_in_foot")
# ...
```
This would result among other the following lines to be printed:
```text
event='user logged in' view='/login' peer='127.0.0.1' user='test-user' request_id='e08ddf0d-23a5-47ce-b20e-73ab8877d736'
event='user did something' view='/login' peer='127.0.0.1' something='shot_in_foot' request_id='e08ddf0d-23a5-47ce-b20e-73ab8877d736'
```
As you can see, `view`, `peer`, and `request_id` are present in **both** log entries.
structlog-25.1.0/docs/exceptions.md 0000644 0000000 0000000 00000004367 14742155321 014224 0 ustar 00 # Exceptions
While you should use a proper crash reporter like [Sentry](https://sentry.io) in production, *structlog* has helpers for formatting exceptions for humans and machines.
All *structlog*'s exception features center around passing an `exc_info` key-value pair in the event dict.
There are three possible behaviors depending on its value:
1. If the value is a tuple, render it as if it was returned by {func}`sys.exc_info`.
2. If the value is an Exception, render it.
3. If the value is true but no tuple, call {func}`sys.exc_info` and render that.
If there is no `exc_info` key or false, the event dict is not touched.
This behavior is analog to the one of the stdlib's logging.
## Transformations
*structlog* comes with {class}`structlog.processors.ExceptionRenderer` that deduces and removes the `exc_info` key as outlined above, calls a user-supplied function with the synthesized `exc_info`, and stores its return value in the `exception` key.
The most common use-cases are already covered by the following processors:
{func}`structlog.processors.format_exc_info`
: Formats it to a flat string like the standard library would on the console.
{obj}`structlog.processors.dict_tracebacks`
: Uses {class}`structlog.tracebacks.ExceptionDictTransformer` to give you a structured and JSON-serializable `exception` key.
## Console rendering
Our {doc}`console-output`'s {class}`structlog.dev.ConsoleRenderer` takes an *exception_formatter* argument that allows for customizing the output of exceptions.
{func}`structlog.dev.plain_traceback`
: Is the default if neither [Rich] nor [*better-exceptions*] are installed.
As the name suggests, it renders a plain traceback.
{func}`structlog.dev.better_traceback`
: Uses [*better-exceptions*] to render a colorful traceback.
: It's the default if *better-exceptions* is installed and Rich is not.
{class}`structlog.dev.RichTracebackFormatter`
: Uses [Rich] to render a colorful traceback.
It's a class because it allows for customizing the output by passing arguments to Rich.
: It's the default if Rich is installed.
:::{seealso}
{doc}`console-output` for more information on *structlog*'s console features.
:::
[*better-exceptions*]: https://github.com/qix-/better-exceptions
[Rich]: https://github.com/Textualize/rich
structlog-25.1.0/docs/frameworks.md 0000644 0000000 0000000 00000010634 14742155321 014215 0 ustar 00 # Frameworks
To have consistent log output, it makes sense to configure *structlog* *before* any logging is done.
The best place to perform your configuration varies with applications and frameworks.
If you use standard library's {mod}`logging`, it makes sense to configure them next to each other.
## Celery
[Celery](https://docs.celeryq.dev/)'s multi-process architecture leads unavoidably to race conditions that show up as interleaved logs.
It ships standard library-based helpers in the form of [`celery.utils.log.get_task_logger()`](https://docs.celeryq.dev/en/stable/userguide/tasks.html#logging) that you should use inside of tasks to prevent that problem.
The most straight-forward way to integrate that with *structlog* is using {doc}`standard-library` and wrapping that logger using {func}`structlog.wrap_logger`:
```python
from celery.utils.log import get_task_logger
logger = structlog.wrap_logger(get_task_logger(__name__))
```
If you want to automatically bind task metadata to your {doc}`contextvars`, you can use [Celery's signals](https://docs.celeryq.dev/en/stable/userguide/signals.html):
```python
from celery import signals
@signals.task_prerun.connect
def on_task_prerun(sender, task_id, task, args, kwargs, **_):
structlog.contextvars.bind_contextvars(task_id=task_id, task_name=task.name)
```
See [this issue](https://github.com/hynek/structlog/issues/287) for more details.
## Django
[*django-structlog*](https://pypi.org/project/django-structlog/) is a popular and well-maintained package that does all the heavy lifting.
## Flask
See Flask's [Logging docs](https://flask.palletsprojects.com/en/latest/logging/).
Generally speaking: configure *structlog* *before* instantiating `flask.Flask`.
Here's a [signal handler](https://flask.palletsprojects.com/en/latest/signals/) that binds various request details into [*context variables*](contextvars.md):
```python
def bind_request_details(sender: Flask, **extras: dict[str, Any]) -> None:
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(
request_id=request.headers.get("X-Unique-ID", "NONE"),
peer=peer,
)
if current_user.is_authenticated:
structlog.contextvars.bind_contextvars(
user_id=current_user.get_id(),
)
```
You add it to an existing `app` like this:
```python
from flask import request_started
request_started.connect(bind_request_details, app)
```
## Litestar
[Litestar](https://docs.litestar.dev/) comes with *structlog* support [out of the box](https://docs.litestar.dev/latest/usage/logging.html).
## OpenTelemetry
The [Python OpenTelemetry SDK](https://opentelemetry.io/docs/languages/python/) offers an easy API to get the current span, so you can enrich your logs with a straight-forward processor:
```python
from opentelemetry import trace
def add_open_telemetry_spans(_, __, event_dict):
span = trace.get_current_span()
if not span.is_recording():
event_dict["span"] = None
return event_dict
ctx = span.get_span_context()
parent = getattr(span, "parent", None)
event_dict["span"] = {
"span_id": format(ctx.span_id, "016x"),
"trace_id": format(ctx.trace_id, "032x"),
"parent_span_id": None if not parent else format(parent.span_id, "016x"),
}
return event_dict
```
## Pyramid
Configure it in the [application constructor](https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/startup.html#the-startup-process).
Here's an example for a Pyramid [*tween*](https://docs.pylonsproject.org/projects/pyramid/en/latest/glossary.html#term-tween) that stores various request-specific data into [*context variables*](contextvars.md):
```python
@dataclass
class StructLogTween:
handler: Callable[[Request], Response]
registry: Registry
def __call__(self, request: Request) -> Response:
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(
peer=request.client_addr,
request_id=request.headers.get("X-Unique-ID", "NONE"),
user_agent=request.environ.get("HTTP_USER_AGENT", "UNKNOWN"),
user=request.authenticated_userid,
)
return self.handler(request)
```
## Twisted
The [plugin definition](https://docs.twisted.org/en/stable/core/howto/plugin.html) is the best place.
If your app is not a plugin, put it into your [tac file](https://docs.twisted.org/en/stable/core/howto/application.html).
structlog-25.1.0/docs/getting-started.md 0000644 0000000 0000000 00000024345 14742155321 015146 0 ustar 00 # Getting Started
(install)=
## Installation
You can install *structlog* from [PyPI](https://pypi.org/project/structlog/) using *pip*:
```console
$ python -m pip install structlog
```
If you want pretty exceptions in development (you know you do!), additionally install either [Rich] or [*better-exceptions*].
Try both to find out which one you like better -- the screenshot in the README and docs homepage is rendered by Rich.
On **Windows**, you also have to install [Colorama](https://pypi.org/project/colorama/) if you want colorful output beside exceptions.
## Your first log entry
A lot of effort went into making *structlog* accessible without reading pages of documentation.
As a result, the simplest possible usage looks like this:
```{doctest}
>>> import structlog
>>> log = structlog.get_logger()
>>> log.info("hello, %s!", "world", key="value!", more_than_strings=[1, 2, 3]) # doctest: +SKIP
2022-10-07 10:41:29 [info ] hello, world! key=value! more_than_strings=[1, 2, 3]
```
Here, *structlog* takes advantage of its default settings:
- Output is sent to **[standard out](https://en.wikipedia.org/wiki/Standard_out#Standard_output_.28stdout.29)** instead of doing nothing.
- It **imitates** standard library {mod}`logging`'s **log level names** for familiarity.
By default, no level-based filtering is done, but it comes with a **very fast [filtering machinery](filtering)**.
- Like in `logging`, positional arguments are [**interpolated into the message string using %**](https://docs.python.org/3/library/stdtypes.html#old-string-formatting).
That might look dated, but it's *much* faster than using {any}`str.format` and allows *structlog* to be used as drop-in replacement for {mod}`logging`.
If you *know* that the log entry is *always* gonna be logged out, just use [f-strings](https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals) which are the fastest.
- All keywords are formatted using {class}`structlog.dev.ConsoleRenderer`.
That in turn uses {func}`repr` to serialize **any value to a string**.
- It's rendered in nice **{doc}`colors `**.
- If you have [Rich] or [*better-exceptions*] installed, **exceptions** will be rendered in **colors** and with additional **helpful information**.
Please note that even in most complex logging setups the example would still look just like that thanks to {doc}`configuration`.
Using the defaults, as above, is equivalent to:
```python
import logging
import structlog
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.StackInfoRenderer(),
structlog.dev.set_exc_info,
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
structlog.dev.ConsoleRenderer()
],
wrapper_class=structlog.make_filtering_bound_logger(logging.NOTSET),
context_class=dict,
logger_factory=structlog.PrintLoggerFactory(),
cache_logger_on_first_use=False
)
log = structlog.get_logger()
```
:::{note}
- {func}`structlog.stdlib.recreate_defaults()` allows you to switch *structlog* to using standard library's `logging` module for output for better interoperability with just one function call.
- {func}`~structlog.make_filtering_bound_logger()` (re-)uses {any}`logging`'s log levels, but doesn't use `logging` at all.
The exposed API is {class}`~structlog.typing.FilteringBoundLogger`.
- For brevity and to enable doctests, all further examples in *structlog*'s documentation use the more simplistic {class}`~structlog.processors.KeyValueRenderer()` without timestamps.
:::
There you go, structured logging!
However, this alone wouldn't warrant its own package.
After all, there's even a [recipe] on structured logging for the standard library.
So let's go a step further.
(building-ctx)=
## Building a context
Imagine a hypothetical web application that wants to log out all relevant data with just the APIs that we've introduced so far:
```python
def view(request):
user_agent = request.get("HTTP_USER_AGENT", "UNKNOWN")
peer_ip = request.client_addr
if something:
log.info("something", user_agent=user_agent, peer_ip=peer_ip)
return "something"
elif something_else:
log.info("something_else", user_agent=user_agent, peer_ip=peer_ip)
return "something_else"
else:
log.info("else", user_agent=user_agent, peer_ip=peer_ip)
return "else"
```
The calls themselves are nice and straight to the point, however you're repeating yourself all over the place.
It's easy to forget to add a key-value pair in one of the incantations.
At this point, you'll be tempted to write a closure like:
```python
def log_closure(event):
log.info(event, user_agent=user_agent, peer_ip=peer_ip)
```
inside of the view.
Problem solved?
Not quite.
What if the parameters are introduced step by step?
And do you really want to have a logging closure in each of your views?
Let's have a look at a better approach:
```python
def view(request):
log = log.bind(
user_agent=request.get("HTTP_USER_AGENT", "UNKNOWN"),
peer_ip=request.client_addr,
)
if foo := request.get("foo"):
log = log.bind(foo=foo)
if something:
log.info("something")
return "something"
elif something_else:
log.info("something_else")
return "something_else"
else:
log.info("else")
return "else"
```
Suddenly your logger becomes your closure!
---
To *structlog*, a log entry is just a dictionary called *event dict\[ionary\]*:
- You can pre-build a part of the dictionary step by step.
These pre-saved values are called the *context*.
- As soon as an *event* happens -- which are the `kwargs` of the log call -- it is merged together with the *context* to an *event dict* and logged out.
- Each logger with its context is *immutable*.
You manipulate the context by creating new loggers using `bind()` and `unbind()`.
The last point is very clean and easy to reason about, but sometimes it's useful to store _some_ data globally.
In our example above the peer IP comes to mind.
There's no point in extracting it in every view!
For that, *structlog* gives you thread-local context storage based on the {mod}`contextvars` module:
```pycon
>>> structlog.contextvars.bind_contextvars(peer_ip="1.2.3.4")
>>> structlog.get_logger().info("something")
2022-10-10 10:18:05 [info ] something peer_ip=1.2.3.4
```
See {doc}`contextvars` for more information and a more complete example.
## Manipulating log entries in flight
Now that your log events are dictionaries, it's also much easier to manipulate them than if they were plain strings.
To facilitate that, *structlog* has the concept of {doc}`processor chains `.
A processor is a function that receives the event dictionary along with two other arguments and returns a new event dictionary that may or may not differ from the one it got passed.
The next processor in the chain receives that returned dictionary instead of the original one.
Let's assume you wanted to add a timestamp to every event dict.
The processor would look like this:
```{doctest}
>>> import datetime
>>> def timestamper(_, __, event_dict):
... event_dict["time"] = datetime.datetime.now().isoformat()
... return event_dict
```
Plain Python, plain dictionaries.
Now you have to tell *structlog* about your processor by {doc}`configuring ` it:
```{doctest}
>>> structlog.configure(processors=[timestamper, structlog.processors.KeyValueRenderer()])
>>> structlog.get_logger().info("hi") # doctest: +SKIP
event='hi' time='2018-01-21T09:37:36.976816'
```
## Rendering
Finally you want to have control over the actual format of your log entries.
As you may have noticed in the previous section, renderers are just processors too.
The type of the return value that is required from the renderer depends on the input that the *logger* that is wrapped by *structlog* needs.
While usually it's a string or bytes, there's no rule saying it _has_ to be a string!
So assuming you want to follow [best practices](logging-best-practices.md) and render your event dictionary to JSON that is picked up by a log aggregation system like ELK or Graylog, *structlog* comes with batteries included -- you just have to tell it to use its {class}`~structlog.processors.JSONRenderer`:
```{doctest}
>>> structlog.configure(processors=[structlog.processors.JSONRenderer()])
>>> structlog.get_logger().info("hi")
{"event": "hi"}
```
## *structlog* and standard library's `logging`
While *structlog*'s loggers are very fast and sufficient for the majority of our users, you're not bound to them.
Instead, it's been designed from day one to wrap your *existing* loggers and **add** *structure* and *incremental context building* to them.
The most prominent example of such an "existing logger" is certainly the logging module in the standard library.
To make this common case as simple as possible, *structlog* comes with [some tools](standard-library.md) to help you.
As noted before, the fastest way to transform *structlog* into a `logging`-friendly package is calling {func}`structlog.stdlib.recreate_defaults()`.
## asyncio
The default *bound logger* that you get back from {func}`structlog.get_logger()` and standard library's {class}`structlog.stdlib.BoundLogger` don't have just the familiar log methods like `debug()` or `info()`, but also their async cousins, that simply prefix the name with an a:
```pycon
>>> import asyncio
>>> logger = structlog.get_logger()
>>> async def f():
... await logger.ainfo("async hi!")
...
>>> logger.info("Loop isn't running yet, but we can log!")
2023-04-06 07:25:48 [info ] Loop isn't running yet, but we can log!
>>> asyncio.run(f())
2023-04-06 07:26:08 [info ] async hi!
```
You can use the sync and async logging methods interchangeably within the same application.
## Liked what you saw?
Now you're all set for the rest of the user's guide and can start reading about [bound loggers](bound-loggers.md) -- the heart of *structlog*.
```{include} ../README.md
:start-after:
:end-before:
```
[*better-exceptions*]: https://github.com/qix-/better-exceptions
[recipe]: https://docs.python.org/3/howto/logging-cookbook.html#implementing-structured-logging
[Rich]: https://github.com/Textualize/rich
structlog-25.1.0/docs/glossary.md 0000644 0000000 0000000 00000005121 14742155321 013673 0 ustar 00 # Glossary
Please feel free to [file an issue](https://github.com/hynek/structlog/issues) if you think some important concept is missing here.
:::{glossary}
Event Dictionary
Often abbreviated as *event dict*.
It's a dictionary that contains all the information that is logged, with the `event` key having the special role of being the name of the event.
It's the result of the values bound to the {term}`bound logger`'s context and the key-value pairs passed to the logging method.
It is then passed through the {term}`processor` chain that can add, modify, and even remove key-value pairs.
Bound Logger
An instance of a {class}`structlog.typing.BindableLogger` that is returned by either {func}`structlog.get_logger` or the bind/unbind/new methods on it.
As the name suggests, it's possible to bind key-value pairs to it -- this data is called the {term}`context` of the logger.
Its methods are the user's logging API and depend on the type of the bound logger.
The two most common implementations are {class}`structlog.BoundLogger` and {class}`structlog.stdlib.BoundLogger`.
Bound loggers are **immutable**.
The context can only be modified by creating a new bound logger using its `bind()`and `unbind()` methods.
:::{seealso}
{doc}`bound-loggers`
:::
Context
A dictionary of key-value pairs belonging to a {term}`bound logger`.
When a log entry is logged out, the context is the base for the event dictionary with the keyword arguments of the logging method call merged in.
Bound loggers are **immutable**, so it's not possible to modify a context directly.
But you can create a new bound logger with a different context using its `bind()` and `unbind()` methods.
Native Loggers
Loggers created using {func}`structlog.make_filtering_bound_logger` which includes the default configuration.
These loggers are very fast and do **not** use the standard library.
Wrapped Logger
The logger that is wrapped by *structlog* and that is responsible for the actual output.
By default it's a {class}`structlog.PrintLogger` for native logging.
Another popular choice is {class}`logging.Logger` for standard library logging.
:::{seealso}
{doc}`standard-library`
:::
Processor
A callable that is called on every log entry.
It receives the return value of its predecessor as an argument and returns a new event dictionary.
This allows for composable transformations of the event dictionary.
The result of the final processor is passed to the {term}`wrapped logger`.
:::{seealso}
{doc}`processors`
:::
:::
structlog-25.1.0/docs/index.md 0000644 0000000 0000000 00000006762 14742155321 013153 0 ustar 00 # structlog
*Simple. Powerful. Fast. Pick three.*
Release **{sub-ref}`release`** ([What's new?](https://github.com/hynek/structlog/blob/main/CHANGELOG.md))
---
```{include} ../README.md
:start-after:
:end-before:
```
```{include} ../README.md
:start-after:
:end-before:
```
If you’d like more information on why structured logging in general – and *structlog* in particular – are good ideas, we’ve prepared a [summary](why.md) just for you.
Otherwise, let’s dive right in!
```{toctree}
:hidden: true
why
```
## Basics
The first chapters teach you all you need to use *structlog* productively.
They build gently on each other, so ideally, read them in order.
If anything seems confusing, don't hesitate to have a look at our {doc}`glossary`!
```{toctree}
:maxdepth: 2
:caption: Basics
getting-started
bound-loggers
configuration
processors
contextvars
exceptions
```
## Development affordances
*structlog*'s focus is on production systems, but it comes with **pretty console logging** and handy in-development helpers both for your **comfort** and your code's **quality**.
```{toctree}
:maxdepth: 2
:caption: Development Affordances
console-output
testing
typing
```
(integration)=
## Integration with existing systems
*structlog* is both zero-config as well as highly configurable.
You can use it on its own or integrate with existing systems.
Dedicated support for the standard library and Twisted is shipped out-of-the-box.
```{toctree}
:maxdepth: 2
:caption: Integrations
frameworks
standard-library
twisted
```
## *structlog* in practice
The following chapters deal with considerations of using *structlog* in the real world.
```{toctree}
:maxdepth: 2
:caption: In Practice
recipes
logging-best-practices
performance
```
## Reference
```{toctree}
:maxdepth: 2
:caption: Reference
api
glossary
genindex
modindex
```
## Deprecated features
```{toctree}
:maxdepth: 1
:caption: Deprecated Features
thread-local
```
```{toctree}
:hidden:
:caption: Meta
license
PyPI
GitHub
Changelog
Contributing
Security Policy
Funding
```
structlog-25.1.0/docs/license.md 0000644 0000000 0000000 00000002016 14742155321 013452 0 ustar 00 # License and Hall of Fame
*structlog* is licensed both under the [Apache License, Version 2](https://choosealicense.com/licenses/apache/) and the [MIT license](https://choosealicense.com/licenses/mit/).
Any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
---
The reason for that is to be both protected against patent claims by own contributors and still allow the usage within GPLv2 software. For more legal details, see [this issue](https://github.com/pyca/cryptography/issues/1209) on the bug tracker of PyCA's *cryptography* project.
The full license texts can be also found in the source code repository:
- [Apache License 2.0](https://github.com/hynek/structlog/blob/main/LICENSE-APACHE)
- [MIT](https://github.com/hynek/structlog/blob/main/LICENSE-MIT)
## Credits
```{include} ../README.md
:parser: myst_parser.sphinx_
:start-after: "## Credits"
:end-before:
```
structlog-25.1.0/docs/logging-best-practices.md 0000644 0000000 0000000 00000011475 14742155321 016375 0 ustar 00 # Logging Best Practices
Logging is not a new concept and is in no way unique to Python.
Logfiles have existed for decades, and there's little reason to reinvent the wheel in our little world.
Therefore, let's rely on proven tools as much as possible and do only the bare minimum inside Python applications[^unix].
A simple but powerful approach is to log to unbuffered [standard out](https://en.wikipedia.org/wiki/Standard_out#Standard_output_.28stdout.29
) and let other tools take care of the rest.
That can be your terminal window while developing; it can be [*systemd*](https://en.wikipedia.org/wiki/Systemd) redirecting your log entries to [*syslogd*](https://en.wikipedia.org/wiki/Syslogd) and rotating them using [*logrotate*](https://github.com/logrotate/logrotate); or it can be your [cluster manager](https://kubernetes.io/docs/concepts/cluster-administration/logging/) forwarding them to an obscenely expensive log aggregator service.
It doesn't matter where or how your application runs -- it just works, and the reason why the popular [*Twelve-Factor App* methodology](https://12factor.net/logs) suggests just that.
[^unix]: This is obviously a privileged UNIX-centric view but even Windows has tools and means for log management although we won't be able to discuss them here.
## Canonical log lines
Generally speaking, having as few log entries per request as possible is a good thing.
The less noise, the more insights.
*structlog*'s ability to {ref}`bind data to loggers incrementally ` -- plus {doc}`loggers that are local to the current execution context ` -- can help you to minimize the output to a *single log entry*.
At Stripe, this concept is called [Canonical Log Lines](https://brandur.org/canonical-log-lines).
## Pretty printing vs. structured output
Colorful and pretty printed log messages are nice during development when you locally run your code.
However, in production you should emit structured output (like JSON) which is a lot easier to parse by log aggregators.
Since you already log in a structured way, writing JSON output with *structlog* comes naturally.
You can even generate structured exception tracebacks.
This makes analyzing errors easier, since log aggregators can render JSON much better than multiline strings with a lot escaped quotation marks.
Here is a simple example of how you can have pretty logs during development and JSON output when your app is running in a production context:
```{doctest}
>>> import sys
>>> import structlog
>>>
>>> shared_processors = [
... # Processors that have nothing to do with output,
... # e.g., add timestamps or log level names.
... ]
>>> if sys.stderr.isatty():
... # Pretty printing when we run in a terminal session.
... # Automatically prints pretty tracebacks when "rich" is installed
... processors = shared_processors + [
... structlog.dev.ConsoleRenderer(),
... ]
... else:
... # Print JSON when we run, e.g., in a Docker container.
... # Also print structured tracebacks.
... processors = shared_processors + [
... structlog.processors.dict_tracebacks,
... structlog.processors.JSONRenderer(),
... ]
>>> structlog.configure(processors)
```
## Centralized logging
Nowadays you usually don't want your log files in compressed archives distributed over dozens -- if not thousands -- of servers or cluster nodes.
You want them in a single location.
Parsed, indexed, and easy to search.
### ELK
The ELK stack ([**E**lasticsearch][elasticsearch], [**L**ogstash][logstash], [**K**ibana][kibana]) from Elastic is a great way to store, parse, and search your logs.
The way it works is that you have local log shippers like [Filebeat] that parse your log files and forward the log entries to your [Logstash] server.
Logstash parses the log entries and stores them in [Elasticsearch].
Finally, you can view and search them in [Kibana].
If your log entries consist of a JSON dictionary, this is fairly easy and efficient.
All you have to do is to tell [Logstash] either that your log entries are prepended with a timestamp from {class}`~structlog.processors.TimeStamper` or the name of your timestamp field.
### Graylog
[Graylog](https://graylog.org/) goes one step further.
It not only supports everything those above do (and then some); you can also directly log JSON entries towards it -- optionally even through an AMQP server (like [RabbitMQ](https://www.rabbitmq.com/)) for better reliability.
Additionally, [Graylog's Extended Log Format](https://go2docs.graylog.org/current/getting_in_log_data/gelf.html) (GELF) allows for structured data which makes it an obvious choice to use together with *structlog*.
[elasticsearch]: https://www.elastic.co/elasticsearch
[filebeat]: https://github.com/elastic/beats/tree/main/filebeat
[kibana]: https://www.elastic.co/kibana
[logstash]: https://www.elastic.co/logstash
structlog-25.1.0/docs/make.bat 0000644 0000000 0000000 00000011756 14742155321 013126 0 ustar 00 @ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^` where ^ is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\structlog.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\structlog.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end
structlog-25.1.0/docs/performance.md 0000644 0000000 0000000 00000010556 14742155321 014341 0 ustar 00 # Performance
Here are a few hints how to get the best performance out of *structlog* in production:
- Use *structlog*'s native *BoundLogger* (created using {func}`structlog.make_filtering_bound_logger`) if you want to use level-based filtering.
`return None` is hard to beat.
- Avoid (frequently) calling log methods on loggers you get back from {func}`structlog.get_logger` or {func}`structlog.wrap_logger`.
Since those functions are usually called in module scope and thus before you are able to configure them, they return a proxy object that assembles the correct logger on demand.
Create a local logger if you expect to log frequently without binding:
```python
logger = structlog.get_logger()
def f():
log = logger.bind()
for i in range(1000000000):
log.info("iterated", i=i)
```
Since global scope lookups are expensive in Python, it's generally a good idea to copy frequently-used symbols into local scope.
- Set the *cache_logger_on_first_use* option to `True` so the aforementioned on-demand loggers will be assembled only once and cached for future uses:
```python
configure(cache_logger_on_first_use=True)
```
This has two drawbacks:
1. Later calls of {func}`~structlog.configure` don't have any effect on already cached loggers -- that shouldn't matter outside of {doc}`testing ` though.
2. The resulting bound logger is not pickleable.
Therefore, you can't set this option if you, for example, plan on passing loggers around using {mod}`multiprocessing`.
- Avoid sending your log entries through the standard library if you can: its dynamic nature and flexibility make it a major bottleneck.
Instead use {class}`structlog.WriteLoggerFactory` or -- if your serializer returns bytes (for example, [*orjson*] or [*msgspec*]) -- {class}`structlog.BytesLoggerFactory`.
You can still configure `logging` for packages that you don't control, but avoid it for your *own* log entries.
- Configure {class}`~structlog.processors.JSONRenderer` to use a faster JSON serializer than the standard library.
Possible alternatives are among others are [*orjson*], [*msgspec*], or [RapidJSON](https://pypi.org/project/python-rapidjson/).
- Be conscious about whether and how you use *structlog*'s *asyncio* support.
While it's true that moving log processing into separate threads prevents your application from hanging, it also comes with a performance cost.
Decide judiciously whether or not you're willing to pay that price.
If your processor chain has a good and predictable performance without external dependencies (as it should), it might not be worth it.
## Example
Here's an example for a production-ready *structlog* configuration that's as fast as it gets:
```python
import logging
import orjson
import structlog
structlog.configure(
cache_logger_on_first_use=True,
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.format_exc_info,
structlog.processors.TimeStamper(fmt="iso", utc=True),
structlog.processors.JSONRenderer(serializer=orjson.dumps),
],
logger_factory=structlog.BytesLoggerFactory(),
)
```
It has the following properties:
- Caches all loggers on first use.
- Filters all log entries below the `info` log level **very** efficiently.
The `debug` method literally consists of `return None`.
- Supports {doc}`contextvars` (thread-local contexts outside of *asyncio*).
- Adds the log level name.
- Renders exceptions into the `exception` key.
- Adds an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) timestamp under the `timestamp` key in the UTC timezone.
- Renders the log entries as JSON using [*orjson*] which is faster than *plain* logging in {mod}`logging`.
- Uses {class}`structlog.BytesLoggerFactory` because *orjson* returns bytes.
That saves encoding ping-pong.
Therefore a log entry might look like this:
```json
{"event":"hello","level":"info","timestamp":"2023-11-02T08:03:38.298565Z"}
```
---
If you need standard library support for external projects, you can either just use a JSON formatter like [*python-json-logger*](https://pypi.org/project/python-json-logger/), or pipe them through *structlog* as documented in {doc}`standard-library`.
[*orjson*]: https://github.com/ijl/orjson
[*msgspec*]: https://jcristharif.com/msgspec/
structlog-25.1.0/docs/processors.md 0000644 0000000 0000000 00000013562 14742155321 014242 0 ustar 00 # Processors
The true power of *structlog* lies in its *combinable log processors*.
A log processor is a regular callable or in other words:
A function or an instance of a class with a `__call__()` method.
(chains)=
## Chains
The *processor chain* is a list of processors.
Each processors receives three positional arguments:
**logger**
: Your wrapped logger object.
For example {class}`logging.Logger` or {class}`structlog.typing.FilteringBoundLogger` (default).
**method_name**
: The name of the wrapped method.
If you called `log.warning("foo")`, it will be `"warning"`.
**event_dict**
: Current context together with the current event.
If the context was `{"a": 42}` and the event is `"foo"`, the initial `event_dict` will be `{"a":42, "event": "foo"}`.
The return value of each processor is passed on to the next one as `event_dict` until finally the return value of the last processor gets passed into the wrapped logging method.
:::{note}
*structlog* only looks at the return value of the **last** processor.
That means that as long as you control the next processor in the chain (the processor that will get your return value passed as an argument), you can return whatever you want.
Returning a modified event dictionary from your processors is just a convention to make processors composable.
:::
### Examples
If you set up your logger like:
```python
structlog.configure(processors=[f1, f2, f3])
log = structlog.get_logger().bind(x=42)
```
and call `log.info("some_event", y=23)`, it results in the following call chain:
```python
wrapped_logger.info(
f3(wrapped_logger, "info",
f2(wrapped_logger, "info",
f1(wrapped_logger, "info", {"event": "some_event", "x": 42, "y": 23})
)
)
)
```
In this case, `f3` has to make sure it returns something `wrapped_logger.info` can handle (see {ref}`adapting`).
For the example with `PrintLogger` above, this means `f3` must return a string.
The simplest modification a processor can make is adding new values to the `event_dict`.
Parsing human-readable timestamps is tedious, not so [UNIX timestamps](https://en.wikipedia.org/wiki/UNIX_time) -- let's add one to each log entry:
```python
import calendar
import time
def timestamper(logger, log_method, event_dict):
event_dict["timestamp"] = calendar.timegm(time.gmtime())
return event_dict
```
:::{important}
You're explicitly allowed to modify the `event_dict` parameter, because a copy has been created before calling the first processor.
:::
Please note that *structlog* comes with such a processor built in: {class}`~structlog.processors.TimeStamper`.
## Filtering
If a processor raises {class}`structlog.DropEvent`, the event is silently dropped.
Therefore, the following processor drops every entry:
```python
from structlog import DropEvent
def dropper(logger, method_name, event_dict):
raise DropEvent
```
But we can do better than that!
(cond-drop)=
How about dropping only log entries that are marked as coming from a certain peer (for example, monitoring)?
```python
class ConditionalDropper:
def __init__(self, peer_to_ignore):
self._peer_to_ignore = peer_to_ignore
def __call__(self, logger, method_name, event_dict):
"""
>>> cd = ConditionalDropper("127.0.0.1")
>>> cd(None, "", {"event": "foo", "peer": "10.0.0.1"})
{'peer': '10.0.0.1', 'event': 'foo'}
>>> cd(None, "", {"event": "foo", "peer": "127.0.0.1"})
Traceback (most recent call last):
...
DropEvent
"""
if event_dict.get("peer") == self._peer_to_ignore:
raise DropEvent
return event_dict
```
Since it's so common to filter by the log level, *structlog* comes with {func}`structlog.make_filtering_bound_logger` that filters log entries before they even enter the processor chain.
It does **not** use the standard library, but it does use its names and order of log levels.
(adapting)=
## Adapting and rendering
An important role is played by the *last* processor because its duty is to adapt the `event_dict` into something the logging methods of the *wrapped logger* understand.
With that, it's also the *only* processor that needs to know anything about the underlying system.
It can return one of three types:
- An Unicode string ({any}`str`), a bytes string ({any}`bytes`), or a {any}`bytearray` that is passed as the first (and only) positional argument to the underlying logger.
- A tuple of `(args, kwargs)` that are passed as `log_method(*args, **kwargs)`.
- A dictionary which is passed as `log_method(**kwargs)`.
Therefore `return "hello world"` is a shortcut for `return (("hello world",), {})` (the example in {ref}`chains` assumes this shortcut has been taken).
This should give you enough power to use *structlog* with any logging system while writing agnostic processors that operate on dictionaries.
:::{versionchanged} 14.0.0 Allow final processor to return a {any}`dict`.
:::
:::{versionchanged} 20.2.0 Allow final processor to return a {any}`bytes`.
:::
:::{versionchanged} 21.2.0 Allow final processor to return a {any}`bytearray`.
:::
### Examples
The probably most useful formatter for string based loggers is {class}`structlog.processors.JSONRenderer`.
Advanced log aggregation and analysis tools like [*Logstash*](https://www.elastic.co/logstash) offer features like telling them "this is JSON, deal with it" instead of fiddling with regular expressions.
For a list of shipped processors, check out the {ref}`API documentation `.
## Third-Party packages
*structlog* was specifically designed to be as composable and reusable as possible, so whatever you're missing:
chances are, you can solve it with a processor!
Since processors are self-contained callables, it's easy to write your own and to share them in separate packages.
We collect those packages in our [GitHub Wiki](https://github.com/hynek/structlog/wiki/Third-Party-Extensions) and encourage you to add your package too!
structlog-25.1.0/docs/recipes.md 0000644 0000000 0000000 00000016233 14742155321 013470 0 ustar 00 # Recipes
Because *structlog* is entirely based on dictionaries and callables, the sky is the limit with what you can achieve.
That can be daunting in the beginning, so here are a few examples of tasks that have come up repeatedly.
Please note that recipes related to integration with frameworks have an [own chapter](frameworks.md).
(rename-event)=
## Renaming the `event` key
The name of the event is hard-coded in *structlog* to `event`.
But that doesn't mean it has to be called that in your logs.
With the {class}`structlog.processors.EventRenamer` processor, you can, for instance, rename the log message to `msg` and use `event` for something custom, that you bind to `_event` in your code:
```pycon
>>> from structlog.processors import EventRenamer
>>> event_dict = {"event": "something happened", "_event": "our event!"}
>>> EventRenamer("msg", "_event")(None, "", event_dict)
{'msg': 'something happened', 'event': 'our event!'}
```
(finer-filtering)=
## Fine-grained log-level filtering
*structlog*'s native log levels as provided by {func}`structlog.make_filtering_bound_logger` only know **one** log level – the one that is passed to `make_filtering_bound_logger()`.
Sometimes, that can be a bit too coarse, though.
You can achieve finer control by adding the {class}`~structlog.processors.CallsiteParameterAdder` processor and writing a simple processor that acts on the call site data added.
Let's assume you have the following code:
```python
logger = structlog.get_logger()
def f():
logger.info("f called")
def g():
logger.info("g called")
f()
g()
```
And you don't want to see log entries from function `f`.
You add {class}`~structlog.processors.CallsiteParameterAdder` to the processor chain and then look at the `func_name` field in the *event dict*:
```python
def filter_f(_, __, event_dict):
if event_dict.get("func_name") == "f":
raise structlog.DropEvent
return event_dict
structlog.configure(
processors=[
structlog.processors.CallsiteParameterAdder(
[structlog.processors.CallsiteParameter.FUNC_NAME]
),
filter_f, # <-- your processor!
structlog.processors.KeyValueRenderer(),
]
)
```
Running this gives you:
```
event='g called' func_name='g'
```
{class}`~structlog.processors.CallsiteParameterAdder` is *very* powerful in what info it can add, so your possibilities are limitless.
Pick the data you're interested in from the {class}`structlog.processors.CallsiteParameter` {class}`~enum.Enum`.
(custom-wrappers)=
## Custom wrappers
```{testsetup}
import structlog
structlog.configure(
processors=[structlog.processors.KeyValueRenderer()],
)
```
```{testcleanup}
import structlog
structlog.reset_defaults()
```
The type of the *bound loggers* that are returned by {func}`structlog.get_logger()` is called the *wrapper class*, because it wraps the original logger that takes care of the output.
This wrapper class is [configurable](configuration.md).
Originally, *structlog* used a generic wrapper class {class}`structlog.BoundLogger` by default.
That class still ships with *structlog* and can wrap *any* logger class by intercepting unknown method names and proxying them to the wrapped logger.
Nowadays, the default is a {class}`structlog.typing.FilteringBoundLogger` that imitates standard library’s log levels with the possibility of efficiently filtering at a certain level (inactive log methods are a plain `return None` each).
If you’re integrating with {mod}`logging` or Twisted, you may want to use one of their specific *bound loggers* ({class}`structlog.stdlib.BoundLogger` and {class}`structlog.twisted.BoundLogger`, respectively).
---
On top of that all, you can also write your own wrapper classes.
To make it easy for you, *structlog* comes with the class {class}`structlog.BoundLoggerBase` which takes care of all data binding duties so you just add your log methods if you choose to sub-class it.
(wrapper-class-example)=
### Example
It’s easiest to demonstrate with an example:
```{doctest}
>>> from structlog import BoundLoggerBase, PrintLogger, wrap_logger
>>> class SemanticLogger(BoundLoggerBase):
... def info(self, event, **kw):
... if not "status" in kw:
... return self._proxy_to_logger("info", event, status="ok", **kw)
... else:
... return self._proxy_to_logger("info", event, **kw)
...
... def user_error(self, event, **kw):
... self.info(event, status="user_error", **kw)
>>> log = wrap_logger(PrintLogger(), wrapper_class=SemanticLogger)
>>> log = log.bind(user="fprefect")
>>> log.user_error("user.forgot_towel")
user='fprefect' status='user_error' event='user.forgot_towel'
```
You can observe the following:
- The wrapped logger can be found in the instance variable {attr}`structlog.BoundLoggerBase._logger`.
- The helper method {meth}`structlog.BoundLoggerBase._proxy_to_logger` that is a [DRY] convenience function that runs the processor chain, handles possible {class}`structlog.DropEvent`s and calls a named function on `_logger`.
- You can run the chain by hand through using {meth}`structlog.BoundLoggerBase._process_event` .
These two methods and one attribute are all you need to write own *bound loggers*.
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
## Passing context to worker threads
Thread-local context data based on [context variables](contextvars.md) is -- as the name says -- local to the thread that binds it.
When using threads to process work in parallel, you have to pass the thread-local context **into** the worker threads.
One way is to retrieve the context vars and pass them along to the worker threads.
Then, Inside of the worker, re-bind them using `bind_contextvars`.
The following example uses [*pathos*](https://pypi.org/project/pathos/) to create a `ThreadPool`.
The context variables are retrieved and passed as the first argument to the partial function.
The pool invokes the partial function, once for each element of `workers`.
Inside of `do_some_work`, the context vars are bound and a message about the great work being performed is logged -- including the `request_id` key / value pair.
```
from functools import partial
import structlog
from structlog.contextvars import bind_contextvars
from pathos.threading import ThreadPool
logger = structlog.get_logger(__name__)
def do_some_work(ctx, this_worker):
bind_contextvars(**ctx)
logger.info("WorkerDidSomeWork", worker=this_worker)
def structlog_with_threadpool(f):
ctx = structlog.contextvars.get_contextvars()
func = partial(f, ctx)
workers = ["1", "2", "3"]
with ThreadPool() as pool:
return list(pool.map(func, workers))
def manager(request_id: str):
bind_contextvars(request_id=request_id)
logger.info("StartingWorkers")
structlog_with_threadpool(do_some_work)
```
See the [issue 425](https://github.com/hynek/structlog/issues/425) for a more complete example.
## Switching console output to standard error
When using structlog without standard library integration and want the log output to go to standard error (*stderr*) instead of standard out (*stdout*), you can switch with a single line of configuration:
```python
structlog.configure(logger_factory=structlog.PrintLoggerFactory(sys.stderr))
```
structlog-25.1.0/docs/standard-library.md 0000644 0000000 0000000 00000054061 14742155321 015301 0 ustar 00 # Standard Library Logging
Ideally, *structlog* should be able to be used as a drop-in replacement for standard library's {mod}`logging` by wrapping it.
In other words, you should be able to replace your call to {func}`logging.getLogger` by a call to {func}`structlog.get_logger` and things should keep working as before (if *structlog* is configured right, see {ref}`stdlib-config` below).
If you run into incompatibilities, it is a *bug* so please take the time to [report it](https://github.com/hynek/structlog/issues)!
If you're a heavy `logging` user, your [help](https://github.com/hynek/structlog/issues?q=is%3Aopen+is%3Aissue+label%3Astdlib) to ensure a better compatibility would be highly appreciated!
:::{important}
The quickest way to get started with *structlog* and `logging` is {func}`structlog.stdlib.recreate_defaults()`.
It will recreate the default configuration on top of `logging` and optionally configure `logging` for you.
:::
## Just enough `logging`
If you want to use *structlog* with `logging`, you should have at least a fleeting understanding on how the standard library operates because *structlog* will *not* do any magic things in the background for you.
Most importantly you have to *configure* the `logging` system *additionally* to configuring *structlog*.
Usually it is enough to use:
```
import logging
import sys
logging.basicConfig(
format="%(message)s",
stream=sys.stdout,
level=logging.INFO,
)
```
This will send all log messages with the [log level](https://docs.python.org/3/library/logging.html#logging-levels) `logging.INFO` and above (that means that, for example, `logging.debug` calls are ignored) to standard out without any special formatting by the standard library.
If you require more complex behavior, please refer to the standard library's `logging` documentation.
## Concrete bound logger
*structlog* ships a stdlib-specific [*bound logger*](bound-loggers.md) that mirrors the log methods of standard library's {any}`logging.Logger` with correct type hints.
If you want to take advantage of said type hints, you have to either annotate the logger coming from {func}`structlog.get_logger`, or use {func}`structlog.stdlib.get_logger()` that has the appropriate type hints.
Please note though, that it will neither configure nor verify your configuration.
It will call `structlog.get_logger()` just like if you would've called it -- the only difference are the type hints.
See also {doc}`typing`.
### `asyncio`
For `asyncio` applications, you may not want your whole application to block while the processor chain is formatting your log entries.
For that use case *structlog* comes with a set of non-standard methods that will do all processing in a thread pool executor.
They have the same names as the regular methods, except they are prefixed by an `a`.
So instead of `logger.info("event!")` you write `await logger.ainfo("event!)`.
No extra configuration is necessary and you can mix-and-match both types of methods within the same application.
This means an increased computational cost per log entry, but your application will not block because of logging.
```{versionadded} 23.1.0
```
---
*structlog* also comes with {class}`structlog.stdlib.AsyncBoundLogger` that blankly makes all logging methods asynchronous (in other words, you have to use `await log.info()` instead of just `log.info()`).
To use it, {doc}`configure ` *structlog* to use `AsyncBoundLogger` as `wrapper_class`.
```{versionadded} 20.2.0
```
```{deprecated} 23.1.0
```
## Processors
*structlog* comes with a few standard library-specific processors:
{func}`~structlog.stdlib.render_to_log_args_and_kwargs`:
: Renders the event dictionary into positional and keyword arguments for `logging.Logger` logging methods.
This is useful if you want to render your log entries entirely within `logging`.
{func}`~structlog.stdlib.render_to_log_kwargs`:
: Same as above, but does not support passing positional arguments from *structlog* loggers to `logging.Logger` logging methods as positional arguments.
*structlog* positional arguments are still passed to `logging` under `positional_args` key of `extra` keyword argument.
{func}`~structlog.stdlib.filter_by_level`:
: Checks the log entry's log level against the configuration of standard library's logging.
Log entries below the threshold get silently dropped.
Put it at the beginning of your processing chain to avoid expensive operations from happening in the first place.
{func}`~structlog.stdlib.add_logger_name`:
: Adds the name of the logger to the event dictionary under the key `logger`.
{func}`~structlog.stdlib.add_log_level`:
: Adds the log level to the event dictionary under the key `level`.
{func}`~structlog.stdlib.add_log_level_number`:
: Adds the log level number to the event dictionary under the key `level_number`.
Log level numbers map to the log level names.
The Python stdlib uses them for filtering logic.
This adds the same numbers so users can leverage similar filtering.
Compare:
```
level in ("warning", "error", "critical")
level_number >= 30
```
The mapping of names to numbers is in `structlog.stdlib._NAME_TO_LEVEL`.
{class}`~structlog.stdlib.ExtraAdder`:
: Add extra attributes of `logging.LogRecord` objects to the event dictionary.
This processor can be used for adding data passed in the `extra` parameter of the `logging` module's log methods to the event dictionary.
{func}`~structlog.stdlib.PositionalArgumentsFormatter`:
: This processes and formats positional arguments (if any) passed to log methods in the same way the `logging` module would do, for example, `logger.info("Hello, %s", name)`.
*structlog* also comes with {class}`~structlog.stdlib.ProcessorFormatter` which is a `logging.Formatter` that enables you to format non-*structlog* log entries using *structlog* renderers *and* multiplex *structlog*’s output with different renderers (see [below](processor-formatter) for an example).
(stdlib-config)=
## Suggested configurations
:::{note}
We do appreciate that fully integrating *structlog* with standard library's `logging` is fiddly when done for the first time.
This is the price of flexibility and unfortunately -- given the different needs of our users -- we can't make it any simpler without compromising someone's use-cases.
However, once it is set up, you can rely on not having to ever touch it again.
:::
Depending *where* you'd like to do your formatting, you can take one of four approaches:
### Don't integrate
The most straight-forward option is to configure standard library `logging` close enough to what *structlog* is logging and leaving it at that.
Since these are usually log entries from third parties that don't take advantage of *structlog*'s features, this is surprisingly often a perfectly adequate approach.
For instance, if you log JSON in production, configure `logging` to use [*python-json-logger*] to make it print JSON too, and then tweak the configuration to match their outputs.
You can also use {class}`~structlog.stdlib.ProcessorFormatter` as a formatter for `logging` to get the same output for both *structlog* and `logging` log entries -- see [below](processor-formatter) for an example.
:::{note}
If you want to use same file (for example, `sys.stdout` or `sys.stderr`) for both *structlog* and `logging.StreamHandler` output, you must use {class}`~structlog.WriteLogger` instead of {class}`~structlog.PrintLogger`.
This is because {class}`~structlog.PrintLogger` uses `print(log, file=file, flush=True)` to write log, and `print` writes the `log` message and a newline ("\n") to the stream separately.
This can cause interleaving of log entries from *structlog* and `logging` loggers.
{class}`~structlog.WriteLogger` writes log entries atomically to the file (for example, `file.write(log+"\n")`).
:::
### Rendering within *structlog*
This is the simplest approach where *structlog* does all the heavy lifting and passes a fully-formatted string to `logging`.
Chances are, this is all you need.
```{mermaid}
:align: center
flowchart TD
User
structlog
stdlib[Standard Library\ne.g. logging.StreamHandler]
User --> |"structlog.get_logger().info('foo')"| structlog
User --> |"logging.getLogger().info('foo')"| stdlib
structlog --> |"logging.getLogger().info(#quot;{'event': 'foo'}#quot;)"| stdlib ==> Output
Output
```
A basic configuration to output structured logs in JSON format looks like this:
```python
import structlog
structlog.configure(
processors=[
# If log level is too low, abort pipeline and throw away log entry.
structlog.stdlib.filter_by_level,
# Add the name of the logger to event dict.
structlog.stdlib.add_logger_name,
# Add log level to event dict.
structlog.stdlib.add_log_level,
# Perform %-style formatting.
structlog.stdlib.PositionalArgumentsFormatter(),
# Add a timestamp in ISO 8601 format.
structlog.processors.TimeStamper(fmt="iso"),
# If the "stack_info" key in the event dict is true, remove it and
# render the current stack trace in the "stack" key.
structlog.processors.StackInfoRenderer(),
# If the "exc_info" key in the event dict is either true or a
# sys.exc_info() tuple, remove "exc_info" and render the exception
# with traceback into the "exception" key.
structlog.processors.format_exc_info,
# If some value is in bytes, decode it to a Unicode str.
structlog.processors.UnicodeDecoder(),
# Add callsite parameters.
structlog.processors.CallsiteParameterAdder(
{
structlog.processors.CallsiteParameter.FILENAME,
structlog.processors.CallsiteParameter.FUNC_NAME,
structlog.processors.CallsiteParameter.LINENO,
}
),
# Render the final event dict as JSON.
structlog.processors.JSONRenderer()
],
# `wrapper_class` is the bound logger that you get back from
# get_logger(). This one imitates the API of `logging.Logger`.
wrapper_class=structlog.stdlib.BoundLogger,
# `logger_factory` is used to create wrapped loggers that are used for
# OUTPUT. This one returns a `logging.Logger`. The final value (a JSON
# string) from the final processor (`JSONRenderer`) will be passed to
# the method of the same name as that you've called on the bound logger.
logger_factory=structlog.stdlib.LoggerFactory(),
# Effectively freeze configuration after creating the first bound
# logger.
cache_logger_on_first_use=True,
)
```
To make your program behave like a proper [*12 Factor App*](https://12factor.net/logs) that outputs only JSON to `stdout`, configure the `logging` module like this:
```
import logging
import sys
logging.basicConfig(
format="%(message)s",
stream=sys.stdout,
level=logging.INFO,
)
```
In this case *only* your own logs are formatted as JSON:
```pycon
>>> structlog.get_logger("test").warning("hello")
{"event": "hello", "logger": "test", "level": "warning", "timestamp": "2017-03-06T07:39:09.518720Z"}
>>> logging.getLogger("test").warning("hello")
hello
```
### Rendering using `logging`-based formatters
You can choose to use *structlog* only for building the event dictionary and leave all formatting -- additionally to the output -- to the standard library.
```{mermaid}
:align: center
flowchart TD
User
structlog
stdlib[Standard Library\ne.g. logging.StreamHandler]
User --> |"structlog.get_logger().info('foo', bar=42)"| structlog
User --> |"logging.getLogger().info('foo')"| stdlib
structlog --> |"logging.getLogger().info('foo', extra={"bar": 42})"| stdlib ==> Output
Output
```
```python
import structlog
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
# Transform event dict into `logging.Logger` method arguments.
# "event" becomes "msg" and the rest is passed as a dict in
# "extra". IMPORTANT: This means that the standard library MUST
# render "extra" for the context to appear in log entries! See
# warning below.
structlog.stdlib.render_to_log_kwargs,
],
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
```
Now you have the event dict available within each log record.
If you want *all* your log entries (meaning: also those not from your application / *structlog*) to be formatted as JSON, you can use the [*python-json-logger*] library:
```python
import logging
import sys
from pythonjsonlogger import jsonlogger
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(jsonlogger.JsonFormatter())
root_logger = logging.getLogger()
root_logger.addHandler(handler)
```
Now both *structlog* and `logging` will emit JSON logs:
```pycon
>>> structlog.get_logger("test").warning("hello")
{"message": "hello", "logger": "test", "level": "warning"}
>>> logging.getLogger("test").warning("hello")
{"message": "hello"}
```
:::{warning}
With this approach, it's the standard library `logging` formatter's duty to do something useful with the event dict.
In the above example that's `jsonlogger.JsonFormatter`.
Keep this in mind if you only get the event name without any context, and exceptions are ostensibly swallowed.
:::
(processor-formatter)=
### Rendering using *structlog*-based formatters within `logging`
Finally, the most ambitious approach.
Here, you use *structlog*'s {class}`~structlog.stdlib.ProcessorFormatter` as a {any}`logging.Formatter` for both `logging` as well as *structlog* log entries.
Consequently, the output is the duty of the standard library too.
```{mermaid}
:align: center
flowchart TD
User
structlog
structlog2[structlog]
stdlib["Standard Library"]
User --> |"structlog.get_logger().info(#quot;foo#quot;, bar=42)"| structlog
User --> |"logging.getLogger().info(#quot;foo#quot;)"| stdlib
structlog --> |"logging.getLogger().info(event_dict, {#quot;extra#quot;: {#quot;_logger#quot;: logger, #quot;_name#quot;: name})"| stdlib
stdlib --> |"structlog.stdlib.ProcessorFormatter.format(logging.Record)"| structlog2
structlog2 --> |"Returns a string that is passed into logging handlers.\nThis flow is controlled by the logging configuration."| stdlib2
stdlib2[Standard Library\ne.g. logging.StreamHandler] ==> Output
```
{class}`~structlog.stdlib.ProcessorFormatter` has two parts to its API:
1. On the *structlog* side, the {doc}`processor chain ` must be configured to end with {func}`structlog.stdlib.ProcessorFormatter.wrap_for_formatter` as the renderer.
It converts the processed event dictionary into something that `ProcessorFormatter` understands.
2. On the `logging` side, you must configure `ProcessorFormatter` as your formatter of choice.
`logging` then calls `ProcessorFormatter`'s `format()` method.
For that, `ProcessorFormatter` wraps a processor chain that is responsible for rendering your log entries to strings.
Thus, the simplest possible configuration looks like the following:
```python
import logging
import structlog
structlog.configure(
processors=[
# Prepare event dict for `ProcessorFormatter`.
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
)
formatter = structlog.stdlib.ProcessorFormatter(
processors=[structlog.dev.ConsoleRenderer()],
)
handler = logging.StreamHandler()
# Use OUR `ProcessorFormatter` to format all `logging` entries.
handler.setFormatter(formatter)
root_logger = logging.getLogger()
root_logger.addHandler(handler)
root_logger.setLevel(logging.INFO)
```
which will allow both of these to work in other modules:
```pycon
>>> import logging
>>> import structlog
>>> logging.getLogger("stdlog").info("woo")
woo _from_structlog=False _record=
>>> structlog.get_logger("structlog").info("amazing", events="oh yes")
amazing _from_structlog=True _record= events=oh yes
```
Of course, you probably want timestamps and log levels in your output.
The `ProcessorFormatter` has a `foreign_pre_chain` argument which is responsible for adding properties to events from the standard library -- in other words, those that do not originate from a *structlog* logger -- and which should in general match the `processors` argument to {func}`structlog.configure` so you get a consistent output.
`_from_structlog` and `_record` allow your processors to determine whether the log entry is coming from *structlog*, and to extract information from `logging.LogRecord`s and add them to the event dictionary.
However, you probably don't want to have them in your log files, thus we've added the `ProcessorFormatter.remove_processors_meta` processor to do so conveniently.
For example, to add timestamps, log levels, and traceback handling to your logs without `_from_structlog` and `_record` noise you should do:
```python
timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S")
shared_processors = [
structlog.stdlib.add_log_level,
timestamper,
]
structlog.configure(
processors=shared_processors + [
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
formatter = structlog.stdlib.ProcessorFormatter(
# These run ONLY on `logging` entries that do NOT originate within
# structlog.
foreign_pre_chain=shared_processors,
# These run on ALL entries after the pre_chain is done.
processors=[
# Remove _record & _from_structlog.
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.dev.ConsoleRenderer(),
],
)
```
which (given the same `logging.*` calls as in the previous example) will result in:
```pycon
>>> logging.getLogger("stdlog").info("woo")
2021-11-15 11:41:47 [info ] woo
>>> structlog.get_logger("structlog").info("amazing", events="oh yes")
2021-11-15 11:41:47 [info ] amazing events=oh yes
```
This allows you to set up some sophisticated logging configurations.
For example, to use the standard library's `logging.config.dictConfig` to log colored logs to the console and plain logs to a file you could do:
```python
import logging.config
import structlog
timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S")
pre_chain = [
# Add the log level and a timestamp to the event_dict if the log entry
# is not from structlog.
structlog.stdlib.add_log_level,
# Add extra attributes of LogRecord objects to the event dictionary
# so that values passed in the extra parameter of log methods pass
# through to log output.
structlog.stdlib.ExtraAdder(),
timestamper,
]
def extract_from_record(_, __, event_dict):
"""
Extract thread and process names and add them to the event dict.
"""
record = event_dict["_record"]
event_dict["thread_name"] = record.threadName
event_dict["process_name"] = record.processName
return event_dict
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"plain": {
"()": structlog.stdlib.ProcessorFormatter,
"processors": [
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.dev.ConsoleRenderer(colors=False),
],
"foreign_pre_chain": pre_chain,
},
"colored": {
"()": structlog.stdlib.ProcessorFormatter,
"processors": [
extract_from_record,
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.dev.ConsoleRenderer(colors=True),
],
"foreign_pre_chain": pre_chain,
},
},
"handlers": {
"default": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "colored",
},
"file": {
"level": "DEBUG",
"class": "logging.handlers.WatchedFileHandler",
"filename": "test.log",
"formatter": "plain",
},
},
"loggers": {
"": {
"handlers": ["default", "file"],
"level": "DEBUG",
"propagate": True,
},
},
}
)
structlog.configure(
processors=[
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
timestamper,
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
```
This defines two formatters: one plain and one colored.
Both are run for each log entry.
Log entries that do not originate from *structlog*, are additionally pre-processed using a cached `timestamper` and {func}`~structlog.stdlib.add_log_level`.
Additionally, for both `logging` and *structlog* -- but only for the colorful logger -- we also extract some data from {class}`logging.LogRecord`:
```pycon
>>> logging.getLogger().warning("bar")
2021-11-15 13:26:52 [warning ] bar process_name=MainProcess thread_name=MainThread
>>> structlog.get_logger("structlog").warning("foo", x=42)
2021-11-15 13:26:52 [warning ] foo process_name=MainProcess thread_name=MainThread x=42
>>> pathlib.Path("test.log").read_text()
2021-11-15 13:26:52 [warning ] bar
2021-11-15 13:26:52 [warning ] foo x=42
```
(Sadly, you have to imagine the colors in the first two outputs.)
If you leave `foreign_pre_chain` as `None`, formatting will be left to `logging`.
Meaning: you can define a `format` for {class}`~structlog.stdlib.ProcessorFormatter` too!
[*python-json-logger*]: https://github.com/madzak/python-json-logger
structlog-25.1.0/docs/testing.md 0000644 0000000 0000000 00000004507 14742155321 013514 0 ustar 00 # Testing
*structlog* comes with tools for testing the logging behavior of your application.
If you need functionality similar to {meth}`unittest.TestCase.assertLogs`, or you want to capture all logs for some other reason, you can use the {func}`structlog.testing.capture_logs` context manager:
```{doctest}
>>> from structlog import get_logger
>>> from structlog.testing import capture_logs
>>> with capture_logs() as cap_logs:
... get_logger().bind(x="y").info("hello")
>>> cap_logs
[{'x': 'y', 'event': 'hello', 'log_level': 'info'}]
```
Note that inside the context manager all configured processors are disabled.
:::{note}
`capture_logs()` relies on changing the configuration.
If you have *cache_logger_on_first_use* enabled for {doc}`performance `, any cached loggers will not be affected, so it’s recommended you do not enable it during tests.
:::
You can build your own helpers using {class}`structlog.testing.LogCapture`.
For example a [*pytest*](https://docs.pytest.org/) fixture to capture log output could look like this:
```
@pytest.fixture(name="log_output")
def fixture_log_output():
return LogCapture()
@pytest.fixture(autouse=True)
def fixture_configure_structlog(log_output):
structlog.configure(
processors=[log_output]
)
def test_my_stuff(log_output):
do_something()
assert log_output.entries == [...]
```
---
You can also use {class}`structlog.testing.CapturingLogger` (directly, or via {class}`~structlog.testing.CapturingLoggerFactory` that always returns the same logger) that is more low-level and great for unit tests:
```{doctest}
>>> import structlog
>>> cf = structlog.testing.CapturingLoggerFactory()
>>> structlog.configure(logger_factory=cf, processors=[structlog.processors.JSONRenderer()])
>>> log = get_logger()
>>> log.info("test!")
>>> cf.logger.calls
[CapturedCall(method_name='info', args=('{"event": "test!"}',), kwargs={})]
```
```{testcleanup}
import structlog
structlog.reset_defaults()
```
---
Additionally *structlog* also ships with a logger that just returns whatever it gets passed into it: {class}`structlog.testing.ReturnLogger`.
```{doctest}
>>> from structlog import ReturnLogger
>>> ReturnLogger().info(42) == 42
True
>>> obj = ["hi"]
>>> ReturnLogger().info(obj) is obj
True
>>> ReturnLogger().info("hello", when="again")
(('hello',), {'when': 'again'})
```
structlog-25.1.0/docs/thread-local.md 0000644 0000000 0000000 00000015176 14742155321 014402 0 ustar 00 # Legacy Thread-local Context
:::{attention}
The `structlog.threadlocal` module is deprecated as of *structlog* 22.1.0 in favor of {doc}`contextvars`.
The standard library {mod}`contextvars` module provides a more feature-rich superset of the thread-local APIs and works with thread-local data, async code, and greenlets.
Therefore, as of 22.1.0, the `structlog.threadlocal` module is frozen and will be removed after May 2023.
:::
```{testsetup} *
import structlog
structlog.configure(
processors=[structlog.processors.KeyValueRenderer()],
)
```
```{testcleanup} *
import structlog
structlog.reset_defaults()
```
## The `merge_threadlocal` processor
*structlog* provides a simple set of functions that allow explicitly binding certain fields to a global (thread-local) context and merge them later using a processor into the event dict.
The general flow of using these functions is:
- Use {func}`structlog.configure` with {func}`structlog.threadlocal.merge_threadlocal` as your first processor.
- Call {func}`structlog.threadlocal.clear_threadlocal` at the beginning of your request handler (or whenever you want to reset the thread-local context).
- Call {func}`structlog.threadlocal.bind_threadlocal` as an alternative to your bound logger's `bind()` when you want to bind a particular variable to the thread-local context.
- Use *structlog* as normal.
Loggers act as they always do, but the {func}`structlog.threadlocal.merge_threadlocal` processor ensures that any thread-local binds get included in all of your log messages.
- If you want to access the thread-local storage, you use {func}`structlog.threadlocal.get_threadlocal` and {func}`structlog.threadlocal.get_merged_threadlocal`.
These functions map 1:1 to the {doc}`contextvars` APIs, so please use those instead:
- {func}`structlog.contextvars.merge_contextvars`
- {func}`structlog.contextvars.clear_contextvars`
- {func}`structlog.contextvars.bind_contextvars`
- {func}`structlog.contextvars.get_contextvars`
- {func}`structlog.contextvars.get_merged_contextvars`
## Thread-local contexts
*structlog* also provides thread-local context storage in a form that you may already know from [*Flask*](https://flask.palletsprojects.com/en/latest/design/#thread-locals) and that makes the *entire context* global to your thread or greenlet.
This makes its behavior more difficult to reason about which is why we generally recommend to use the {func}`~structlog.contextvars.merge_contextvars` route.
Therefore, there are currently no plans to re-implement this behavior on top of context variables.
### Wrapped dicts
In order to make your context thread-local, *structlog* ships with a function that can wrap any dict-like class to make it usable for thread-local storage: {func}`structlog.threadlocal.wrap_dict`.
Within one thread, every instance of the returned class will have a *common* instance of the wrapped dict-like class:
```{doctest}
>>> from structlog.threadlocal import wrap_dict
>>> WrappedDictClass = wrap_dict(dict)
>>> d1 = WrappedDictClass({"a": 1})
>>> d2 = WrappedDictClass({"b": 2})
>>> d3 = WrappedDictClass()
>>> d3["c"] = 3
>>> d1 is d3
False
>>> d1 == d2 == d3 == WrappedDictClass()
True
>>> d3 # doctest: +ELLIPSIS
```
To enable thread-local context use the generated class as the context class:
```python
configure(context_class=WrappedDictClass)
```
:::{note}
Creation of a new `BoundLogger` initializes the logger's context as `context_class(initial_values)`, and then adds any values passed via `.bind()`.
As all instances of a wrapped dict-like class share the same data, in the case above, the new logger's context will contain all previously bound values in addition to the new ones.
:::
`structlog.threadlocal.wrap_dict` returns always a completely *new* wrapped class:
```{doctest}
>>> from structlog.threadlocal import wrap_dict
>>> WrappedDictClass = wrap_dict(dict)
>>> AnotherWrappedDictClass = wrap_dict(dict)
>>> WrappedDictClass() != AnotherWrappedDictClass()
True
>>> WrappedDictClass.__name__ # doctest: +SKIP
WrappedDict-41e8382d-bee5-430e-ad7d-133c844695cc
>>> AnotherWrappedDictClass.__name__ # doctest: +SKIP
WrappedDict-e0fc330e-e5eb-42ee-bcec-ffd7bd09ad09
```
In order to be able to bind values temporarily to a logger, `structlog.threadlocal` comes with a [context manager](https://docs.python.org/2/library/stdtypes.html#context-manager-types): {func}`structlog.threadlocal.tmp_bind`:
```{testsetup} ctx
from structlog import PrintLogger, wrap_logger
from structlog.threadlocal import tmp_bind, wrap_dict
WrappedDictClass = wrap_dict(dict)
log = wrap_logger(PrintLogger(), context_class=WrappedDictClass)
```
```{doctest} ctx
>>> log.bind(x=42) # doctest: +ELLIPSIS
, ...)>
>>> log.msg("event!")
x=42 event='event!'
>>> with tmp_bind(log, x=23, y="foo") as tmp_log:
... tmp_log.msg("another event!")
x=23 y='foo' event='another event!'
>>> log.msg("one last event!")
x=42 event='one last event!'
```
The state before the `with` statement is saved and restored once it's left.
If you want to detach a logger from thread-local data, there's {func}`structlog.threadlocal.as_immutable`.
#### Downsides & caveats
The convenience of having a thread-local context comes at a price though:
:::{warning}
- If you can't rule out that your application re-uses threads, you *must* remember to **initialize your thread-local context** at the start of each request using {func}`~structlog.BoundLogger.new` (instead of {func}`~structlog.BoundLogger.bind`).
Otherwise you may start a new request with the context still filled with data from the request before.
- **Don't** stop assigning the results of your `bind()`s and `new()`s!
**Do**:
```
log = log.new(y=23)
log = log.bind(x=42)
```
**Don't**:
```
log.new(y=23)
log.bind(x=42)
```
Although the state is saved in a global data structure, you still need the global wrapped logger produce a real bound logger.
Otherwise each log call will result in an instantiation of a temporary BoundLogger.
See `configuration` for more details.
- It [doesn't play well](https://github.com/hynek/structlog/issues/296) with `os.fork` and thus `multiprocessing` (unless configured to use the `spawn` start method).
:::
## API
```{eval-rst}
.. module:: structlog.threadlocal
.. autofunction:: bind_threadlocal
.. autofunction:: unbind_threadlocal
.. autofunction:: bound_threadlocal
.. autofunction:: get_threadlocal
.. autofunction:: get_merged_threadlocal
.. autofunction:: merge_threadlocal
.. autofunction:: clear_threadlocal
.. autofunction:: wrap_dict
.. autofunction:: tmp_bind(logger, **tmp_values)
.. autofunction:: as_immutable
```
structlog-25.1.0/docs/twisted.md 0000644 0000000 0000000 00000010322 14742155321 013512 0 ustar 00 # Twisted
:::{warning}
Since `sys.exc_clear` has been dropped in Python 3, there is currently no way to avoid multiple tracebacks in your log files if using *structlog* together with Twisted on Python 3.
:::
:::{note}
*structlog* currently only supports the legacy -- but still perfectly working -- Twisted logging system found in `twisted.python.log`.
:::
## Concrete bound logger
To make *structlog*'s behavior less magical, it ships with a Twisted-specific wrapper class that has an explicit API instead of improvising: `structlog.twisted.BoundLogger`.
It behaves exactly like the generic `structlog.BoundLogger` except:
- it's slightly faster due to less overhead,
- has an explicit API ({func}`~structlog.twisted.BoundLogger.msg` and {func}`~structlog.twisted.BoundLogger.err`),
- hence causing less cryptic error messages if you get method names wrong.
In order to avoid that *structlog* disturbs your CamelCase harmony, it comes with an alias for `structlog.get_logger` called `structlog.getLogger`.
## Processors
*structlog* comes with two Twisted-specific processors:
{func}`structlog.twisted.EventAdapter`
: This is useful if you have an existing Twisted application and just want to wrap your loggers for now.
It takes care of transforming your event dictionary into something [twisted.python.log.err](https://docs.twisted.org/en/stable/api/twisted.python.log.html#err) can digest.
For example:
```python
def onError(fail):
failure = fail.trap(MoonExploded)
log.err(failure, _why="event-that-happened")
```
will still work as expected.
Needs to be put at the end of the processing chain.
It formats the event using a renderer that needs to be passed into the constructor:
```python
configure(processors=[EventAdapter(KeyValueRenderer()])
```
The drawback of this approach is that Twisted will format your exceptions as multi-line log entries which is painful to parse.
Therefore *structlog* comes with:
{func}`structlog.twisted.JSONRenderer`
: Goes a step further and circumvents Twisted logger's Exception / Failure handling and renders it itself as JSON strings.
That gives you regular and simple-to-parse single-line JSON log entries no matter what happens.
## Bending foreign logging to your will
*structlog* comes with a wrapper for Twisted's log observers to ensure the rest of your logs are in JSON too: `structlog.twisted.JSONLogObserverWrapper`.
What it does is determining whether a log entry has been formatted by `structlog.twisted.JSONRenderer` and if not, converts the log entry to JSON with `event` being the log message and putting Twisted's `system` into a second key.
So for example:
```
2013-09-15 22:02:18+0200 [-] Log opened.
```
becomes:
```
2013-09-15 22:02:18+0200 [-] {"event": "Log opened.", "system": "-"}
```
There is obviously some redundancy here.
Also, I'm presuming that if you write out JSON logs, you're going to let something else parse them which makes the human-readable date entries more trouble than they're worth.
To get a clean log without timestamps and additional system fields (`[-]`), *structlog* comes with `structlog.twisted.PlainFileLogObserver` that writes only the plain message to a file and `structlog.twisted.plainJSONStdOutLogger` that composes it with the aforementioned `structlog.twisted.JSONLogObserverWrapper` and gives you a pure JSON log without any timestamps or other noise straight to [standard out]:
```console
$ twistd -n --logger structlog.twisted.plainJSONStdOutLogger web
{"event": "Log opened.", "system": "-"}
{"event": "twistd 13.1.0 (python 2.7.3) starting up.", "system": "-"}
{"event": "reactor class: twisted...EPollReactor.", "system": "-"}
{"event": "Site starting on 8080", "system": "-"}
{"event": "Starting factory ", ...}
...
```
## Suggested configuration
```python
import structlog
structlog.configure(
processors=[
structlog.processors.StackInfoRenderer(),
structlog.twisted.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.twisted.LoggerFactory(),
wrapper_class=structlog.twisted.BoundLogger,
cache_logger_on_first_use=True,
)
```
See also {doc}`logging-best-practices`.
[standard out]: https://en.wikipedia.org/wiki/Standard_out#Standard_output_.28stdout.29
structlog-25.1.0/docs/typing.md 0000644 0000000 0000000 00000003555 14742155321 013353 0 ustar 00 # Type Hints
Static type hints -- together with a type checker like [Mypy](https://mypy.readthedocs.io/en/stable/) -- are an excellent way to make your code more robust, self-documenting, and maintainable in the long run.
And as of 20.2.0, *structlog* comes with type hints for all of its APIs.
Since *structlog* is highly configurable and tries to give a clean façade to its users, adding types without breaking compatibility -- while remaining useful! -- was a formidable task.
---
The main problem is that `structlog.get_logger()` returns whatever you've configured the *bound logger* to be.
The only commonality are the binding methods like `bind()` and we've extracted them into the {class}`structlog.typing.BindableLogger` {class}`~typing.Protocol`.
But using that as a return type is worse than useless, because you'd have to use {func}`typing.cast` on every logger returned by `structlog.get_logger()`, if you wanted to actually call any logging methods.
The second problem is that said `bind()` and its cousins are inherited from a common base class (a [big](https://www.youtube.com/watch?v=3MNVP9-hglc) [mistake](https://python-patterns.guide/gang-of-four/composition-over-inheritance/) in hindsight) and can't know what concrete class subclasses them and therefore what type they are returning.
The chosen solution is adding {func}`structlog.stdlib.get_logger()` that just calls `structlog.get_logger()` but has the correct type hints and adding `structlog.stdlib.BoundLogger.bind` et al that also only delegate to the base class.
`structlog.get_logger()` is typed as returning {any}`typing.Any` so you can use your own type annotation and stick to the old APIs, if that's what you prefer:
```
import structlog
logger: structlog.stdlib.BoundLogger = structlog.get_logger()
logger.info("hi") # <- ok
logger.msg("hi") # <- Mypy: 'error: "BoundLogger" has no attribute "msg"'
```
structlog-25.1.0/docs/why.md 0000644 0000000 0000000 00000010773 14742155321 012650 0 ustar 00 # Why …
## … structured logging?
> I believe the widespread use of format strings in logging is based on two presumptions:
>
> - The first level consumer of a log message is a human.
> - The programmer knows what information is needed to debug an issue.
>
> I believe these presumptions are **no longer correct** in server side software.
>
> —[Paul Querna](https://paul.querna.org/articles/2011/12/26/log-for-machines-in-json/)
Structured logging means that you don't write hard-to-parse and hard-to-keep-consistent prose in your log entries.
Instead, you log *events* that happen in a *context* of key-value pairs.
:::{tip}
More general advice about production-grade logging can be found in the later chapter on {doc}`logging-best-practices`.
:::
## … structlog?
### Easier logging
You can stop writing prose and start thinking in terms of an event that happens in the context of key-value pairs:
```pycon
>>> from structlog import get_logger
>>> log = get_logger()
>>> log.info("key_value_logging", out_of_the_box=True, effort=0)
2020-11-18 09:17:09 [info ] key_value_logging effort=0 out_of_the_box=True
```
Each log entry is a meaningful dictionary instead of an opaque string now!
That said, *structlog* is not taking anything away from you.
You can still use string interpolation using positional arguments:
```pycon
>>> log.info("Hello, %s!", "world")
2022-10-10 07:19:25 [info ] Hello, world!
```
### Data binding
Since log entries are dictionaries, you can start binding and re-binding key-value pairs to your loggers to ensure they are present in every following logging call:
```pycon
>>> log = log.bind(user="anonymous", some_key=23)
>>> log = log.bind(user="hynek", another_key=42)
>>> log.info("user.logged_in", happy=True)
2020-11-18 09:18:28 [info ] user.logged_in another_key=42 happy=True some_key=23 user=hynek
```
You can also bind key-value pairs to {doc}`context variables ` that look global, but are local to your thread or *asyncio* context -- which usually means your web request.
### Powerful pipelines
Each log entry goes through a [processor pipeline](processors.md) that is just a chain of functions that receive a dictionary and return a new dictionary that gets fed into the next function.
That allows for simple but powerful data manipulation:
```python
def timestamper(logger, log_method, event_dict):
"""Add a timestamp to each log entry."""
event_dict["timestamp"] = time.time()
return event_dict
```
There are [plenty of processors](structlog.processors) for most common tasks coming with *structlog*:
- Collectors of [call stack information](structlog.processors.StackInfoRenderer) ("How did this log entry happen?"),
- …and [exceptions](structlog.processors.format_exc_info) ("What happened‽").
- Flexible [timestamping](structlog.processors.TimeStamper).
### Formatting
*structlog* is completely flexible about *how* the resulting log entry is emitted.
Since each log entry is a dictionary, it can be formatted to **any** format:
- A colorful key-value format for [local development](console-output.md),
- [JSON](structlog.processors.JSONRenderer) or [*logfmt*](structlog.processors.LogfmtRenderer) for easy parsing,
- or some standard format you have parsers for like *nginx* or Apache *httpd*.
Internally, formatters are processors whose return value (usually a string) is passed into loggers that are responsible for the output of your message.
*structlog* comes with multiple useful formatters out-of-the-box.
### Output
*structlog* is also flexible with the final output of your log entries:
- A **built-in** lightweight printer like in the examples above.
Easy to use and fast.
- Use the [**standard library**](standard-library.md)'s or [**Twisted**](twisted.md)'s logging modules for compatibility.
In this case *structlog* works like a wrapper that formats a string and passes them off into existing systems that won't know that *structlog* even exists.
Or the other way round: *structlog* comes with a `logging` formatter that allows for processing third party log records.
- Don't format it to a string at all!
*structlog* passes you a dictionary and you can do with it whatever you want.
Reported use cases are sending them out via network or saving them to a database.
### Highly testable
*structlog* is thoroughly tested and we see it as our duty to help you to achieve the same in *your* applications.
That's why it ships with a [test helpers](testing.md) to introspect your application's logging behavior with little-to-no boilerplate.
structlog-25.1.0/docs/_static/BoundLogger.svg 0000644 0000000 0000000 00000154760 14742155321 016102 0 ustar 00