Registrando exceções não capturadas no Python


181

Como você faz com que exceções não capturadas sejam exibidas através do loggingmódulo, e não stderr?

Sei que a melhor maneira de fazer isso seria:

try:
    raise Exception, 'Throwing a boring exception'
except Exception, e:
    logging.exception(e)

Mas minha situação é tal que seria muito bom se logging.exception(...)fosse invocada automaticamente sempre que uma exceção não fosse detectada.



Respostas:


143

Como Ned apontou, sys.excepthooké invocado toda vez que uma exceção é levantada e não capturada. A implicação prática disso é que, no seu código, você pode substituir o comportamento padrão de sys.excepthookfazer o que quiser (incluindo o uso logging.exception).

Como exemplo do homem de palha:

>>> import sys
>>> def foo(exctype, value, tb):
...     print 'My Error Information'
...     print 'Type:', exctype
...     print 'Value:', value
...     print 'Traceback:', tb
... 

Substituir sys.excepthook:

>>> sys.excepthook = foo

Cometa erro de sintaxe óbvio (deixe de fora os dois pontos) e recupere informações de erro personalizadas:

>>> def bar(a, b)
My Error Information
Type: <type 'exceptions.SyntaxError'>
Value: invalid syntax (<stdin>, line 1)
Traceback: None

Para mais informações sobre sys.excepthook, leia os documentos .


4
@ Codemonkey Não é uma palavra-chave reservada, é um nome de tipo preexistente. Você pode usar typecomo argumento de função, embora os IDEs se queixem de ocultar o global type(bem como usar var self = thisJavascript). Realmente não importa, a menos que você precise acessar o typeobjeto dentro de sua função; nesse caso, você pode usar type_como argumento.
Ryan P

3
A frase "toda vez" aqui é enganosa: "sys.excepthook é invocado toda vez que uma exceção é gerada e não capturada" ... porque em um programa, pode haver exatamente uma exceção "não capturada". Além disso, sys.excepthookNÃO é chamado quando uma exceção é "gerada". É chamado quando o programa termina devido a uma exceção não capturada, que não pode ocorrer mais de uma vez.
Nawaz

2
@Nawaz: isso pode acontecer mais de uma vez em um REPL
JFS

2
@Nawaz Também pode acontecer várias vezes se o programa estiver usando threads. Eu também parecem laço de eventos GUI (como o Qt) manter a correr, mesmo que a exceção tornou a sys.excepthook
three_pineapples

1
Qualquer pessoa que tente testar o código acima, gere um erro de retorno ao testar sua função. O SyntaxError não está sendo tratado pelo sys.excepthook. Você pode usar print (1/0) e isso invocará a função que você definiu para substituir sys.excepthook
Parth Karia

177

Aqui está um pequeno exemplo completo que também inclui alguns outros truques:

import sys
import logging
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)

def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))

sys.excepthook = handle_exception

if __name__ == "__main__":
    raise RuntimeError("Test unhandled")
  • Ignore KeyboardInterrupt para que um programa python do console possa sair com Ctrl + C.

  • Confie inteiramente no módulo de log do python para formatar a exceção.

  • Use um criador de logs personalizado com um manipulador de exemplo. Essa alteração altera a exceção não tratada para ir para stdout em vez de stderr, mas você pode adicionar todos os tipos de manipuladores nesse mesmo estilo ao objeto de logger.


13
Eu usaria logger.critical()dentro do manipulador excepthook, já que uma exceção não capturada é bastante crítica, eu diria.
Gitaarik

2
Esta é a resposta mais prática IMO.
David Morales

@gnu_lorien obrigado pelo trecho. Em qual arquivo você colocaria isso?
Stelios

@chefarov O principal arquivo onde você inicializar todos os outros logging
gnu_lorien

Oi, Como podemos escrever para arquivo como debug.log esta informação. Eu tento adicionar linha logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')Mas não ajudou.
precisa saber é o seguinte

26

O método sys.excepthookserá chamado se uma exceção for detectada: http://docs.python.org/library/sys.html#sys.excepthook

Quando uma exceção é gerada e não capturada, o intérprete chama sys.excepthook com três argumentos, a classe de exceção, a instância de exceção e um objeto de retorno. Em uma sessão interativa, isso acontece pouco antes do controle ser retornado ao prompt; em um programa Python, isso acontece logo antes do programa sair. O tratamento dessas exceções de nível superior pode ser personalizado atribuindo outra função de três argumentos ao sys.excepthook.


2
Por que ele envia a classe de exceção? Você não pode sempre conseguir isso chamando typea instância?
Neil G

Qual é o tipo dos parâmetros sys.excepthook?
Martin Thoma

23

Por que não:

import sys
import logging
import traceback

def log_except_hook(*exc_info):
    text = "".join(traceback.format_exception(*exc_info))
    logging.error("Unhandled exception: %s", text)

sys.excepthook = log_except_hook

None()

Aqui está a saída com sys.excepthookcomo visto acima:

$ python tb.py
ERROR:root:Unhandled exception: Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

Aqui está a saída com o sys.excepthookcomentado:

$ python tb.py
Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

A única diferença é que o primeiro tem ERROR:root:Unhandled exception:no início da primeira linha.


Outra diferença é que o primeiro grava o rastreio no sistema de log, para que quaisquer manipuladores e formatadores instalados sejam aplicados. O último escreve diretamente para sys.stderr.
Radiaph 23/05/19

8

Para aproveitar a resposta de Jacinda, mas usando um objeto logger:

