Como faço para registrar um erro do Python com informações de depuração?

468

Estou imprimindo mensagens de exceção do Python em um arquivo de log com logging.error:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero

É possível imprimir informações mais detalhadas sobre a exceção e o código que a gerou do que apenas a cadeia de exceção? Coisas como números de linhas ou traços de pilha seriam ótimos.

provavelmente na praia
fonte

Respostas:

733

logger.exception produzirá um rastreamento de pilha ao lado da mensagem de erro.

Por exemplo:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.exception("message")

Resultado:

ERROR:root:message
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

@Paulo Verifique as notas: "esteja ciente de que no Python 3 você deve chamar o logging.exceptionmétodo dentro da exceptpeça. Se você chamar esse método em um local arbitrário, poderá receber uma exceção bizarra. Os documentos alertam sobre isso."

SiggyF
fonte
131
O exceptionmétodo simplesmente chama error(message, exc_info=1). Assim que você passar exc_infopara qualquer um dos métodos de log de um contexto de exceção, receberá um retorno.
Helmut Grohne
16
Você também pode definir sys.excepthook(veja aqui ) para evitar a necessidade de agrupar todo o seu código em try / except.
julho
23
Você poderia escrever apenas except Exception:porque você não está usando ede qualquer maneira;)
Marco Ferrari
21
Você pode muito bem querer inspecionar eao tentar depurar interativamente seu código. :) É por isso que eu sempre o incluo.
Vicki Laidler 16/02
4
Corrija-me se estiver errado, neste caso, não há tratamento real da exceção e, portanto, faz sentido adicionar raiseno final do exceptescopo. Caso contrário, a execução continuará como se estivesse tudo bem.
quer
184

Uma coisa agradável sobre logging.exceptionque resposta de SiggyF não mostra é que você pode passar uma mensagem arbitrária, e registro ainda vai mostrar o rastreamento completo com todos os detalhes de exceção:

import logging
try:
    1/0
except ZeroDivisionError:
    logging.exception("Deliberate divide by zero traceback")

Com o comportamento de registro padrão (nas versões recentes) de apenas erros de impressão sys.stderr, fica assim:

>>> import logging
>>> try:
...     1/0
... except ZeroDivisionError:
...     logging.exception("Deliberate divide by zero traceback")
... 
ERROR:root:Deliberate divide by zero traceback
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
ncoghlan
fonte
Uma exceção pode ser registrada sem fornecer uma mensagem?
Stevoisiak
@StevenVascellaro - eu sugiro que você passe ''se você realmente não quiser digitar uma mensagem ... a função não pode ser chamada sem pelo menos um argumento, no entanto, então você terá que fornecer algo.
ArtOfWarfare
147

O uso de exc_infoopções pode ser melhor para permitir que você escolha o nível de erro (se você usar exception, ele sempre estará no errornível):

try:
    # do something here
except Exception as e:
    logging.critical(e, exc_info=True)  # log exception info at CRITICAL log level
flycee
fonte
@CivFan: Na verdade, não olhei para as outras edições ou a introdução da postagem; essa introdução também foi adicionada por um editor de terceiros. Não vejo em nenhum lugar nos comentários excluídos que essa tenha sido a intenção, mas também posso desfazer minha edição e removê-los, pois a votação aqui demorou demais para ser para outra coisa senão a versão editada .
Martijn Pieters
Existe logging.fatalum método na biblioteca de log? Eu só vejo critical.
Ian
1
@Ian É um apelido para critical, assim como warné warning.
0xc0de 12/03/19
35

Citação

E se o seu aplicativo fizer logon de outra maneira - sem usar o loggingmódulo?

Agora, tracebackpode ser usado aqui.

import traceback

def log_traceback(ex, ex_traceback=None):
    if ex_traceback is None:
        ex_traceback = ex.__traceback__
    tb_lines = [ line.rstrip('\n') for line in
                 traceback.format_exception(ex.__class__, ex, ex_traceback)]
    exception_logger.log(tb_lines)
  • Use-o no Python 2 :

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
  • Use-o no Python 3 :

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)
zangw
fonte
Por que você colocou "_, _, ex_traceback = sys.exc_info ()" fora da função log_traceback e a passou como argumento? Por que não usá-lo diretamente dentro da função?
Basil Musa
@BasilMusa, para responder sua pergunta, em suma, compatível com Python 3, porque o ex_tracebacké de ex.__traceback__sob Python 3, mas ex_tracebacké de sys.exc_info()sob Python 2.
zangw
12

Se você usar os logs simples - todos os seus registros de log deve corresponder esta regra: one record = one line. Seguindo essa regra, você pode usar grepe outras ferramentas para processar seus arquivos de log.

Mas as informações de rastreamento são multilinhas. Portanto, minha resposta é uma versão estendida da solução proposta por zangw acima neste tópico. O problema é que as linhas de traceback podem ter \ndentro, então precisamos fazer um trabalho extra para nos livrar dos finais dessa linha:

import logging


logger = logging.getLogger('your_logger_here')

def log_app_error(e: BaseException, level=logging.ERROR) -> None:
    e_traceback = traceback.format_exception(e.__class__, e, e.__traceback__)
    traceback_lines = []
    for line in [line.rstrip('\n') for line in e_traceback]:
        traceback_lines.extend(line.splitlines())
    logger.log(level, traceback_lines.__str__())

