Руководство по логированию ошибок Python для разработчиков
Узнайте, как логировать подробные ошибки Python с трассировкой стека, номерами строк и отладочной информацией. Освойте лучшие практики логирования исключений.
Как можно записать в лог ошибку Python с информацией отладки?
Я вывожу сообщения об исключениях Python в файл журнала с помощью logging.error:
import logging
try:
1/0
except ZeroDivisionError as e:
logging.error(e) # ERROR:root:division by zero
Можно ли залогировать более подробную информацию об исключении и коде, который его вызвал — например, номера строк или трассировку стека — вместо простого текста исключения?
Да, вы можете логировать подробную информацию об ошибках Python, включая трассировки стека и номера строк. Вместо использования logging.error(e), которое захватывает только строку исключения, следует использовать logging.exception() или передавать exc_info=True, чтобы включить полный трассировочный стек с номерами строк, именами файлов и информацией о вызовах.
Содержание
- Базовые методы логирования исключений
- Техники подробной информации об исключениях
- Лучшие практики логирования исключений
- Расширенный контекст ошибок и настройка
- Настройки и варианты форматирования
Базовые методы логирования исключений
Использование logging.exception() для автоматических трассировок стека
Самый простой и распространённый способ — использовать logging.exception(), который автоматически включает трассировку исключения с номерами строк и информацией о файлах:
import logging
try:
1 / 0
except ZeroDivisionError as e:
logging.exception("Произошла деление на ноль")
Вывод будет выглядеть так:
ERROR:root:Произошла деление на ноль
Traceback (most recent call last):
File "script.py", line 4, in <module>
1 / 0
ZeroDivisionError: division by zero
Использование параметра exc_info=True
Для других уровней логирования или когда нужна более гибкая настройка, можно использовать параметр exc_info=True:
import logging
try:
result = 10 / 0
except ZeroDivisionError as e:
logging.critical("Критическая ошибка деления", exc_info=True)
logging.error("Произошла ошибка", exc_info=True)
logging.warning("Предупреждение о потенциальной проблеме", exc_info=True)
Техники подробной информации об исключениях
Использование traceback.format_exc()
Для более детального контроля над форматированием исключения можно использовать traceback.format_exc(), чтобы получить полную трассировку как строку:
import logging
import traceback
try:
# Некоторая операция, которая может завершиться ошибкой
data = [1, 2, 3]
value = data[5] # Вызывает IndexError
except Exception as e:
# Получаем полную трассировку как строку
error_msg = traceback.format_exc()
# Логируем с дополнительным контекстом
logging.error(f"Операция завершилась неудачей: {error_msg}")
Использование sys.exc_info() для максимального контроля
Для самой подробной информации об исключении можно использовать sys.exc_info(), чтобы получить сырые данные исключения:
import logging
import sys
import traceback
try:
# Симуляция ошибки
open("nonexistent_file.txt", "r")
except Exception as e:
exc_type, exc_value, exc_traceback = sys.exc_info()
# Форматируем детали исключения
error_details = {
'exception_type': exc_type.__name__,
'exception_message': str(exc_value),
'traceback': traceback.format_exception(exc_type, exc_value, exc_traceback)
}
# Логируем полную информацию об ошибке
logging.error(f"Произошла ошибка: {error_details}")
Извлечение конкретных номеров строк и информации о файле
Если нужно извлечь конкретные номера строк и информацию о файле из трассировки:
import logging
import traceback
import sys
def log_with_location():
try:
# Ваш код здесь
result = 1 / 0
except Exception:
exc_type, exc_value, exc_traceback = sys.exc_info()
# Получаем самый последний кадр
frame = exc_traceback.tb_frame
filename = frame.f_code.co_filename
lineno = frame.f_lineno
func_name = frame.f_code.co_name
logging.error(
f"Ошибка в {func_name}() в {filename}:{lineno}: {str(exc_value)}",
exc_info=True
)
Лучшие практики логирования исключений
Когда использовать каждый метод
logging.exception(): используйте для исключений в основной логике приложения, когда нужно захватить полный контекст.exc_info=True: используйте, когда нужно логировать на разных уровнях (DEBUG, INFO, WARNING), но при этом включать трассировки стека.traceback.format_exc(): используйте, когда нужно обработать или изменить трассировку перед логированием.sys.exc_info(): используйте, когда нужна максимальная гибкость в обработке исключений и форматировании.
Производительность
Будьте внимательны к производительности при логировании исключений, особенно в продакшене:
import logging
# Хорошо: логируем трассировки стека только для ошибок, а не для debug/info
try:
result = complex_operation()
except Exception as e:
logging.error("Сложная операция завершилась неудачей", exc_info=True) # Трассировка только для ошибок
Безопасность
Не логируйте чувствительную информацию в трассировках стека:
# Плохо: может логировать пароли или ключи API в трассировках
try:
api_call_with_secrets(secrets)
except Exception as e:
logging.error("Вызов API завершился неудачей", exc_info=True) # Может раскрыть секреты
# Хорошо: очищаем перед логированием
try:
api_call_with_secrets(secrets)
except Exception as e:
# Логируем только тип и сообщение ошибки, без полной трассировки
logging.error(f"Вызов API завершился с {type(e).__name__}: {str(e)}")
Расширенный контекст ошибок и настройка
Добавление пользовательского контекста к исключениям
Улучшите логирование ошибок, добавив дополнительный контекст:
import logging
import traceback
from contextlib import contextmanager
import sys
class ErrorLogger:
def __init__(self, logger):
self.logger = logger
@contextmanager
def error_context(self, operation_name, **context):
try:
yield
except Exception as e:
# Захватываем текущую трассировку стека
exc_type, exc_value, exc_traceback = sys.exc_info()
# Форматируем детали ошибки
error_details = {
"operation": operation_name,
"error_type": exc_type.__name__,
"error_message": str(exc_value),
"context": context,
"stack_trace": traceback.format_exception(exc_type, exc_value, exc_traceback)
}
self.logger.error(f"Операция завершилась неудачей: {error_details}")
# Использование
logger = logging.getLogger(__name__)
error_logger = ErrorLogger(logger)
with error_logger.error_context("data_processing", user_id=123, batch_id="batch_456"):
# Ваш код здесь
result = 1 / 0
Структурированное логирование в JSON
Для современных систем логирования рассмотрите структурированное логирование:
import logging
import json
import traceback
import datetime
# Настройка логирования в JSON
logging.basicConfig(level=logging.INFO, format='%(message)s')
def log_structured_error(message, **kwargs):
try:
# Ваше действие
result = perform_operation()
except Exception as e:
error_record = {
"level": "ERROR",
"message": message,
"timestamp": datetime.datetime.utcnow().isoformat(),
"exception": {
"type": type(e).__name__,
"message": str(e),
"traceback": traceback.format_exc()
},
**kwargs
}
logging.info(json.dumps(error_record))
# Использование
log_structured_error(
"Обработка данных завершилась неудачей",
operation="user_validation",
user_id=123,
additional_info={"retry_count": 3}
)
Настройки и варианты форматирования
Пользовательский формат логов для деталей исключений
Настройте логирование так, чтобы включать подробную информацию об исключениях:
import logging
# Настройка подробного формата логов
logging.basicConfig(
filename='app.log',
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(filename)s:%(lineno)d'
)
try:
result = 10 / 0
except ZeroDivisionError as e:
logging.exception("Ошибка деления на ноль")
Расширенные форматтеры с обработкой исключений
Создайте пользовательские форматтеры для улучшенного логирования исключений:
import logging
import traceback
class DetailedFormatter(logging.Formatter):
def formatException(self, exc_info):
# Пользовательское форматирование исключения
return f"\nДЕТАЛИ ИСКЛЮЧЕНИЯ:\n{traceback.format_exception(*exc_info)}"
def format(self, record):
# Добавляем пользовательские поля
record.custom_field = "additional_info"
return super().format(record)
# Настройка с пользовательским форматтером
formatter = DetailedFormatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(filename)s:%(lineno)d'
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
try:
# Ваш код
result = 1 / 0
except Exception as e:
logger.error("Операция завершилась неудачей", exc_info=True)
Ротация логов и хранение исключений
Для продакшн-среды реализуйте правильную ротацию логов:
import logging
from logging.handlers import RotatingFileHandler
# Настройка обработчика с ротацией файлов по размеру и количеству резервных копий
handler = RotatingFileHandler(
'app.log',
maxBytes=10*1024*1024, # 10 МБ
backupCount=5
)
handler.setFormatter(logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(filename)s:%(lineno)d'
))
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.ERROR)
try:
# Ваш код
complex_operation()
except Exception as e:
logger.exception("Сложная операция завершилась неудачей")
Источники
- Python Logging Exception Guide - Last9
- Python Documentation - traceback Module
- Stack Overflow - How do I log a Python error with debug information?
- Python Documentation - Logging HOWTO
- Real Python - Capturing Stack Traces
- Delft Stack - How to Log an Error With Debug Information in Python
- Better Stack Community - How to log Python error with stack trace
- Uptrace - Advanced Python Logging: Mastering Configuration & Best Practices
Заключение
Чтобы эффективно логировать ошибки Python с подробной информацией отладки, у вас есть несколько мощных вариантов:
- Используйте
logging.exception()для самого простого подхода, автоматически включающего полные трассировки стека с номерами строк и информацией о файлах. - Используйте параметр
exc_info=Trueкогда нужно логировать на разных уровнях, но при этом захватывать подробную информацию об исключении. - Используйте
traceback.format_exc()для более детального контроля над форматированием трассировки. - Используйте
sys.exc_info()когда нужна максимальная гибкость в обработке исключений и добавлении пользовательского контекста.
Для продакшн-среды рассмотрите структурированное логирование в формате JSON, правильную ротацию логов и пользовательские форматтеры, которые усиливают детали исключений. Всегда учитывайте влияние на производительность и безопасность при логировании чувствительной информации из трассировок стека. Выбор метода зависит от конкретных требований вашего проекта, но главное — захватывать всю необходимую отладочную информацию для быстрого устранения неполадок и мониторинга состояния приложения.