Skip to content

Commit d94d590

Browse files
committed
Address review vs pep 1 & 12 & my code feedback.
(thanks Claude!)
1 parent 0e438c5 commit d94d590

File tree

1 file changed

+68
-23
lines changed

1 file changed

+68
-23
lines changed

peps/pep-0830.rst

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ Motivation
2525
With the introduction of exception groups (:pep:`654`), Python programs can now
2626
propagate multiple unrelated exceptions simultaneously. When debugging these,
2727
or when correlating exceptions with external logs and metrics, knowing *when*
28-
each exception occurred is often as important as knowing *what* occurred.
28+
each exception occurred is often as important as knowing *what* occurred;
29+
a common pain point when diagnosing problems in production.
2930

3031
Currently there is no standard way to obtain this information. Python authors
3132
must manually add timing to exception messages or rely on logging frameworks,
@@ -124,11 +125,16 @@ The feature is enabled through CPython's two standard mechanisms:
124125

125126
``PYTHON_TRACEBACK_TIMESTAMPS`` environment variable
126127
Set to ``us`` or ``1`` for microsecond-precision decimal timestamps,
127-
``ns`` for raw nanoseconds, or ``iso`` for ISO 8601 UTC format.
128+
``ns`` for nanoseconds, or ``iso`` for ISO 8601 UTC format.
128129
Empty, unset, or ``0`` disables timestamps (the default).
129130

130131
``-X traceback_timestamps=<format>`` command-line option
131132
Accepts the same values. Takes precedence over the environment variable.
133+
If ``-X traceback_timestamps`` is specified with no ``=<format>`` value,
134+
that acts as an implicit ``=1`` (microsecond-precision format).
135+
136+
Consistent with other CPython config behavior, an invalid environment variable
137+
value is silently ignored while an invalid ``-X`` flag value is an error.
132138

133139
A new ``traceback_timestamps`` field in ``PyConfig`` stores the selected format,
134140
accessible as ``sys.flags.traceback_timestamps``.
@@ -144,18 +150,21 @@ the format ``<@timestamp>``. Example with ``iso``:
144150
Traceback (most recent call last):
145151
File "<stdin>", line 3, in california_raisin
146152
raise RuntimeError("not enough sunshine")
147-
RuntimeError: not enough sunshine <@2025-02-01T20:43:01.026169Z>
153+
RuntimeError: not enough sunshine <@2026-04-12T18:07:30.346914Z>
148154
149155
When colorized output is enabled, the timestamp is rendered in a muted color
150156
to keep it visually distinct from the exception message.
151157

158+
The ``us`` format produces ``<@1776017164.530916>`` and the ``ns`` format
159+
produces ``<@1776017178687320256ns>``.
160+
152161
Traceback Module Updates
153162
------------------------
154163

155-
``TracebackException`` and the public formatting functions (``print_exception``,
156-
``format_exception``, ``format_exception_only``) gain a ``no_timestamp``
157-
keyword argument (default ``False``) that suppresses timestamp display even
158-
when globally enabled.
164+
``TracebackException`` and the public formatting functions (``print_exc``,
165+
``print_exception``, ``format_exception``, ``format_exception_only``) gain a
166+
``no_timestamp`` keyword argument (default ``False``) that suppresses timestamp
167+
display even when globally enabled.
159168

160169
A new utility function ``traceback.strip_exc_timestamps(text)`` is provided
161170
to strip ``<@...>`` timestamp suffixes from formatted traceback strings.
@@ -181,7 +190,24 @@ C struct, recording nanoseconds since the Unix epoch. This design was chosen
181190
over using exception notes (:pep:`678`) because a struct field costs nothing
182191
when not populated, avoids creating string and list objects at raise time, and
183192
defers all formatting work to traceback rendering. The feature is entirely
184-
opt-in and does not change exception handling semantics or performance.
193+
opt-in and does not change exception handling semantics.
194+
195+
The use of exception notes as a carrier for the information was deemed
196+
infeasible due to their performance overhead and lack of explicit purpose.
197+
Notes are great, but were designed for a far different use case, not as a
198+
way to collect data captured upon every Exception instantiation.
199+
200+
Performance Measurements
201+
------------------------
202+
203+
The pyperformance suite has been run on the merge base, on the PR branch with
204+
the feature disabled, and on the PR branch with the feature enabled in both
205+
``ns`` and ``iso`` modes.
206+
207+
TODO(@gpshead): summarize results of the most recent run here.
208+
209+
TODO(@gpshead): Do another run with the feature enabled but without the StopIteration special case to demonstrate why that choice was made.
210+
185211