def catchException(logger, typ, value, traceback):
    logger.critical("My Error Information")
    logger.critical("Type: %s" % typ)
    logger.critical("Value: %s" % value)
    logger.critical("Traceback: %s" % traceback)

# Use a partially applied function
func = lambda typ, value, traceback: catchException(logger, typ, value, traceback)
sys.excepthook = func

2
Seria melhor usar em functools.partial()vez de lambda. Veja: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro

@MariuszJamro why?
davr 11/11

4

Agrupe sua chamada de entrada do aplicativo em um try...exceptbloco para poder capturar e registrar (e talvez aumentar novamente) todas as exceções não capturadas. Por exemplo, em vez de:

if __name__ == '__main__':
    main()

Faça isso:

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise

Não é isso que pergunta. A intenção da pergunta é perguntar o que fazer quando a exceção NÃO é tratada pelo código.
Mayank Jaiswal

1
Bem, o Python é uma linguagem de programação, e isso implica que ele não faz as coisas "automaticamente" (como o OP deseja), exceto se e quando você solicitar. Em outras palavras, não há como "automaticamente" registrar todas as exceções, a menos que você as codifique - e é isso que está na minha resposta.
Flaviovs 30/03

1
Bem, se você olhar para a resposta de Ned Batchelder, há algo chamado gancho de exceção. Você precisa definir em um local no seu código e todas as suas exceções não detectadas são tratadas.
Mayank Jaiswal #

1
O gancho de exceção não altera o fato de que não é "automático" (no sentido que o OP deseja) - em outras palavras, você ainda precisa codificá-lo. A resposta de Ned (que usa o gancho de exceção) realmente aborda a questão original - é apenas que, na minha opinião , a maneira como faz isso é muito menos pitônica que a minha.
Flaviovs 31/03

1
Depende dos seus próprios objetivos. Se você programa para agradar o IDE, sim, a captura de todas as exceções pode não ser uma opção. Mas se você deseja lidar com os erros normalmente e exibir um bom feedback para o usuário, receio que você precise capturar todas as exceções. Ok, chega de sarcasmo :-) - se você olhar com cuidado, verá que o código intercepta a exceção, mas aumenta novamente, a menos que seu IDE esteja fazendo algo "mágico" que não deveria estar fazendo, ele ainda será exibido. a exceção.
flaviovs

3

Talvez você possa fazer algo na parte superior de um módulo que redireciona o stderr para um arquivo e depois registrá-lo na parte inferior

sock = open('error.log', 'w')               
sys.stderr = sock

doSomething() #makes errors and they will log to error.log

logging.exception(open('error.log', 'r').read() )

3

Embora a resposta de @ gnu_lorien tenha me dado um bom ponto de partida, meu programa falha na primeira exceção.

Eu vim com uma solução aprimorada personalizada (e / ou), que silenciosamente registra Exceções de funções decoradas @handle_error.

import logging

__author__ = 'ahmed'
logging.basicConfig(filename='error.log', level=logging.DEBUG)


def handle_exception(exc_type, exc_value, exc_traceback):
    import sys
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    logging.critical(exc_value.message, exc_info=(exc_type, exc_value, exc_traceback))


def handle_error(func):
    import sys

    def __inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception, e:
            exc_type, exc_value, exc_tb = sys.exc_info()
            handle_exception(exc_type, exc_value, exc_tb)
        finally:
            print(e.message)
    return __inner


@handle_error
def main():
    raise RuntimeError("RuntimeError")


if __name__ == "__main__":
    for _ in xrange(1, 20):
        main()

2

Para responder à pergunta do Sr. Zeus discutida na seção de comentários da resposta aceita, eu uso isso para registrar exceções não capturadas em um console interativo (testado com o PyCharm 2018-2019). Eu descobri sys.excepthookque não funciona em um shell python, então eu olhei mais fundo e descobri que poderia usá-lo sys.exc_info. No entanto, sys.exc_infonão há argumentos diferentes dos sys.excepthooktrês argumentos.

Aqui, eu uso os dois sys.excepthooke sys.exc_infopara registrar as exceções em um console interativo e um script com uma função de wrapper. Para anexar uma função de gancho a ambas as funções, tenho duas interfaces diferentes, dependendo se argumentos são fornecidos ou não.

Aqui está o código:

def log_exception(exctype, value, traceback):
    logger.error("Uncaught exception occurred!",
                 exc_info=(exctype, value, traceback))


def attach_hook(hook_func, run_func):
    def inner(*args, **kwargs):
        if not (args or kwargs):
            # This condition is for sys.exc_info
            local_args = run_func()
            hook_func(*local_args)
        else:
            # This condition is for sys.excepthook
            hook_func(*args, **kwargs)
        return run_func(*args, **kwargs)
    return inner


sys.exc_info = attach_hook(log_exception, sys.exc_info)
sys.excepthook = attach_hook(log_exception, sys.excepthook)

A configuração do registro pode ser encontrada na resposta de gnu_lorien.


2

No meu caso (usando python 3) ao usar a resposta de @Jacinda, o conteúdo do traceback não foi impresso. Em vez disso, ele simplesmente imprime o objeto em si: <traceback object at 0x7f90299b7b90>.

Em vez disso, eu faço:

import sys
import logging
import traceback

def custom_excepthook(exc_type, exc_value, exc_traceback):
    # Do not print exception when user cancels the program
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logging.error("An uncaught exception occurred:")
    logging.error("Type: %s", exc_type)
    logging.error("Value: %s", exc_value)

    if exc_traceback:
        format_exception = traceback.format_tb(exc_traceback)
        for line in format_exception:
            logging.error(repr(line))

sys.excepthook = custom_excepthook
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.