Другое

Руководство по логированию ошибок Python для разработчиков

Узнайте, как логировать подробные ошибки Python с трассировкой стека, номерами строк и отладочной информацией. Освойте лучшие практики логирования исключений.

Как можно записать в лог ошибку Python с информацией отладки?

Я вывожу сообщения об исключениях Python в файл журнала с помощью logging.error:

python
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(), который автоматически включает трассировку исключения с номерами строк и информацией о файлах:

python
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:

python
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(), чтобы получить полную трассировку как строку:

python
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(), чтобы получить сырые данные исключения:

python
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}")

Извлечение конкретных номеров строк и информации о файле

Если нужно извлечь конкретные номера строк и информацию о файле из трассировки:

python
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(): используйте, когда нужна максимальная гибкость в обработке исключений и форматировании.

Производительность

Будьте внимательны к производительности при логировании исключений, особенно в продакшене:

python
import logging

# Хорошо: логируем трассировки стека только для ошибок, а не для debug/info
try:
    result = complex_operation()
except Exception as e:
    logging.error("Сложная операция завершилась неудачей", exc_info=True)  # Трассировка только для ошибок

Безопасность

Не логируйте чувствительную информацию в трассировках стека:

python
# Плохо: может логировать пароли или ключи 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)}")

Расширенный контекст ошибок и настройка

Добавление пользовательского контекста к исключениям

Улучшите логирование ошибок, добавив дополнительный контекст:

python
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

Для современных систем логирования рассмотрите структурированное логирование:

python
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}
)

Настройки и варианты форматирования

Пользовательский формат логов для деталей исключений

Настройте логирование так, чтобы включать подробную информацию об исключениях:

python
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("Ошибка деления на ноль")

Расширенные форматтеры с обработкой исключений

Создайте пользовательские форматтеры для улучшенного логирования исключений:

python
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)

Ротация логов и хранение исключений

Для продакшн-среды реализуйте правильную ротацию логов:

python
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("Сложная операция завершилась неудачей")

Источники

  1. Python Logging Exception Guide - Last9
  2. Python Documentation - traceback Module
  3. Stack Overflow - How do I log a Python error with debug information?
  4. Python Documentation - Logging HOWTO
  5. Real Python - Capturing Stack Traces
  6. Delft Stack - How to Log an Error With Debug Information in Python
  7. Better Stack Community - How to log Python error with stack trace
  8. Uptrace - Advanced Python Logging: Mastering Configuration & Best Practices

Заключение

Чтобы эффективно логировать ошибки Python с подробной информацией отладки, у вас есть несколько мощных вариантов:

  1. Используйте logging.exception() для самого простого подхода, автоматически включающего полные трассировки стека с номерами строк и информацией о файлах.
  2. Используйте параметр exc_info=True когда нужно логировать на разных уровнях, но при этом захватывать подробную информацию об исключении.
  3. Используйте traceback.format_exc() для более детального контроля над форматированием трассировки.
  4. Используйте sys.exc_info() когда нужна максимальная гибкость в обработке исключений и добавлении пользовательского контекста.

Для продакшн-среды рассмотрите структурированное логирование в формате JSON, правильную ротацию логов и пользовательские форматтеры, которые усиливают детали исключений. Всегда учитывайте влияние на производительность и безопасность при логировании чувствительной информации из трассировок стека. Выбор метода зависит от конкретных требований вашего проекта, но главное — захватывать всю необходимую отладочную информацию для быстрого устранения неполадок и мониторинга состояния приложения.

Авторы
Проверено модерацией
Модерация