Depois disso (quando estiver analisando seus logs), você poderá copiar / colar as linhas de retorno necessárias do seu arquivo de log e fazer o seguinte:

ex_traceback = ['line 1', 'line 2', ...]
for line in ex_traceback:
    print(line)

Lucro!

doomatel
fonte
9

Essa resposta se baseia nas excelentes acima.

Na maioria dos aplicativos, você não estará chamando logging.exception (e) diretamente. Provavelmente você definiu um criador de logs personalizado específico para seu aplicativo ou módulo como este:

# Set the name of the app or module
my_logger = logging.getLogger('NEM Sequencer')
# Set the log level
my_logger.setLevel(logging.INFO)

# Let's say we want to be fancy and log to a graylog2 log server
graylog_handler = graypy.GELFHandler('some_server_ip', 12201)
graylog_handler.setLevel(logging.INFO)
my_logger.addHandler(graylog_handler)

Nesse caso, basta usar o logger para chamar a exceção (e) assim:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
Vai
fonte
Este é um toque final útil se você quiser um criador de logs dedicado apenas para exceções.
logicOnAbstractions
7

Você pode registrar o rastreamento de pilha sem uma exceção.

https://docs.python.org/3/library/logging.html#logging.Logger.debug

O segundo argumento opcional de palavra-chave é stack_info, cujo padrão é False. Se verdadeiro, as informações da pilha são adicionadas à mensagem de log, incluindo a chamada de log real. Observe que essas não são as mesmas informações de pilha exibidas por meio da especificação de exc_info: a primeira consiste em quadros de pilha da parte inferior da pilha até a chamada de registro no encadeamento atual, enquanto a segunda são informações sobre quadros de pilha que foram desenrolados, após uma exceção, enquanto procura por manipuladores de exceção.

Exemplo:

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> logging.getLogger().info('This prints the stack', stack_info=True)
INFO:root:This prints the stack
Stack (most recent call last):
  File "<stdin>", line 1, in <module>
>>>
Baczek
fonte
5

Um pouco de tratamento decorador (muito vagamente inspirado na mônada e no levantamento do Talvez). Você pode remover com segurança as anotações do tipo Python 3.6 e usar um estilo de formatação de mensagens mais antigo.

fallible.py

from functools import wraps
from typing import Callable, TypeVar, Optional
import logging


A = TypeVar('A')


def fallible(*exceptions, logger=None) \
        -> Callable[[Callable[..., A]], Callable[..., Optional[A]]]:
    """
    :param exceptions: a list of exceptions to catch
    :param logger: pass a custom logger; None means the default logger, 
                   False disables logging altogether.
    """
    def fwrap(f: Callable[..., A]) -> Callable[..., Optional[A]]:

        @wraps(f)
        def wrapped(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                message = f'called {f} with *args={args} and **kwargs={kwargs}'
                if logger:
                    logger.exception(message)
                if logger is None:
                    logging.exception(message)
                return None

        return wrapped

    return fwrap

Demo:

In [1] from fallible import fallible

In [2]: @fallible(ArithmeticError)
    ...: def div(a, b):
    ...:     return a / b
    ...: 
    ...: 

In [3]: div(1, 2)
Out[3]: 0.5

In [4]: res = div(1, 0)
ERROR:root:called <function div at 0x10d3c6ae8> with *args=(1, 0) and **kwargs={}
Traceback (most recent call last):
  File "/Users/user/fallible.py", line 17, in wrapped
    return f(*args, **kwargs)
  File "<ipython-input-17-e056bd886b5c>", line 3, in div
    return a / b

In [5]: repr(res)
'None'

Você também pode modificar esta solução para retornar algo um pouco mais significativo do que Nonea exceptparte (ou até tornar a solução genérica, especificando esse valor de retorno nos fallibleargumentos de).

Eli Korvigo
fonte
0

No seu módulo de registro (se módulo personalizado), apenas ative o stack_info.

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)
Piyush Kumar
fonte
-1

Se você pode lidar com a dependência extra e usar o twisted.log, não precisará registrar explicitamente os erros e também devolverá todo o tempo de rastreamento e hora ao arquivo ou fluxo.

Jakob Bowyer
fonte
8
Talvez twistedseja uma boa recomendação, mas essa resposta realmente não contribui muito. Ele não diz como usar twisted.log, nem quais vantagens ele tem sobre o loggingmódulo da biblioteca padrão, nem explica o que significa "você não precisa registrar erros explicitamente" .
Mark Amery
-8

Uma maneira limpa de fazer isso é usando format_exc() e analisar a saída para obter a parte relevante:

from traceback import format_exc

try:
    1/0
except Exception:
    print 'the relevant part is: '+format_exc().split('\n')[-2]

Saudações

caraconan
fonte
4
Hã? Por que essa é "a parte relevante" ? Tudo o que .split('\n')[-2]faz é jogar fora o número da linha e o retorno do resultado de format_exc()- informações úteis que você normalmente deseja! Além do mais, ele nem faz um bom trabalho nisso ; se sua mensagem de exceção contiver uma nova linha, essa abordagem imprimirá apenas a linha final da mensagem de exceção - o que significa que você perde a classe de exceção e a maior parte da mensagem de exceção, além de perder o traceback. -1.
Mark Amery