diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d55243b59beded46411a01c53000126b6215e5c..922c569d38f00c108da42f2cd9e7744fd684656e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.10 + rev: v0.11.7 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/CHANGELOG.md b/CHANGELOG.md index 23f15a672cea3c0ff9613a05b12c4b7c430560b6..ce0ac4b4ab738a72de3e09bd10dd5e7627d15f2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,14 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/ <!-- changelog follows --> +## [25.3.0](https://github.com/hynek/structlog/compare/25.2.0...25.3.0) - 2025-04-25 + +### Fixed + +- `structlog.processors.TimeStamper` now again uses timestamps using UTC for custom format strings when `utc=True`. + [#713](https://github.com/hynek/structlog/pull/713) + + ## [25.2.0](https://github.com/hynek/structlog/compare/25.1.0...25.2.0) - 2025-03-11 ### Added diff --git a/PKG-INFO b/PKG-INFO index ee85fef91f5950214f0c0fa9c487839c4e7174d7..d04857a9eec242227aee1987e9c917b750f55b7d 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: structlog -Version: 25.2.0 +Version: 25.3.0 Summary: Structured Logging for Python Project-URL: Documentation, https://www.structlog.org/ Project-URL: Changelog, https://github.com/hynek/structlog/blob/main/CHANGELOG.md @@ -94,15 +94,15 @@ Especially those generously supporting us at the *The Organization* tier and hig import pathlib, tomllib for sponsor in tomllib.loads(pathlib.Path("pyproject.toml").read_text())["tool"]["sponcon"]["sponsors"]: - print(f'<a href="{sponsor["url"]}"><img title="{sponsor["title"]}" src="https://www.structlog.org/en/25.2.0/_static/sponsors/{sponsor["img"]}" width="190" /></a>') + print(f'<a href="{sponsor["url"]}"><img title="{sponsor["title"]}" src="https://www.structlog.org/en/25.3.0/_static/sponsors/{sponsor["img"]}" width="190" /></a>') ]]] --> -<a href="https://www.variomedia.de/"><img title="Variomedia AG" src="https://www.structlog.org/en/25.2.0/_static/sponsors/Variomedia.svg" width="190" /></a> -<a href="https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek"><img title="Tidelift" src="https://www.structlog.org/en/25.2.0/_static/sponsors/Tidelift.svg" width="190" /></a> -<a href="https://klaviyo.com/"><img title="Klaviyo" src="https://www.structlog.org/en/25.2.0/_static/sponsors/Klaviyo.svg" width="190" /></a> -<a href="https://privacy-solutions.org/"><img title="Privacy Solutions" src="https://www.structlog.org/en/25.2.0/_static/sponsors/Privacy-Solutions.svg" width="190" /></a> -<a href="https://www.emsys-renewables.com/"><img title="emsys renewables" src="https://www.structlog.org/en/25.2.0/_static/sponsors/emsys-renewables.svg" width="190" /></a> -<a href="https://filepreviews.io/"><img title="FilePreviews" src="https://www.structlog.org/en/25.2.0/_static/sponsors/FilePreviews.svg" width="190" /></a> -<a href="https://polar.sh/"><img title="Polar" src="https://www.structlog.org/en/25.2.0/_static/sponsors/Polar.svg" width="190" /></a> +<a href="https://www.variomedia.de/"><img title="Variomedia AG" src="https://www.structlog.org/en/25.3.0/_static/sponsors/Variomedia.svg" width="190" /></a> +<a href="https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek"><img title="Tidelift" src="https://www.structlog.org/en/25.3.0/_static/sponsors/Tidelift.svg" width="190" /></a> +<a href="https://klaviyo.com/"><img title="Klaviyo" src="https://www.structlog.org/en/25.3.0/_static/sponsors/Klaviyo.svg" width="190" /></a> +<a href="https://privacy-solutions.org/"><img title="Privacy Solutions" src="https://www.structlog.org/en/25.3.0/_static/sponsors/Privacy-Solutions.svg" width="190" /></a> +<a href="https://www.emsys-renewables.com/"><img title="emsys renewables" src="https://www.structlog.org/en/25.3.0/_static/sponsors/emsys-renewables.svg" width="190" /></a> +<a href="https://filepreviews.io/"><img title="FilePreviews" src="https://www.structlog.org/en/25.3.0/_static/sponsors/FilePreviews.svg" width="190" /></a> +<a href="https://polar.sh/"><img title="Polar" src="https://www.structlog.org/en/25.3.0/_static/sponsors/Polar.svg" width="190" /></a> <!-- [[[end]]] --> </p> @@ -138,28 +138,11 @@ Save time, reduce risk, and improve code health, while paying the maintainers of ## Release Information -### Added - -- `structlog.tracebacks.Stack` now includes an `exc_notes` field reflecting the notes attached to the exception. - [#684](https://github.com/hynek/structlog/pull/684) - - -### Changed - -- `structlog.stdlib.BoundLogger`'s binding-related methods now also return `Self`. - [#694](https://github.com/hynek/structlog/pull/694) - -- `structlog.processors.TimeStamper` now produces internally timezone-aware `datetime` objects. - Default output hasn't changed, but you can now use `%z` in your *fmt* string. - [#709](https://github.com/hynek/structlog/pull/709) - - ### Fixed -- Expose `structlog.dev.RichTracebackFormatter` for imports. - [#699](https://github.com/hynek/structlog/pull/699) -- Expose `structlog.processors.LogfmtRenderer` for imports. - [#701](https://github.com/hynek/structlog/pull/701) +- `structlog.processors.TimeStamper` now again uses timestamps using UTC for custom format strings when `utc=True`. + [#713](https://github.com/hynek/structlog/pull/713) + --- diff --git a/src/structlog/processors.py b/src/structlog/processors.py index 4ef7578099743f99b730d4b02c38d4c0b28a8af9..39e1532822ea0c80385c4547d7cc3798e370efbe 100644 --- a/src/structlog/processors.py +++ b/src/structlog/processors.py @@ -553,12 +553,18 @@ def _make_stamper( return stamper_iso_local - def stamper_fmt(event_dict: EventDict) -> EventDict: + def stamper_fmt_local(event_dict: EventDict) -> EventDict: event_dict[key] = now().astimezone().strftime(fmt) + return event_dict + def stamper_fmt_utc(event_dict: EventDict) -> EventDict: + event_dict[key] = now().strftime(fmt) return event_dict - return stamper_fmt + if utc: + return stamper_fmt_utc + + return stamper_fmt_local class MaybeTimeStamper: diff --git a/tests/processors/test_renderers.py b/tests/processors/test_renderers.py index be8007add04b3ff316f598512c70a23e393fc3eb..1bef95f04b850f56ff5f886afe60577405df0ee9 100644 --- a/tests/processors/test_renderers.py +++ b/tests/processors/test_renderers.py @@ -414,6 +414,36 @@ class TestTimeStamper: assert "1980" == d["timestamp"] + @freeze_time("1980-03-25 16:00:00") + def test_inserts_formatted_utc(self): + """ + The fmt string in UTC timezone works. + + The exact hours calculated here maybe incorrect because of freezegun bugs: + https://github.com/spulec/freezegun/issues/348 + https://github.com/spulec/freezegun/issues/494 + """ + + ts = TimeStamper(fmt="%Y-%m-%d %H:%M:%S %Z") + d = ts(None, None, {}) + + assert "1980-03-25 16:00:00 UTC" == d["timestamp"] + + @freeze_time("1980-03-25 16:00:00") + def test_inserts_formatted_local(self): + """ + The fmt string in local timezone works. + + The exact hours calculated here maybe incorrect because of freezegun bugs: + https://github.com/spulec/freezegun/issues/348 + https://github.com/spulec/freezegun/issues/494 + """ + local_tz = datetime.datetime.now().astimezone().tzname() + ts = TimeStamper(fmt="%Y-%m-%d %H:%M:%S %Z", utc=False) + d = ts(None, None, {}) + + assert f"1980-03-25 16:00:00 {local_tz}" == d["timestamp"] + @freeze_time("1980-03-25 16:00:00") def test_tz_aware(self): """ diff --git a/tests/test_stdlib.py b/tests/test_stdlib.py index 383e60dd5decb014d85ac1a732d8dd1d87f99d2a..607308d36d266571eff857027d2a26620ba0f163 100644 --- a/tests/test_stdlib.py +++ b/tests/test_stdlib.py @@ -663,7 +663,7 @@ class TestExtraAdder: handler.setLevel(0) logger.addHandler(handler) logger.setLevel(0) - logging.warning("allow = %s", allow) + logging.warning("allow = %s", allow) # noqa: LOG015 event_dict = {"event": "Some text"} expected = self._copy_allowed(event_dict, extra_dict, allow)