Exceção de log com traceback

152

Como posso registrar meus erros do Python?

try:
    do_something()
except:
    # How can I log my exception here, complete with its traceback?
TIMEX
fonte

Respostas:

203

Use logging.exceptionde dentro do except:manipulador / bloco para registrar a exceção atual junto com as informações de rastreamento, anexadas com uma mensagem.

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

Agora, olhando para o arquivo de log /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
nosklo
fonte
1
Olhei o código do django para isso e estou assumindo que a resposta é não, mas existe uma maneira de limitar o retorno a uma certa quantidade de caracteres ou profundidade? O problema é que, para grandes rastreamentos, leva muito tempo.
Eduard Luca #
10
Observe que, se você definir um criador de logs, logger = logging.getLogger('yourlogger')precisará escrever logger.exception('...')para que isso funcione ...
576i
Podemos modificar isso para que a mensagem seja impressa com INFO no nível do log?
NM
Observe que, para certos aplicativos externos, como o insight do Azure, o trackback não é armazenado nos logs. É então necessário passá-los explicitamente para a sequência de mensagens, como mostrado abaixo.
Edgar H
138

As exc_infoopções de uso podem ser melhores, permanece aviso ou título do erro:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)
flycee
fonte
Nunca me lembro como o exc_info=kwarg é chamado; obrigado!
berto
4
Isso é idêntico ao logging.exception, com a exceção de que o tipo é registrado redundantemente duas vezes. Apenas use logging.exception, a menos que você queira um nível diferente de erro.
Wyrmwood
@Wyrmwood não é idêntico, pois você deve fornecer uma mensagem para logging.exception
Peter Wood
57

Meu trabalho recentemente me encarregou de registrar todas as tracebacks / exceções do nosso aplicativo. Tentei várias técnicas que outros haviam publicado on-line, como a acima, mas decidiram por uma abordagem diferente. Substituindotraceback.print_exception .

Eu tenho um artigo em http://www.bbarrows.com/ Isso seria muito mais fácil de ler, mas eu vou colá-lo aqui também.

Quando encarregado de registrar todas as exceções que nosso software pode encontrar na natureza, tentei várias técnicas diferentes para registrar nossos rastreamentos de exceção em python. No começo, pensei que o gancho de exceção do sistema python, sys.excepthook, seria o lugar perfeito para inserir o código de log. Eu estava tentando algo parecido com:

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  

Isso funcionou para o thread principal, mas logo descobri que o my sys.excepthook não existiria em nenhum novo thread iniciado pelo meu processo. Esse é um problema enorme, porque quase tudo acontece nos threads deste projeto.

Depois de pesquisar no Google e ler muita documentação, as informações mais úteis que encontrei foram do rastreador do Python Issue.

O primeiro post no tópico mostra um exemplo de trabalho que sys.excepthookNÃO persiste nos tópicos (como mostrado abaixo). Aparentemente, esse é um comportamento esperado.

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

As mensagens neste tópico do Python Issue realmente resultam em 2 hacks sugeridos. Subclasse Threade agrupe o método run em nossa própria tentativa, exceto o bloco para capturar e registrar exceções ou patch de macacothreading.Thread.run para executar em sua própria tentativa, exceto bloquear e registrar as exceções.

O primeiro método de subclassificação Threadparece-me menos elegante em seu código, pois você teria que importar e usar sua Threadclasse personalizada EM TODA PARTE QUE QUISER para ter um encadeamento de log. Isso acabou sendo um aborrecimento, porque eu tive que pesquisar em toda a nossa base de códigos e substituir tudo normal Threadspor esse costume Thread. No entanto, ficou claro o que isso Threadestava fazendo e seria mais fácil para alguém diagnosticar e depurar se algo desse errado com o código de log personalizado. Um encadeamento de registro de custódia pode se parecer com o seguinte:

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

O segundo método de aplicação de patches de macacos threading.Thread.runé bom, porque eu poderia executá-lo uma vez logo após__main__ e instrumentar meu código de log em todas as exceções. O patch do macaco pode ser irritante para a depuração, pois altera a funcionalidade esperada de algo. O patch sugerido do rastreador do Python Issue foi:

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

