PEP: 830
Title: Add timestamps to exceptions and tracebacks
Author: Gregory P. Smith <greg@krypto.org>
Discussions-To: https://discuss.python.org/t/106942
Status: Draft
Type: Standards Track
Created: 15-Mar-2026
Python-Version: 3.16
Post-History: `12-Apr-2026 <https://discuss.python.org/t/106942>`__,
              `18-Apr-2026 <https://discuss.python.org/t/pep-830-add-timestamps-to-exceptions-and-tracebacks/106942/27>`__


Abstract
========

This PEP adds an optional ``__timestamp_ns__`` attribute to ``BaseException``
that records when the exception was instantiated with no observable overhead.
When enabled via environment variable or command-line flag, formatted
tracebacks display this timestamp alongside the exception message.


Motivation
==========

With the introduction of exception groups (:pep:`654`), Python programs can now
propagate multiple unrelated exceptions simultaneously.  When debugging these,
or when correlating exceptions with external logs and metrics, knowing *when*
each exception occurred is often as important as knowing *what* occurred;
a common pain point when diagnosing problems in production.

Currently there is no standard way to obtain this information.  Python authors
must manually add timing to exception messages or rely on logging frameworks,
which can be costly and is inconsistently done and error-prone.

Consider an async service that fetches data from multiple backends
concurrently and reports every failure rather than failing fast.  The
resulting ``ExceptionGroup`` contains all the errors in submission order,
with no indication of when each one occurred::

    import asyncio

    async def fetch_user(uid):
        await asyncio.sleep(0.5)
        raise ConnectionError(f"User service timeout for {uid}")

    async def fetch_orders(uid):
        await asyncio.sleep(0.1)
        raise ValueError(f"Invalid user_id format: {uid}")

    async def fetch_recommendations(uid):
        await asyncio.sleep(2.3)
        raise TimeoutError("Recommendation service timeout")

    async def get_dashboard(uid):
        results = await asyncio.gather(
            fetch_user(uid),
            fetch_orders(uid),
            fetch_recommendations(uid),
            return_exceptions=True,
        )
        errors = [r for r in results if isinstance(r, Exception)]
        if errors:
            raise ExceptionGroup("dashboard fetch failed", errors)

    asyncio.run(get_dashboard("usr_12@34"))

With ``PYTHON_TRACEBACK_TIMESTAMPS=iso``, the output becomes:

.. code-block:: text

      + Exception Group Traceback (most recent call last):
      |   File "service.py", line 26, in <module>
      |     asyncio.run(get_dashboard("usr_12@34"))
      |   ...
      |   File "service.py", line 24, in get_dashboard
      |     raise ExceptionGroup("dashboard fetch failed", errors)
      | ExceptionGroup: dashboard fetch failed (3 sub-exceptions) <@2026-04-19T07:24:31.102431Z>
      +-+---------------- 1 ----------------
        | Traceback (most recent call last):
        |   File "service.py", line 5, in fetch_user
        |     raise ConnectionError(f"User service timeout for {uid}")
        | ConnectionError: User service timeout for usr_12@34 <@2026-04-19T07:24:29.300461Z>
        +---------------- 2 ----------------
        | Traceback (most recent call last):
        |   File "service.py", line 9, in fetch_orders
        |     raise ValueError(f"Invalid user_id format: {uid}")
        | ValueError: Invalid user_id format: usr_12@34 <@2026-04-19T07:24:28.899918Z>
        +---------------- 3 ----------------
        | Traceback (most recent call last):
        |   File "service.py", line 13, in fetch_recommendations
        |     raise TimeoutError("Recommendation service timeout")
        | TimeoutError: Recommendation service timeout <@2026-04-19T07:24:31.102394Z>
        +------------------------------------

The sub-exceptions are listed in submission order, but the timestamps reveal
that the order validation actually failed first (at 28.899s), the user
service half a second later (at 29.300s), and the recommendation service last after 2.3
seconds (at 31.102s).  These can also be correlated with metrics dashboards, load
balancer logs, traces from other services, or the program's own logs to
build a complete picture.


Specification
=============

Exception Timestamp Attribute
-----------------------------

A new read/write attribute ``__timestamp_ns__`` is added to ``BaseException``.
It stores nanoseconds since the Unix epoch in UTC (same semantics and
precision as ``time.time_ns()``) as a C ``int64_t`` exposed via a member
descriptor.  When timestamps are disabled, for control flow exceptions (see
below), or if reading the clock fails, the value is ``0``.

