Виняток з журналу із простеженням


Відповіді:


203

Використовуйте logging.exceptionзсередини except:обробника / блоку для реєстрації поточного винятку разом із інформацією про сліди, попередньо повідомленою повідомленням.

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)

logging.debug('This message should go to the log file')

try:
    run_my_stuff()
except:
    logging.exception('Got exception on main handler')
    raise

Зараз дивимось на файл журналу /tmp/logging_example.out:

DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
  File "/tmp/teste.py", line 9, in <module>
    run_my_stuff()
NameError: name 'run_my_stuff' is not defined

1
Переглянув код джанго для цього, і я припускаю, що відповідь "ні", але чи є спосіб обмежити прослідкування певною кількістю символів чи глибини? Проблема полягає в тому, що для великих відслідковування потрібно досить багато часу.
Едуард Лука

10
Зауважте, що якщо ви визначаєте реєстратор, logger = logging.getLogger('yourlogger')вам потрібно написати, logger.exception('...')щоб це працювало ...
576i

Чи можемо ми змінити це так, щоб повідомлення було надруковано з INFO рівня журналу?
NM

Зауважте, що для деяких зовнішніх додатків, таких як Azure insight, відстеження не зберігається в журналах. Після цього необхідно передати їх явно в рядок повідомлень, як показано нижче.
Едгар Н

139

exc_infoПараметри використання можуть бути кращими, залишається попередження або заголовок помилки:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)

Я ніколи не можу згадати, як exc_info=називається кварґ; Дякую!
Берто

4
Це ідентично logging.exception, за винятком того, що тип надмірно реєструється двічі. Просто використовуйте logging.exception, якщо ви не хочете іншого рівня, ніж помилки.
Вірмвуд

@Wyrmwood це не тотожно, як вам потрібно надіслати повідомленняlogging.exception
Peter Wood

57

Моя робота нещодавно поставила перед мене завдання записувати всі відстеження / винятки з нашої програми. Я спробував численні методи, які інші розмістили в Інтернеті, наприклад, описаний вище, але застосував інший підхід. Перекриття traceback.print_exception.

У мене є запис на веб- сайті http://www.bbarrows.com/ Це було б набагато простіше читати, але я вставлю його також і тут.

Коли я мав завдання записувати всі винятки, з якими наше програмне забезпечення може зустрічатися в дикій природі, я спробував декілька різних методів, щоб зафіксувати наші відслідки виключень python. Спочатку я думав, що гачок виключення системи python, sys.excepthook, буде ідеальним місцем для вставки коду реєстрації. Я намагався щось подібне:

import traceback
import StringIO
import logging
import os, sys

def my_excepthook(excType, excValue, traceback, logger=logger):
    logger.error("Logging an uncaught exception",
                 exc_info=(excType, excValue, traceback))

sys.excepthook = my_excepthook  

Це працювало для основної теми, але незабаром я виявив, що мій sys.excepthook не буде існувати в будь-яких нових потоках, які почав мій процес. Це величезна проблема, оскільки більшість всього відбувається в нитках цього проекту.

Після гуглінгу та ознайомлення з великою кількістю документації найкорисніша інформація, яку я знайшла, була від трекера Python Issue.

Перший пост у потоці показує робочий приклад sys.excepthookНЕ, що зберігається в потоках (як показано нижче). Мабуть, це очікувана поведінка.

import sys, threading

def log_exception(*args):
    print 'got exception %s' % (args,)
sys.excepthook = log_exception

def foo():
    a = 1 / 0

threading.Thread(target=foo).start()

Повідомлення цього потоку Python Issue дійсно призводять до 2 запропонованих хаків. Будь ласка, підклас Threadі перетворіть метод запуску в нашу власну спробу, крім блоку, для того, щоб виловлювати та записувати винятки або патч мавп threading.Thread.runдля запуску у вашій власній спробі, окрім блокування та реєстрації виключень.

Перший метод підкласифікації Threadздається мені менш елегантним у вашому коді, тому що вам доведеться імпортувати та використовувати свій користувальницький Threadклас ВСЯКОГО, де ви хотіли мати тему реєстрації. Це в кінцевому підсумку стало клопотом, оскільки мені довелося шукати всю нашу кодову базу і замінити все нормальне Threadsна цей звичай Thread. Однак було зрозуміло, що це Threadробить, і комусь буде легше діагностувати та налагоджувати, якщо щось пішло не так зі спеціальним кодом журналу. Захована нитка реєстрації може виглядати приблизно так:

class TracebackLoggingThread(threading.Thread):
    def run(self):
        try:
            super(TracebackLoggingThread, self).run()
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception, e:
            logger = logging.getLogger('')
            logger.exception("Logging an uncaught exception")

Другий метод виправлення мавп threading.Thread.runприємний тим, що я міг просто запустити його один раз після __main__та зафіксувати код реєстрації за всіма винятками. Виправлення мавп може прикро відладжувати, хоча це змінює очікувану функціональність чогось. Запропонований виправлення з трекера Python Issue:

def installThreadExcepthook():
    """
    Workaround for sys.excepthook thread bug
    From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html

(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
    Call once from __main__ before creating any threads.
    If using psyco, call psyco.cannotcompile(threading.Thread.run)
    since this replaces a new-style class method.
    """
    init_old = threading.Thread.__init__
    def init(self, *args, **kwargs):
        init_old(self, *args, **kwargs)
        run_old = self.run
        def run_with_except_hook(*args, **kw):
            try:
                run_old(*args, **kw)
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                sys.excepthook(*sys.exc_info())
        self.run = run_with_except_hook
    threading.Thread.__init__ = init

