File: _traceback.py

package info (click to toggle)
ansible-core 2.19.0~beta6-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 32,628 kB
  • sloc: python: 180,313; cs: 4,929; sh: 4,601; xml: 34; makefile: 21
file content (92 lines) | stat: -rw-r--r-- 3,650 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# Copyright (c) 2024 Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)

"""Internal utility code for supporting traceback reporting."""

from __future__ import annotations

import enum
import traceback

from . import _stack


class TracebackEvent(enum.Enum):
    """The events for which tracebacks can be enabled."""

    ERROR = enum.auto()
    WARNING = enum.auto()
    DEPRECATED = enum.auto()
    DEPRECATED_VALUE = enum.auto()  # implies DEPRECATED


def traceback_for() -> list[str]:
    """Return a list of traceback event names (not enums) which are enabled."""
    return [value.name.lower() for value in TracebackEvent if is_traceback_enabled(value)]


def is_traceback_enabled(event: TracebackEvent) -> bool:
    """Return True if tracebacks are enabled for the specified event, otherwise return False."""
    return _is_traceback_enabled(event)


def maybe_capture_traceback(msg: str, event: TracebackEvent) -> str | None:
    """
    Optionally capture a traceback for the current call stack, formatted as a string, if the specified traceback event is enabled.
    Frames marked with the `_skip_stackwalk` local are omitted.
    """
    _skip_stackwalk = True

    if not is_traceback_enabled(event):
        return None

    tb_lines = []

    if frame_info := _stack.caller_frame():
        # DTFIX-FUTURE: rewrite target-side tracebacks to point at controller-side paths?
        tb_lines.append('Traceback (most recent call last):\n')
        tb_lines.extend(traceback.format_stack(frame_info.frame))
        tb_lines.append(f'Message: {msg}\n')
    else:
        tb_lines.append('(frame not found)\n')  # pragma: nocover

    return ''.join(tb_lines)


def maybe_extract_traceback(exception: BaseException, event: TracebackEvent) -> str | None:
    """Optionally extract a formatted traceback from the given exception, if the specified traceback event is enabled."""

    if not is_traceback_enabled(event):
        return None

    # deprecated: description='use the single-arg version of format_traceback' python_version='3.9'
    tb_lines = traceback.format_exception(type(exception), exception, exception.__traceback__)

    return ''.join(tb_lines)


_module_tracebacks_enabled_events: frozenset[TracebackEvent] | None = None
"""Cached enabled TracebackEvent values extracted from `_ansible_tracebacks_for` module arg."""


def _is_module_traceback_enabled(event: TracebackEvent) -> bool:
    """Module utility function to lazily load traceback config and determine if traceback collection is enabled for the specified event."""
    global _module_tracebacks_enabled_events

    if _module_tracebacks_enabled_events is None:
        try:
            # Suboptimal error handling, but since import order can matter, and this is a critical error path, better to fail silently
            # than to mask the triggering error by issuing a new error/warning here.
            from ..basic import _PARSED_MODULE_ARGS

            _module_tracebacks_enabled_events = frozenset(
                TracebackEvent[value.upper()] for value in _PARSED_MODULE_ARGS.get('_ansible_tracebacks_for')
            )  # type: ignore[union-attr]
        except BaseException:
            return True  # if things failed early enough that we can't figure this out, assume we want a traceback for troubleshooting

    return event in _module_tracebacks_enabled_events


_is_traceback_enabled = _is_module_traceback_enabled
"""Callable to determine if tracebacks are enabled. Overridden on the controller by display. Use `is_traceback_enabled` instead of calling this directly."""