Exception instances reused from an internal free list, such as
``MemoryError``, are timestamped when handed out rather than when originally
allocated.  The interpreter's last-resort static ``MemoryError`` singleton
retains a ``__timestamp_ns__`` of ``0``.

Control Flow Exceptions
------------------------

To avoid performance impact on normal control flow, timestamps are **not**
collected for ``StopIteration`` or ``StopAsyncIteration`` even when the feature
is enabled.  These exceptions are raised at extremely high frequency during
iteration; the check uses C type pointer identity (not ``isinstance``) for
negligible overhead.

Other idioms sometimes described as exceptions-as-control-flow, such as
``hasattr``, three-argument ``getattr``, ``dict.get``, ``dict.setdefault``,
and ``__missing__`` dispatch, do not need special handling: the hot paths in
CPython's C internals already return "not found" without instantiating an
exception object.

Configuration
-------------

The feature is enabled through CPython's two standard mechanisms:

``PYTHON_TRACEBACK_TIMESTAMPS`` environment variable
    Set to ``ns`` or ``1`` for nanosecond-precision decimal timestamps, or
    ``iso`` for ISO 8601 UTC format.  Empty, unset, or ``0`` disables
    timestamps (the default).

``-X traceback_timestamps=<format>`` command-line option
    Accepts the same values.  Takes precedence over the environment variable.
    If ``-X traceback_timestamps`` is specified with no ``=<format>`` value,
    that acts as an implicit ``=1`` (nanosecond-precision format).

Consistent with other CPython config behavior, an invalid environment variable
value is silently ignored while an invalid ``-X`` flag value is an error.

A new ``traceback_timestamps`` field in ``PyConfig`` stores the selected format,
accessible as ``sys.flags.traceback_timestamps``.

Display Format
--------------

Timestamps are appended to the exception message line in tracebacks using
the format ``<@timestamp>``.  This affects formatted traceback output only;
``str(exc)`` and ``repr(exc)`` are unchanged.  Example with ``iso``:

.. code-block:: pytb

    Traceback (most recent call last):
      File "<stdin>", line 3, in california_raisin
        raise RuntimeError("not enough sunshine")
    RuntimeError: not enough sunshine <@2026-04-12T18:07:30.346914Z>

When colorized output is enabled, the timestamp is rendered in a muted color
to keep it visually distinct from the exception message.

The ``ns`` format renders seconds since the epoch with nine fractional
digits, for example ``<@1776017178.687320256>``.  The ``iso`` format renders
at microsecond resolution; the underlying ``__timestamp_ns__`` value is
always nanosecond precision regardless of the display format.

Traceback Module Updates
------------------------

``TracebackException`` and the public formatting functions (``print_exc``,
``print_exception``, ``format_exception``, ``format_exception_only``) gain a
``timestamps`` keyword argument (default ``None``):

``None``
    Follow the global configuration.  Timestamps are shown only when the
    feature is enabled, using the configured format.
``False``
    Never show timestamps, even when the feature is enabled.
``True``
    Show any non-zero ``__timestamp_ns__`` regardless of the global
    configuration, using the configured format if one is set and ``ns``
    otherwise.

Only non-zero values are ever rendered.  When collection is disabled,
non-zero values can still arise on instances unpickled from a process where
collection was enabled, or where ``__timestamp_ns__`` was assigned directly.

A new utility function ``traceback.strip_exc_timestamps(text)`` is provided
to strip ``<@...>`` timestamp suffixes from formatted traceback strings.
This is useful for anything that compares traceback output literally.

Doctest Updates
---------------

A new ``doctest.IGNORE_EXCEPTION_TIMESTAMPS`` option flag is added.  When
enabled, the doctest output checker strips timestamps from actual output before
comparison, so that doctests producing exceptions pass regardless of whether
timestamps are enabled.

Third-party projects are **not** expected to support running their tests with
timestamps enabled, and we do not expect many projects would ever want to.


Rationale
=========

The timestamp is stored as a single ``int64_t`` field in the ``BaseException``
C struct, recording nanoseconds since the Unix epoch.  This design was chosen
over using exception notes (:pep:`678`) because a struct field costs nothing
when not populated, avoids creating string and list objects at raise time, and
defers all formatting work to traceback rendering.  The feature is entirely
opt-in and does not change exception handling semantics.