Тільки до того, як я почав тестувати свій журнал винятків, я зрозумів, що я ставлюся до цього все неправильно.

Для тестування я поставив

raise Exception("Test")

десь у моєму коді. Однак, метод обгортання aa, який назвав цей метод, був спробою, за винятком блоку, який роздрукував прослідкування та проковтнув виняток. Це було дуже неприємно, тому що я побачив, що зворотний трек видається надрукованим до STDOUT, але не реєструється. Тоді я вирішив, що набагато простішим методом реєстрації зворотних зворотів є лише виправлення мавпи методом, який використовує весь код python для друку самих зворотних зворотів, traceback.print_exception. Я закінчив щось подібне до наступного:

def add_custom_print_exception():
    old_print_exception = traceback.print_exception
    def custom_print_exception(etype, value, tb, limit=None, file=None):
        tb_output = StringIO.StringIO()
        traceback.print_tb(tb, limit, tb_output)
        logger = logging.getLogger('customLogger')
        logger.error(tb_output.getvalue())
        tb_output.close()
        old_print_exception(etype, value, tb, limit=None, file=None)
    traceback.print_exception = custom_print_exception

Цей код записує прослідкування у String Buffer і записує його до журналу ERROR. У мене користувальницький обробник журналу налаштував реєстратор 'customLogger', який бере журнали рівня ERROR та надсилає їх додому на аналіз.


2
Досить цікавий підхід. Одне запитання - add_custom_print_exceptionна веб-сайті, на яке ви пов’язані, виявляється, і натомість там є зовсім інший кінцевий код. Який би ви сказали, що кращий / кінцевий і чому? Дякую!
фантастичний

Дякую, чудова відповідь!
101

Існує друк для вирізання та вставки. на делегований виклик до old_print_exception, ліміт і файл повинні бути передані лімітом і файлом, а не None - old_print_exception (etype, value, tb, limit, file)
Marvin

Для вашого останнього блоку коду, а не ініціалізації StringIO та друку винятку з нього, ви можете просто зателефонувати logger.error(traceback.format_tb())(або format_exc (), якщо ви хочете також інформацію про виключення).
Джеймс

8

Ви можете зафіксувати всі невиконані винятки на головному потоці, призначивши обробник sys.excepthook, можливо, використовуючи exc_infoпараметр функції реєстрації Python :

import sys
import logging

logging.basicConfig(filename='/tmp/foobar.log')

def exception_hook(exc_type, exc_value, exc_traceback):
    logging.error(
        "Uncaught exception",
        exc_info=(exc_type, exc_value, exc_traceback)
    )

sys.excepthook = exception_hook

raise Exception('Boom')

Якщо ваша програма використовує потоки, то зауважте, що потоки, створені за допомогою threading.Thread, не спрацьовуватимуть, sys.excepthookколи всередині них виникне вимкнено виняток, як зазначено у випуску 1230540 у трекері випуску Python. Деякі хаки пропонують вирішити це обмеження, як, наприклад, мавпа-виправлення, Thread.__init__щоб перезаписати self.runальтернативним runметодом, який загортає оригінал у tryблок та дзвінки sys.excepthookзсередини exceptблоку. Крім того, ви можете просто вручну загорнути точку входу для кожної своєї теми в try/ exceptсобі.


3

Невиконані повідомлення про виключення переходять до STDERR, тож замість того, щоб реалізовувати свій запис у самій Python, ви можете надіслати STDERR у файл, використовуючи будь-яку оболонку, яку ви використовуєте для запуску сценарію Python. У сценарії Bash ви можете це зробити за допомогою перенаправлення виводу, як описано в посібнику BASH .

Приклади

Додайте помилки до файлу, іншого виводу до терміналу:

./test.py 2>> mylog.log

Перезапис файлу з перемежованим STDOUT та STDERR виведенням:

./test.py &> mylog.log


2

Ви можете отримати трекбек за допомогою реєстратора на будь-якому рівні (DEBUG, INFO, ...). Зауважте, що використовуючи logging.exception, рівень - ПОМИЛКА.

# test_app.py
import sys
import logging

logging.basicConfig(level="DEBUG")

def do_something():
    raise ValueError(":(")

try:
    do_something()
except Exception:
    logging.debug("Something went wrong", exc_info=sys.exc_info())
DEBUG:root:Something went wrong
Traceback (most recent call last):
  File "test_app.py", line 10, in <module>
    do_something()
  File "test_app.py", line 7, in do_something
    raise ValueError(":(")
ValueError: :(

Редагувати:

Це також працює (використовуючи python 3.6)

logging.debug("Something went wrong", exc_info=True)

1

Ось версія, яка використовує sys.excepthook

import traceback
import sys

logger = logging.getLogger()

def handle_excepthook(type, message, stack):
     logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')

sys.excepthook = handle_excepthook

Як щодо використання {traceback.format_exc()}замість {traceback.format_tb(stack)}?
змінна

0

можливо, не так стильно, але простіше:

#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)

-1

Ось простий приклад, взятий із документації на python 2.6 :

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)

logging.debug('This message should go to the log file')

4
Питання полягало в тому, як записати прослідкування
Костянтин Шуберт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.