Как использовать трассировку стека в Python (модуль traceback)
Основы трассировки стека и модуль traceback
Трассировка стека (stack trace или traceback) показывает последовательность вызовов функций, которая привела к возникновению исключения. В Python за работу с ней отвечает встроенный модуль traceback. Он позволяет получать, форматировать и выводить диагностическую информацию об ошибках.
Как эффективно получить и залогировать полный traceback?
Основной способ - использовать функцию traceback.format_exc() или traceback.print_exc(). Первая возвращает строку с отформатированным traceback, вторая выводит его в stderr. Обычно их вызывают в блоке except.
import traceback
try:
1 / 0
except ZeroDivisionError:
tb_str = traceback.format_exc()
print('Получен traceback:')
print(tb_str)Client error python (ошибка http-клиента в python)
Получен traceback: Traceback (most recent call last): File "<stdin>", line 2, in <module> ZeroDivisionError: division by zero
No installed python found (python не найден в системе)
Проблема: При использовании print_exc() без аргументов вывод идёт в sys.stderr, что может быть неудобно при перенаправлении потоков. Решение - передать файловый объект или использовать форматирование в строку.
Как сохранить traceback в переменную для дальнейшего анализа?
Функция traceback.format_exc() возвращает строку. Её можно сохранить в лог, отправить по сети или вывести в GUI.
import traceback, logging
try:
open('nonexistent.file')
except FileNotFoundError:
tb = traceback.format_exc()
logging.error('Ошибка открытия файла:\n%s', tb)
Python traceback using (трассировка ошибок в python)
Типичная ошибка: Забывают вызвать форматирование внутри блока except, когда исключение активно. Если вызов происходит вне except, sys.exc_info() возвращает None, и traceback будет пустым. Всегда проверяйте, что код находится в обработчике исключения.
Как записать traceback в лог с помощью logging?
Модуль logging предоставляет метод logging.exception(), который автоматически добавляет traceback текущего исключения в сообщение лога.
import logging
logging.basicConfig(level=logging.ERROR)
try:
x = [1, 2, 3][10]
except IndexError:
logging.exception('Произошла ошибка индекса')Python pip not found (ошибка 'pip not found' в python)
ERROR:root:Произошла ошибка индекса Traceback (most recent call last): File "<stdin>", line 3, in <module> IndexError: list index out of range
Unable to locate package python (ошибка 'unable to locate package' в python)
Особенность: logging.exception() использует уровень ERROR. Для других уровней (например, WARNING) нужно вызывать logger.warning(tb, exc_info=True). Иначе traceback не будет добавлен.
Как извлечь только верхние или нижние фреймы стека?
Функция traceback.extract_tb() возвращает список объектов FrameSummary. Параметр limit ограничивает количество фреймов. Отрицательное значение limit возвращает фреймы с конца.
import traceback, sys
def f():
return 1/0
def g():
f()
try:
g()
except:
_, _, tb = sys.exc_info()
frames = traceback.extract_tb(tb, limit=2) # только последние 2 фрейма
for frame in frames:
print(f'{frame.filename}:{frame.lineno} in {frame.name}')File not found python (ошибка filenotfounderror в python)
<stdin>:3 in f <stdin>:6 in g
Python modulenotfounderror no module named (ошибка modulenotfounderror)
Проблема: При использовании sys.exc_info() важно не присваивать результат нескольким переменным, иначе можно потерять ссылку на трейсбек. Лучше сразу извлекать tb и работать с ним.
Как получить traceback для текущего стека без исключения?
Функция traceback.print_stack() выводит текущий стек вызовов. Полезна для отладки, чтобы понять, откуда была вызвана функция.
import traceback
def alpha():
beta()
def beta():
traceback.print_stack()
alpha()Io error python (ошибка ввода-вывода в python)
File "<stdin>", line 9, in <module>
alpha()
File "<stdin>", line 4, in alpha
beta()
File "<stdin>", line 7, in beta
traceback.print_stack()ошибка компиляции python (ошибка компиляции (синтаксиса) в python)
Предостережение: print_stack() выводит в stderr. Для сохранения используйте traceback.format_stack(), которая возвращает список строк.
Как обработать исключение с сохранением цепочки стека (chained exceptions)?
При повторном возбуждении исключения используйте конструкцию raise ... from, чтобы сохранить исходный traceback. Это помогает отследить первопричину.
def parse(data):
try:
return int(data)
except ValueError as e:
raise RuntimeError('Неверный формат') from e
try:
parse('abc')
except RuntimeError as err:
print('Исключение с цепочкой:')
traceback.print_exc()Python traceback (трассировка стека в python)
Исключение с цепочкой:
Traceback (most recent call last):
File "<stdin>", line 16, in parse
return int(data)
ValueError: invalid literal for int() with base 10: 'abc'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 23, in <module>
parse('abc')
File "<stdin>", line 18, in parse
raise RuntimeError('Неверный формат') from e
RuntimeError: Неверный форматScript not found python (ошибка 'script not found')
Ошибка: Если используется raise ... from None, цепочка обрывается, и исходный traceback теряется. Применяйте это только если исходное исключение не должно быть видно.
Как настроить формат traceback в тестах (unittest)?
Модуль unittest выводит traceback автоматически при падении тестов. Если нужен свой формат, можно заменить вывод с помощью traceback.print_exception() в кастомном TestResult.
import unittest, traceback, io
class CustomTestResult(unittest.TextTestResult):
def addFailure(self, test, err):
with io.StringIO() as buf:
traceback.print_exception(*err, file=buf)
super().addFailure(test, (err[0], err[1], err[2]))
if __name__ == '__main__':
unittest.main(testRunner=unittest.TextTestRunner(resultclass=CustomTestResult))
Сложность: Для глубокой кастомизации потребуется переопределить методы TestResult. Необходимо хорошо понимать внутреннее устройство unittest.
Расширенные примеры работы с traceback
В этом разделе приведены более сложные сценарии использования модуля traceback, которые часто встречаются на практике.
Кастомное форматирование с TracebackException
Класс traceback.TracebackException позволяет детально управлять форматированием: исключать контекст, ограничивать глубину, изменять представление стека.
import traceback, sys
try:
1 / 0
except ZeroDivisionError:
exc_type, exc_value, exc_tb = sys.exc_info()
tbe = traceback.TracebackException(exc_type, exc_value, exc_tb, limit=2)
# Пропускаем первую строку 'Traceback...'
formatted = list(tbe.format())[1:] # список строк без заголовка
for line in formatted:
print(line, end='')
File "<stdin>", line 3, in <module> ZeroDivisionError: division by zero
Цепочка исключений с явным указанием причины
При перехвате одного исключения и возбуждении другого с сохранением контекста (использование raise ... from) модуль traceback показывает обе трассировки.
import traceback
def read_data(filepath):
try:
with open(filepath) as f:
return f.read()
except FileNotFoundError as e:
raise ValueError(f'Файл {filepath} не найден') from e
try:
read_data('/nonexistent')
except ValueError:
traceback.print_exc()
Traceback (most recent call last):
File "<stdin>", line 6, in read_data
with open(filepath) as f:
FileNotFoundError: [Errno 2] No such file or directory: '/nonexistent'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 14, in <module>
read_data('/nonexistent')
File "<stdin>", line 8, in read_data
raise ValueError(f'Файл {filepath} не найден') from e
ValueError: Файл /nonexistent не найден
Извлечение стека из нескольких потоков
Для получения стека вызовов другого потока используется traceback.format_stack(sys._current_frames()[thread_id]). Это полезно при анализе взаимоблокировок.
import sys, threading, traceback
def worker():
import time
time.sleep(10)
t = threading.Thread(target=worker)
t.start()
import time
time.sleep(0.1) # даём потоку запуститься
# Получаем стек потока t
frames = sys._current_frames()
if t.ident in frames:
stack = traceback.format_stack(frames[t.ident])
print(''.join(stack))
File "<stdin>", line 8, in worker
import time
File "<stdin>", line 9, in worker
time.sleep(10)
Ручное построение traceback из объектов фреймов
Иногда требуется создать собственный traceback, например, для эмуляции исключения при тестировании. Можно использовать traceback.FrameSummary и traceback.StackSummary.
import traceback
# Создаём один фрейм вручную
frame_summary = traceback.FrameSummary('myscript.py', 42, 'calculate')
stack = traceback.StackSummary.from_list([frame_summary])
# Форматируем как строку
result = stack.format()
print(''.join(result))
File "myscript.py", line 42, in calculate
Логирование traceback с кастомным форматтером
Можно настроить обработчик логов так, чтобы он добавлял traceback только определённого уровня, используя exc_info в сообщении.
import logging, traceback
logger = logging.getLogger('app')
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.WARNING)
try:
1/0
except ZeroDivisionError:
# exc_info=True добавит traceback к сообщению
logger.warning('Обнаружено деление на ноль', exc_info=True)
2025-03-21 12:00:00,000 - WARNING - Обнаружено деление на ноль
Traceback (most recent call last):
File "<stdin>", line 11, in <module>
1/0
ZeroDivisionError: division by zero