@@ -25,7 +25,8 @@ Motivation
2525With the introduction of exception groups (:pep: `654 `), Python programs can now
2626propagate multiple unrelated exceptions simultaneously. When debugging these,
2727or 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
3031Currently there is no standard way to obtain this information. Python authors
3132must 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
133139A new ``traceback_timestamps `` field in ``PyConfig `` stores the selected format,
134140accessible 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
150156to keep it visually distinct from the exception message.
151157
158+ The ``us `` format produces ``<@1776017164.530916> `` and the ``ns `` format
159+ produces ``<@1776017178687320256ns> ``.
160+
152161Traceback 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
160169A new utility function ``traceback.strip_exc_timestamps(text) `` is provided
161170to strip ``<@...> `` timestamp suffixes from formatted traceback strings.
@@ -181,7 +190,24 @@ C struct, recording nanoseconds since the Unix epoch. This design was chosen
181190over using exception notes (:pep: `678 `) because a struct field costs nothing
182191when not populated, avoids creating string and list objects at raise time, and
183192defers 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
187213Backwards Compatibility
@@ -201,9 +227,39 @@ byte-identical when the feature is off and to avoid any performance impact
201227on the common case. As much as this author prefers simpler code, it felt
202228riskier 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
208264The ``__timestamp_ns__ `` field is a single ``int64_t `` in the ``BaseException ``
209265C 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
222279Outside 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
230287have 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-
245290Security Implications
246291=====================
247292
0 commit comments