186212

187213
Backwards Compatibility
@@ -201,9 +227,39 @@ byte-identical when the feature is off and to avoid any performance impact
201227
on the common case. As much as this author prefers simpler code, it felt
202228
riskier to have exception pickles all increase in size as a default behavior.
203229

230+
Pickled Exception Examples
231+
--------------------------
232+
233+
With traceback timestamp collection enabled:
234+
235+
.. code-block:: text
236+
237+
❯ build/python -X traceback_timestamps=iso -c 'import pickle; print(pickle.dumps(RuntimeError("pep-830"), protocol=pickle.HIGHEST_PROTOCOL))'
238+
b'\x80\x05\x95L\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x0cRuntimeError\x94\x93\x94\x8c\x07pep-830\x94\x85\x94R\x94}\x94\x8c\x10__timestamp_ns__\x94\x8a\x08\xf4\xd8\x94`\x15\xaf\xa5\x18sb.'
239+
240+
The special case for ``StopIteration`` means it does not carry the dict with timestamp data:
241+
242+
.. code-block:: text
243+
244+
❯ build/python -X traceback_timestamps=iso -c 'import pickle; print(pickle.dumps(StopIteration("pep-830"), protocol=pickle.HIGHEST_PROTOCOL))'
245+
b'\x80\x05\x95,\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\rStopIteration\x94\x93\x94\x8c\x07pep-830\x94\x85\x94R\x94.'
246+
247+
Nor do exceptions carry the timestamp when the feature is disabled (the default):
248+
249+
.. code-block:: text
250+
251+
❯ build/python -X traceback_timestamps=0 -c 'import pickle; print(pickle.dumps(RuntimeError("pep-830"), protocol=pickle.HIGHEST_PROTOCOL))'
252+
b'\x80\x05\x95+\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x0cRuntimeError\x94\x93\x94\x8c\x07pep-830\x94\x85\x94R\x94.'
253+
254+
Which matches what Python 3.13 produces:
255+
256+
.. code-block:: text
257+
258+
❯ python3.13 -c 'import pickle; print(pickle.dumps(RuntimeError("pep-830"), protocol=pickle.HIGHEST_PROTOCOL))'
259+
b'\x80\x05\x95+\x00\x00\x00\x00\x00\x00\x00\x8c\x08builtins\x94\x8c\x0cRuntimeError\x94\x93\x94\x8c\x07pep-830\x94\x85\x94R\x94.'
204260
205261
Maintenance Burden
206-
==================
262+
------------------
207263

208264
The ``__timestamp_ns__`` field is a single ``int64_t`` in the ``BaseException``
209265
C struct, present in every exception object regardless of configuration. The
@@ -216,8 +272,9 @@ helpers are provided for this:
216272

217273
- ``traceback.strip_exc_timestamps(text)`` strips ``<@...>`` suffixes from
218274
formatted traceback strings.
219-
- ``test.support.force_no_traceback_timestamps`` is a decorator that disables
220-
timestamp collection for the duration of a test.
275+
- ``test.support.force_no_traceback_timestamps`` and a ``_test_class`` suffixed
276+
variant are decorators that disable timestamp collection for the duration of
277+
a test or ``TestCase`` class.
221278

222279
Outside of the traceback-specific tests, approximately 14 of ~1230 test files
223280
(roughly 1%) needed one of these helpers, typically tests that capture
@@ -230,18 +287,6 @@ GitHub Actions runs to maintain coverage, most projects are unlikely to
230287
have the feature enabled while running their test suites.
231288

232289

233-
Performance Measurements
234-
========================
235-
236-
The pyperformance suite has been run on the merge base, on the PR branch with
237-
the feature disabled, and on the PR branch with the feature enabled in both
238-
``ns`` and ``iso`` modes.
239-
240-
TODO(@gpshead): summarize results of the most recent run here.
241-
242-
TODO(@gpshead): Do another run with the feature enabled but without the StopIteration special case to demonstrate why that choice was made.
243-
244-
245290
Security Implications
246291
=====================
247292

0 commit comments

Comments
 (0)