The use of exception notes as a carrier for the information was deemed
infeasible due to their performance overhead and lack of explicit purpose.
Notes are great, but were designed for a far different use case, not as a
way to collect data captured upon every Exception instantiation.

Instantiation Time vs. Raise Time
---------------------------------

The timestamp is recorded when the exception object is created rather than
when it is raised.  In the overwhelmingly common ``raise SomeError(...)``
form these are the same moment.  Where they differ, instantiation time is
generally the more useful value: a bare ``raise`` or ``raise exc`` re-raise
preserves the time the error first occurred, not when it was last
re-thrown, and code that constructs exceptions, accumulates them, and later
wraps them in an ``ExceptionGroup`` gets the time each error was detected.

Instantiation time is also the cleaner implementation point.  Exception instantiation
funnels through ``BaseException.__init__`` and its vectorcall path.  Raising
does not have an equivalent single funnel: the lowest-level
``_PyErr_SetRaisedException`` is also invoked by routine save/restore of the
thread's exception state, and the Python ``raise`` statement, C
``PyErr_SetObject``, and direct ``PyErr_SetRaisedException`` calls are
distinct code paths above it.

See `Recording Time of First Raise`_ under Open Issues.

Performance Measurements
------------------------

The pyperformance suite was run on the merge base, on the PR branch with the
feature disabled, and on the PR branch with the feature enabled in both
``us`` and ``iso`` modes.  These measurements were taken against the
reference implementation matching the original version of this PEP, prior to
the revisions recorded in `Change History`_; those revisions are
simplifications of the display layer only, so no performance difference is
expected.

No significant performance changes were observed: only occasional 1-2%
variations that could not be reliably reproduced and fall below the
benchmarking setup's noise threshold.

To validate the control flow special case, the suite was also run with the
two-line ``StopIteration`` / ``StopAsyncIteration`` exclusion in
``Objects/exceptions.c`` removed.  Only a single benchmark,
``async_generators``, showed a regression, reliably running on the order of
10% slower.  It is likely effectively a microbenchmark that does not reflect
most application behavior, but it demonstrates the value of that optimization.

Benchmarks were run against ``configure --enable-optimizations`` builds using
commands such as:

.. code-block:: shell

    pyperformance run -p baseline-3a7df632c96/build/python -o baseline-eopt.json
    pyperformance run -p traceback-timestamps/build/python -o traceback-timestamps-default-eopt.json
    PYTHON_TRACEBACK_TIMESTAMPS=1 pyperformance run --inherit-environ PYTHON_TRACEBACK_TIMESTAMPS -p traceback-timestamps/build/python -o traceback-timestamps-env=1-eopt.json
    PYTHON_TRACEBACK_TIMESTAMPS=iso pyperformance run --inherit-environ PYTHON_TRACEBACK_TIMESTAMPS -p traceback-timestamps/build/python -o traceback-timestamps/Results.silencio/traceback-timestamps-env=iso-eopt.json
    PYTHON_TRACEBACK_TIMESTAMPS=1 pyperformance run --inherit-environ PYTHON_TRACEBACK_TIMESTAMPS -p traceback-timestamps-without-StopIter-cases/build/python -o traceback-timestamps/Results.silencio/traceback-timestamps-without-StopIter-cases-env=1-eopt.json


Backwards Compatibility
=======================

The feature is disabled by default and does not affect existing exception
handling code.  The ``__timestamp_ns__`` attribute is always readable on
``BaseException`` instances, returning ``0`` when timestamps are not collected.

When timestamps are disabled, exceptions pickle in the traditional 2-tuple
format ``(type, args)``.  When a nonzero timestamp is present, exceptions
pickle as ``(type, args, state_dict)`` with ``__timestamp_ns__`` in the state
dictionary.  Older Python versions unpickle these correctly via
``__setstate__``.  Always emitting the 3-tuple form (with a zero timestamp)
would simplify the logic, but was avoided to keep the pickle output
byte-identical when the feature is off and to avoid any performance impact on
the common case.  Simpler code is generally preferable, but having every
exception pickle increase in size as a default behavior was judged the
greater risk.

Pickled Exception Examples
--------------------------

With traceback timestamp collection enabled:

