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.

Jacob Marble
fonte

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 .

Jacinda
fonte
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.

gnu_lorien
fonte
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.

Ned Batchelder
fonte
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.

Tiago Coutinho
fonte
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
Mike
fonte
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
flaviovs
fonte
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() )
Grilo Falante
fonte
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()
guneysus
fonte
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.

Nabs
fonte
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
vigoroso
fonte