1# SPDX-License-Identifier: MIT OR Apache-2.0
2# This file is dual licensed under the terms of the Apache License, Version
3# 2.0, and the MIT License. See the LICENSE file in the root of this
4# repository for complete details.
5
6from __future__ import annotations
7
8import sys
9import traceback
10
11from io import StringIO
12from types import FrameType
13from typing import Callable
14
15from .contextvars import _ASYNC_CALLING_STACK
16from .typing import ExcInfo
17
18
19def _format_exception(exc_info: ExcInfo) -> str:
20 """
21 Prettyprint an `exc_info` tuple.
22
23 Shamelessly stolen from stdlib's logging module.
24 """
25 sio = StringIO()
26
27 traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], None, sio)
28 s = sio.getvalue()
29 sio.close()
30 if s[-1:] == "\n":
31 s = s[:-1]
32
33 return s
34
35
36def _find_first_app_frame_and_name(
37 additional_ignores: list[str] | None = None,
38 *,
39 _getframe: Callable[[], FrameType] = sys._getframe,
40) -> tuple[FrameType, str]:
41 """
42 Remove all intra-structlog calls and return the relevant app frame.
43
44 Args:
45 additional_ignores:
46 Additional names with which the first frame must not start.
47
48 _getframe:
49 Callable to find current frame. Only for testing to avoid
50 monkeypatching of sys._getframe.
51
52 Returns:
53 tuple of (frame, name)
54 """
55 ignores = tuple(["structlog"] + (additional_ignores or []))
56 f = _ASYNC_CALLING_STACK.get(_getframe())
57 name = f.f_globals.get("__name__") or "?"
58 while name.startswith(ignores):
59 if f.f_back is None:
60 name = "?"
61 break
62 f = f.f_back
63 name = f.f_globals.get("__name__") or "?"
64 return f, name
65
66
67def _format_stack(frame: FrameType) -> str:
68 """
69 Pretty-print the stack of *frame* like logging would.
70 """
71 sio = StringIO()
72
73 sio.write("Stack (most recent call last):\n")
74 traceback.print_stack(frame, file=sio)
75 sinfo = sio.getvalue()
76 if sinfo[-1] == "\n":
77 sinfo = sinfo[:-1]
78 sio.close()
79
80 return sinfo