.. code-block:: console

    $ build/python -X traceback_timestamps=iso -c 'import pickle; print(pickle.dumps(RuntimeError("pep-830"), protocol=pickle.HIGHEST_PROTOCOL))'
    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.'

The special case for ``StopIteration`` means it does not carry the dict with timestamp data:

.. code-block:: console

    $ build/python -X traceback_timestamps=iso -c 'import pickle; print(pickle.dumps(StopIteration("pep-830"), protocol=pickle.HIGHEST_PROTOCOL))'
    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.'

Nor do exceptions carry the timestamp when the feature is disabled (the default):

.. code-block:: console

    $ build/python -X traceback_timestamps=0 -c 'import pickle; print(pickle.dumps(RuntimeError("pep-830"), protocol=pickle.HIGHEST_PROTOCOL))'
    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.'

Which matches what Python 3.13 produces:

.. code-block:: console

    $ python3.13 -c 'import pickle; print(pickle.dumps(RuntimeError("pep-830"), protocol=pickle.HIGHEST_PROTOCOL))'
    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.'

Maintenance Burden
------------------

The ``__timestamp_ns__`` field is a single ``int64_t`` in the ``BaseException``
C struct, present in every exception object regardless of configuration.  The
collection code is a guarded ``clock_gettime`` call; the formatting code only
runs at traceback display time.  Both are small and self-contained.

The main ongoing cost is in the test suite.  Tests that compare traceback
output literally need to account for the optional timestamp suffix.  Two
helpers are provided for this:

- ``traceback.strip_exc_timestamps(text)`` strips ``<@...>`` suffixes from
  formatted traceback strings.
- ``test.support.force_no_traceback_timestamps`` and a ``_test_class`` suffixed
  variant are decorators that disable timestamp collection for the duration of
  a test or ``TestCase`` class.

Outside of the traceback-specific tests, approximately 14 of ~1230 test files
(roughly 1%) needed one of these helpers, typically tests that capture
``stderr`` and match against expected traceback output (e.g. ``test_logging``,
``test_repl``, ``test_wsgiref``, ``test_threading``).  The pattern follows the same approach used by ``force_not_colorized`` for
ANSI color codes in tracebacks.

Outside of CPython's own CI, where timestamps are enabled on a couple of
GitHub Actions runs to maintain coverage, most projects are unlikely to
have the feature enabled while running their test suites.


Security Implications
=====================

None.  The feature is opt-in and disabled by default.


How to Teach This
=================

The ``__timestamp_ns__`` attribute and configuration options will be documented
in the ``exceptions`` module reference, the ``traceback`` module reference,
and the command-line interface documentation.

This is a power feature: disabled by default and invisible unless explicitly
enabled.  It does not need to be covered in introductory material.


Reference Implementation
========================

`CPython PR #129337 <https://github.com/python/cpython/pull/129337>`_.


Rejected Ideas
==============

Using Exception Notes
---------------------

Using :pep:`678`'s ``.add_note()`` to attach timestamps was rejected for
several reasons.  Notes require creating string and list objects at raise time,
imposing overhead even when timestamps are not displayed.  Notes added when
*catching* an exception reflect the catch time, not the raise time, and in
async code this difference can be significant.  Not all exceptions are caught
(some propagate to top level or are logged directly), so catch-time notes
would be applied inconsistently.  A struct field captures the timestamp at the
source and defers all formatting to display time.

Using ``sys.excepthook``
------------------------

``sys.excepthook`` runs only when an uncaught exception reaches the top
level, at display time rather than when the exception was created.  For the
motivating example above, the hook would fire once for the resulting
``ExceptionGroup`` after all tasks have completed, so every sub-exception
would receive the same timestamp.  Exceptions that are caught and logged
never reach the hook at all.

Using ``sys.monitoring``
------------------------

An equivalent feature could in principle be built on :pep:`669` as a
third-party add-on without interpreter changes: register a C-implemented
callable for ``sys.monitoring.events.RAISE`` that reads the clock and either
sets a ``__timestamp_ns__`` attribute on the exception or calls
``add_note()``.  ``STOP_ITERATION`` and ``RERAISE`` are separate events, so
subscribing only to ``RAISE`` avoids the iterator hot path and naturally
preserves the original timestamp on re-raise.

