Skip to content

Commit 0d83447

Browse files
committed
Allow PyErr_Display* to emit timestamps as well.
1 parent 6809426 commit 0d83447

6 files changed

Lines changed: 100 additions & 10 deletions

File tree

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ struct _Py_global_strings {
214214
STRUCT_FOR_ID(__sub__)
215215
STRUCT_FOR_ID(__subclasscheck__)
216216
STRUCT_FOR_ID(__subclasshook__)
217+
STRUCT_FOR_ID(__timestamp_ns__)
217218
STRUCT_FOR_ID(__truediv__)
218219
STRUCT_FOR_ID(__type_params__)
219220
STRUCT_FOR_ID(__typing_is_unpacked_typevartuple__)
@@ -254,6 +255,7 @@ struct _Py_global_strings {
254255
STRUCT_FOR_ID(_loop)
255256
STRUCT_FOR_ID(_needs_com_addref_)
256257
STRUCT_FOR_ID(_only_immortal)
258+
STRUCT_FOR_ID(_print_exception_bltin)
257259
STRUCT_FOR_ID(_restype_)
258260
STRUCT_FOR_ID(_showwarnmsg)
259261
STRUCT_FOR_ID(_shutdown)
@@ -262,6 +264,7 @@ struct _Py_global_strings {
262264
STRUCT_FOR_ID(_strptime_datetime_date)
263265
STRUCT_FOR_ID(_strptime_datetime_datetime)
264266
STRUCT_FOR_ID(_strptime_datetime_time)
267+
STRUCT_FOR_ID(_timestamp_formatter)
265268
STRUCT_FOR_ID(_type_)
266269
STRUCT_FOR_ID(_uninitialized_submodules)
267270
STRUCT_FOR_ID(_warn_unawaited_coroutine)

Include/internal/pycore_runtime_init_generated.h

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/traceback.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
'format_tb', 'print_exc', 'format_exc', 'print_exception',
1717
'print_last', 'print_stack', 'print_tb', 'clear_frames',
1818
'FrameSummary', 'StackSummary', 'TracebackException',
19-
'walk_stack', 'walk_tb', 'print_list']
19+
'walk_stack', 'walk_tb', 'print_list', 'strip_exc_timestamps']
2020

2121
#
2222
# Formatting and printing lists of traceback lines.
@@ -126,8 +126,9 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
126126
position of the error.
127127
"""
128128
colorize = kwargs.get("colorize", False)
129+
no_timestamp = kwargs.get("no_timestamp", False)
129130
value, tb = _parse_value_tb(exc, value, tb)
130-
te = TracebackException(type(value), value, tb, limit=limit, compact=True)
131+
te = TracebackException(type(value), value, tb, limit=limit, compact=True, no_timestamp=no_timestamp)
131132
te.print(file=file, chain=chain, colorize=colorize)
132133

133134

@@ -151,8 +152,9 @@ def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
151152
printed as does print_exception().
152153
"""
153154
colorize = kwargs.get("colorize", False)
155+
no_timestamp = kwargs.get("no_timestamp", False)
154156
value, tb = _parse_value_tb(exc, value, tb)
155-
te = TracebackException(type(value), value, tb, limit=limit, compact=True)
157+
te = TracebackException(type(value), value, tb, limit=limit, compact=True, no_timestamp=no_timestamp)
156158
return list(te.format(chain=chain, colorize=colorize))
157159

158160

@@ -172,9 +174,10 @@ def format_exception_only(exc, /, value=_sentinel, *, show_group=False, **kwargs
172174
well, recursively, with indentation relative to their nesting depth.
173175
"""
174176
colorize = kwargs.get("colorize", False)
177+
no_timestamp = kwargs.get("no_timestamp", False)
175178
if value is _sentinel:
176179
value = exc
177-
te = TracebackException(type(value), value, None, compact=True)
180+
te = TracebackException(type(value), value, None, compact=True, no_timestamp=no_timestamp)
178181
return list(te.format_exception_only(show_group=show_group, colorize=colorize))
179182

180183

@@ -194,6 +197,25 @@ def _timestamp_formatter(ns):
194197
_TIMESTAMP_FORMAT = ""
195198

196199

200+
# The regular expression to match timestamps as formatted in tracebacks.
201+
# Not compiled to avoid importing the re module by default.
202+
TIMESTAMP_AFTER_EXC_MSG_RE_GROUP = r"(?P<timestamp> <@[0-9:.Tsnu-]{18,26}>)"
203+
204+
205+
def strip_exc_timestamps(output):
206+
"""Remove exception timestamps from output; for use by tests."""
207+
if _TIMESTAMP_FORMAT:
208+
import re
209+
if isinstance(output, str):
210+
pattern = TIMESTAMP_AFTER_EXC_MSG_RE_GROUP
211+
empty = ""
212+
else:
213+
pattern = TIMESTAMP_AFTER_EXC_MSG_RE_GROUP.encode()
214+
empty = b""
215+
return re.sub(pattern, empty, output)
216+
return output
217+
218+
197219
# -- not official API but folk probably use these two functions.
198220

199221
def _format_final_exc_line(etype, value, *, insert_final_newline=True, colorize=False, timestamp_ns=0):
@@ -1053,7 +1075,8 @@ class TracebackException:
10531075

10541076
def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
10551077
lookup_lines=True, capture_locals=False, compact=False,
1056-
max_group_width=15, max_group_depth=10, save_exc_type=True, _seen=None):
1078+
max_group_width=15, max_group_depth=10, save_exc_type=True,
1079+
no_timestamp=False, _seen=None):
10571080
# NB: we need to accept exc_traceback, exc_value, exc_traceback to
10581081
# permit backwards compat with the existing API, otherwise we
10591082
# need stub thunk objects just to glue it together.
@@ -1082,15 +1105,17 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
10821105
self.__notes__ = [
10831106
f'Ignored error getting __notes__: {_safe_string(e, '__notes__', repr)}']
10841107

1085-
self._timestamp_ns = exc_value.__timestamp_ns__ if _TIMESTAMP_FORMAT else 0
10861108
self._is_syntax_error = False
10871109
self._have_exc_type = exc_type is not None
10881110
if exc_type is not None:
10891111
self.exc_type_qualname = exc_type.__qualname__
10901112
self.exc_type_module = exc_type.__module__
1113+
self._timestamp_ns = (exc_value.__timestamp_ns__
1114+
if _TIMESTAMP_FORMAT and not no_timestamp else 0)
10911115
else:
10921116
self.exc_type_qualname = None
10931117
self.exc_type_module = None
1118+
self._timestamp_ns = 0
10941119

10951120
if exc_type and issubclass(exc_type, SyntaxError):
10961121
# Handle SyntaxError's specially
@@ -1227,7 +1252,20 @@ def _load_lines(self):
12271252

12281253
def __eq__(self, other):
12291254
if isinstance(other, TracebackException):
1230-
return self.__dict__ == other.__dict__
1255+
# It is unlikely anything would ever be equal when timestamp
1256+
# collection is enabled without this. We avoid extra work when
1257+
# it is not enabled.
1258+
if self._timestamp_ns:
1259+
s_dict = self.__dict__.copy()
1260+
s_dict["_timestamp_ns"] = 0
1261+
else:
1262+
s_dict = self.__dict__
1263+
if other._timestamp_ns:
1264+
o_dict = other.__dict__.copy()
1265+
o_dict["_timestamp_ns"] = 0
1266+
else:
1267+
o_dict = other.__dict__
1268+
return s_dict == o_dict
12311269
return NotImplemented
12321270

12331271
def __str__(self):

Python/pythonrun.c

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "pycore_ceval.h" // _Py_EnterRecursiveCall()
1818
#include "pycore_compile.h" // _PyAST_Compile()
1919
#include "pycore_interp.h" // PyInterpreterState.importlib
20+
#include "pycore_long.h" // _PyLong_IsZero()
2021
#include "pycore_object.h" // _PyDebug_PrintTotalRefs()
2122
#include "pycore_parser.h" // _PyParser_ASTFromString()
2223
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
@@ -915,6 +916,36 @@ print_exception_message(struct exception_print_context *ctx, PyObject *type,
915916
if (res < 0) {
916917
return -1;
917918
}
919+
920+
/* attempt to append the exception timestamp if configured to do so
921+
* in the traceback module. non-fatal if any of this fails. */
922+
PyObject *timestamp_formatter = PyImport_ImportModuleAttr(
923+
&_Py_ID(traceback),
924+
&_Py_ID(_timestamp_formatter));
925+
if (timestamp_formatter && PyCallable_Check(timestamp_formatter)) {
926+
PyObject *ns_obj = PyObject_GetAttr(value, &_Py_ID(__timestamp_ns__));
927+
if (ns_obj && PyLong_Check(ns_obj) && !_PyLong_IsZero((PyLongObject *)ns_obj)) {
928+
PyObject* ns_str = PyObject_CallOneArg(timestamp_formatter, ns_obj);
929+
if (ns_str) {
930+
if (PyFile_WriteString(" ", f) >= 0) {
931+
if (PyFile_WriteObject(ns_str, f, Py_PRINT_RAW) < 0) {
932+
#ifdef Py_DEBUG
933+
PyFile_WriteString("<traceback._timestamp_formatter failed>", f);
934+
#endif
935+
}
936+
}
937+
Py_DECREF(ns_str);
938+
} else {
939+
PyErr_Clear();
940+
}
941+
} else {
942+
PyErr_Clear();
943+
}
944+
Py_XDECREF(ns_obj);
945+
} else {
946+
PyErr_Clear();
947+
}
948+
Py_XDECREF(timestamp_formatter);
918949
}
919950

920951
return 0;
@@ -1108,9 +1139,9 @@ _PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb)
11081139
int unhandled_keyboard_interrupt = _PyRuntime.signals.unhandled_keyboard_interrupt;
11091140

11101141
// Try first with the stdlib traceback module
1111-
PyObject *print_exception_fn = PyImport_ImportModuleAttrString(
1112-
"traceback",
1113-
"_print_exception_bltin");
1142+
PyObject *print_exception_fn = PyImport_ImportModuleAttr(
1143+
&_Py_ID(traceback),
1144+
&_Py_ID(_print_exception_bltin));
11141145
if (print_exception_fn == NULL || !PyCallable_Check(print_exception_fn)) {
11151146
goto fallback;
11161147
}

0 commit comments

Comments
 (0)