Somente quando comecei a testar meu log de exceção, percebi que estava fazendo tudo errado.

Para testar eu tinha colocado um

raise Exception("Test")

em algum lugar do meu código. No entanto, agrupar um método que chamou esse método foi uma tentativa, exceto o bloco que imprimiu o retorno e engoliu a exceção. Isso foi muito frustrante porque vi o traceback ser impresso no STDOUT, mas não sendo registrado. Decidi então que um método muito mais fácil de registrar os tracebacks era apenas corrigir o método que todo o código python usa para imprimir os tracebacks, traceback.print_exception. Acabei com algo semelhante ao seguinte:

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

Esse código grava o traceback em um buffer de string e o registra no log de erro. Eu tenho um manipulador de log personalizado que configura o logger 'customLogger' que pega os logs no nível de ERRO e os envia para casa para análise.

Brad Barrows
fonte
2
Uma abordagem bastante interessante. Uma pergunta - add_custom_print_exceptionnão parece estar no site ao qual você vinculou e, em vez disso, há um código final bem diferente lá. Qual deles você diria que é melhor / mais final e por quê? Obrigado!
fantabolous
Obrigado, ótima resposta!
101
Existe um erro de recortar e colar. na chamada delegada old_print_exception, limite e arquivo deve ser passado limite e arquivo, e não nenhum - old_print_exception (etype, valor, tb, limite, arquivo)
Marvin
Para o seu último bloco de código, em vez de inicializar um StringIO e imprimir a exceção, basta chamar logger.error(traceback.format_tb())(ou format_exc () se você quiser as informações da exceção também).
James
8

Você pode registrar todas as exceções não capturadas no thread principal atribuindo um manipulador sys.excepthook, talvez usando o exc_infoparâmetro das funções de log do 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')

Se o seu programa usar threads, no entanto, observe que os threads criados usando nãothreading.Thread serão acionados quando ocorrer uma exceção não capturada dentro deles, conforme observado no Problema 1230540 no rastreador de problemas do Python. Alguns hacks foram sugeridos para contornar essa limitação, como a correção de macacos para substituir por um método alternativo que agrupa o original em um bloco e chama de dentro do bloco. Como alternativa, você pode apenas quebrar manualmente o ponto de entrada de cada um dos seus threads em / você mesmo.sys.excepthookThread.__init__self.runruntrysys.excepthookexcepttryexcept

Mark Amery
fonte
3

As mensagens de exceção não capturadas vão para STDERR; portanto, em vez de implementar seu log no próprio Python, você pode enviar o STDERR para um arquivo usando qualquer shell que esteja usando para executar seu script Python. Em um script Bash, você pode fazer isso com o redirecionamento de saída, conforme descrito no guia BASH .

Exemplos

Anexe erros ao arquivo, outra saída ao terminal:

./test.py 2>> mylog.log

Sobrescreva o arquivo com as saídas STDOUT e STDERR intercaladas:

./test.py &> mylog.log
panchicore
fonte
2

O que eu estava procurando:

import sys
import traceback

exc_type, exc_value, exc_traceback = sys.exc_info()
traceback_in_var = traceback.format_tb(exc_traceback)

Vejo:

Martin Thoma
fonte
1

Você pode obter o rastreamento usando um registrador, em qualquer nível (DEBUG, INFO, ...). Observe que logging.exception, usando , o nível é ERRO.

# 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: :(

EDITAR:

Isso funciona também (usando python 3.6)

logging.debug("Something went wrong", exc_info=True)
Constantin De La Roche
fonte
1

Aqui está uma versão que usa 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
sveilleux2
fonte
Que tal usar em {traceback.format_exc()}vez de {traceback.format_tb(stack)}?
variável
0

talvez não seja tão estiloso, mas mais fácil:

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

Aqui está um exemplo simples, retirado da documentação do 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')
rogeriopvl
fonte
4
A questão era como registrar o traceback
Konstantin Schubert -