This is expected to cost more than the struct-field approach.  ``RAISE``
fires once per Python frame during unwind rather than once per exception, so
the callback runs more often than the task requires, and each invocation is
dispatched through vectorcall, not called directly.  Without a struct
field, storing the value allocates the exception's instance ``__dict__``
plus a ``PyLongObject`` for the timestamp, or for ``add_note()`` the dict
plus a ``__notes__`` list and formatted string.  One of the limited
monitoring tool IDs is also consumed.  The ``add_note()`` variant renders in
tracebacks without further integration; the ``__timestamp_ns__`` variant
would also need the ``sys.excepthook`` singleton, which other code may be
using, or a monkeypatch of the ``traceback`` module to display the value.
Allocating a list, a string, and potentially an instance dict on every raise
in a program felt extreme enough overhead-wise that this has not been tried.

A middle ground would use ``sys.monitoring`` only for collection while
keeping this PEP's ``int64_t`` struct field on ``BaseException`` and the
``traceback`` module's display of any non-zero ``__timestamp_ns__``.  That
removes the conditional clock read from the ``BaseException`` constructor,
giving a tiny saving when the feature is off, at the cost of notably more
overhead when it is on.

Always Collecting vs. Always Displaying
----------------------------------------

*Collecting* timestamps (a ``clock_gettime`` call during instantiation) and
*displaying* them in formatted tracebacks are separate concerns.

Always displaying was rejected because it adds noise that most users do not
need.  Always collecting (even when display is disabled) is cheap since the
``int64_t`` field exists in the struct regardless, but not collecting avoids
any potential for performance impact when the feature is turned off, and there
is no current reason to collect timestamps that will never be shown.  This could be
revisited if programmatic access to exception timestamps becomes useful
independent of traceback display.

Runtime API
-----------

This is an operator-level setting, intended to be configured by whoever
launches the process rather than by library or application code.  Exposing a
runtime toggle for process-wide state invites different parts of a program to
fight over its value; keeping it fixed at startup avoids that.  It has been
omitted for now to keep things simple.  If there is demand for runtime
configurability, nothing blocks adding that at a later date.

Timestamp in the Traceback Header Line
--------------------------------------

Placing the timestamp in the ``Traceback (most recent call last):`` header
was suggested as less disruptive to tools that parse the ``Type: message``
line.  Each link in a ``__cause__`` or ``__context__`` chain, and each
sub-exception in an ``ExceptionGroup``, does get its own header, so this
covers the common case.  However, the header is only emitted when the
exception has a ``__traceback__``; an exception constructed and attached
directly renders as just ``Type: message`` with no header.  The standard
library itself does this: ``concurrent.futures.ProcessPoolExecutor`` and
``multiprocessing.Pool`` attach a constructed ``_RemoteTraceback`` as
``__cause__`` to carry a worker's formatted traceback across the process
boundary, and it renders with no header.  Appending to the message line is
the only placement guaranteed to render for every displayed exception.

There is also a reading-order argument: when scanning logs for errors,
people typically search for the exception type, land on the
``Type: message`` line, and read upward through the frames.  Putting the
timestamp on that line places it where the eye lands first rather than at
the top of a block that is often read last.

More Descriptive ``timestamps`` Parameter Name
----------------------------------------------

Earlier drafts named the ``traceback`` formatting parameter ``no_timestamp``,
a boolean which read as a double negative.  Longer alternatives such as
``allow_timestamps`` or ``show_timestamp`` were also considered.  The short
positive form ``timestamps`` was chosen as a tri-state defaulting to ``None``
(follow the global configuration), with the docstring and documentation
covering the exact behavior.

Custom Timestamp Formats
------------------------

User-defined format strings would add significant complexity.  The two
built-in formats (``ns``, ``iso``) cover the common needs: decimal seconds
for programmatic use and ISO 8601 for correlation with external systems.

Configurable Control Flow Exception Set
-----------------------------------------

Allowing users to register additional exceptions to skip was rejected.  The
exclusion check runs in the hot path of exception creation and uses C type
pointer identity for speed.  Supporting a configurable set would require
either ``isinstance`` checks (too slow, walks the MRO) or a hash set of
type pointers (complexity with unclear benefit).  ``StopIteration`` and
``StopAsyncIteration`` are the only exceptions raised at frequencies where
the cost of ``clock_gettime`` is measurable.  If a practical need arises, an
API to register additional exclusions efficiently could be added as a follow-on
enhancement.

Millisecond Precision
---------------------

Nanosecond precision was chosen over millisecond to match ``time.time_ns()``
and to provide sufficient resolution for high-frequency exception scenarios.

Separate Millisecond and Microsecond Display Formats
----------------------------------------------------

Earlier drafts offered a ``us`` display format alongside ``ns``, and a
``ms`` format was also suggested.  These differed from ``ns`` only in the
number of fractional digits shown.  Since the decimal output is intended for
machine consumption, truncating precision provides no real benefit, so only
the full-precision ``ns`` format is offered.  Users wanting a
human-readable form should use ``iso``.

Returning ``None`` When Unset
-----------------------------

Returning ``None`` rather than ``0`` when no timestamp was collected would be
slightly more Pythonic, but ``__timestamp_ns__`` is exposed as a plain
``int64_t`` member descriptor on the C struct.  Supporting ``None`` would
require a custom getter and boxed storage.  ``0`` is unambiguous.

Using a Coarse Clock
--------------------

The reference implementation uses ``PyTime_TimeRaw``, which reads the
standard wall clock at full resolution; benchmarking shows this cost is not
observable in practice.  A coarse clock such as ``CLOCK_REALTIME_COARSE``
would be low resolution, insufficient for many needs, for no measurable
benefit.


Open Issues
===========

Display Location
----------------

The current specification appends the timestamp to the ``Type: message``
line.  Placing it in the ``Traceback (most recent call last):`` header
instead, or on a separate line, has been proposed; see `Timestamp in the
Traceback Header Line`_ for the reasoning behind the current choice.  This
is open to revision.  Making the location configurable is also possible, but
every knob added is more complexity to support.

A concern was raised that existing code parses the ``Type: message`` line
and would be disrupted by a suffix.  The same concern applies to the
``Traceback`` header: doctest traceback matchers and CPython's own
``test.support`` helpers already needed adjusting in the reference
implementation, though such cases were rare.

Benchmarking the ``sys.monitoring`` Alternative
-----------------------------------------------

See `Using sys.monitoring`_ under Rejected Ideas for the design and its
expected costs.  If there is a strong preference for avoiding interpreter
changes despite that analysis, a prototype and benchmark could be produced
to settle the question.

Recording Time of First Raise
-----------------------------

Recording the time of first raise, in place of or in addition to
instantiation time, was suggested.  This is deferred rather than rejected:
it would cover the case where an exception is constructed well in advance of
being raised, but it requires identifying a clean hook point in the
interpreter's raise paths (see `Instantiation Time vs. Raise Time`_) and
defining the semantics for re-raise.  We are not aware of code commonly
using a pattern of constructing an exception well before its first use.


Acknowledgements
================

Thanks to Nathaniel J. Smith for the original idea suggestion, and to
Daniel Colascione for initial 2025 review feedback on the implementation.


Change History
==============

- 18-Apr-2026

  - Changed the ``ns`` display format from an integer with an ``ns`` suffix
    to seconds with nine decimal digits, allowing direct use with
    ``datetime.fromtimestamp()``.  Dropped the ``us`` format; ``ns`` is now
    the default when enabled via ``1`` or with no explicit format value.
  - Replaced the ``traceback`` module ``no_timestamp`` parameter with a
    tri-state ``timestamps`` (default ``None``, follow the global setting).
  - Clarified that ``__timestamp_ns__`` is UTC, that clock-read failure
    yields ``0``, and how free-list and singleton ``MemoryError`` instances
    are timestamped; that ``str(exc)`` and ``repr(exc)`` are unchanged; and
    that the ``iso`` display format has microsecond resolution.
  - Noted that ``hasattr``, three-argument ``getattr``, and the dict miss
    paths do not instantiate exceptions in CPython's hot paths and need no
    special handling.
  - Added a Rationale subsection on instantiation time vs. raise time.
  - Added Rejected Ideas entries for ``sys.excepthook``, ``sys.monitoring``,
    placing the timestamp in the ``Traceback`` header line, returning
    ``None`` when unset, separate ms/us display formats, and using a
    coarse-resolution clock; reworded the Runtime API rejection.
  - Added an Open Issues section covering display location, benchmarking the
    ``sys.monitoring`` alternative, and recording time of first raise.
  - Reworked the Motivation example to be self-contained.


Copyright
=========

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.
