Como faço para registar um erro em Python com a informação de depuração?
estou a imprimir mensagens de excepção em Python num ficheiro de registo 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 excepção e o código que a gerou do que apenas o texto de excepção? Coisas como números de linha ou traços de pilha seria óptimo.
9 answers
logger.exception
irá enviar um traço 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 Cheque notas, " esteja ciente de que no Python 3 você deve chamar o método logging.exception
logo dentro da parte except
. Se você chamar este método em um lugar arbitrário você pode obter uma exceção bizarra. Os médicos alertaram sobre isso."
Uma coisa boa sobre logging.exception
que SiggyF a resposta não mostra é que você pode passar uma mensagem arbitrária, e o log mostrará ainda o traceback completo com todos os detalhes da excepção:
import logging
try:
1/0
except ZeroDivisionError:
logging.exception("Deliberate divide by zero traceback")
Com o comportamento por omissão (nas versões recentes) de registo de erros de impressão em sys.stderr
, parece-se com isto:
>>> 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
Se usar as opções exc_ info poderá ser melhor, para lhe permitir escolher o nível de erro (se usar exception
, ele irá sempre mostrar error
):
try:
# do something here
except Exception as e:
logging.fatal(e, exc_info=True) # log exception info at FATAL log level
E se a sua aplicação registar de outra forma – não usar o Módulo
logging
?
Agora, traceback
pode 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)
-
Usa-o em Python 2:
try: # your function call is here except Exception as ex: _, _, ex_traceback = sys.exc_info() log_traceback(ex, ex_traceback)
-
Usa-o em Python 3:
try: x = get_number() except Exception as ex: log_traceback(ex)
Se usar registos simples - todos os seus registos devem corresponder a esta regra: one record = one line
. Seguindo esta regra, você pode usar grep
e outras ferramentas para processar seus arquivos de registro.
\n
lá dentro, por isso precisamos de fazer um trabalho extra para nos livrarmos destas terminações de 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__())
[6]Depois disso (quando você vai ser ao analisar os seus registos) poderá copiar / colar as linhas de traceback necessárias do seu ficheiro de registo e fazer isto:
ex_traceback = ['line 1', 'line 2', ...]
for line in ex_traceback:
print(line)
Lucro!
# 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)
Neste caso, basta usar o logger para chamar a excepção(e) assim:
try:
1/0
except ZeroDivisionError, e:
my_logger.exception(e)
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 devolver algo um pouco mais significativo do que None
da parte except
(ou mesmo tornar a solução genérica, especificando este valor de retorno nos argumentos de fallible
).
Se consegue lidar com a dependência extra, então use o twisted.log, você não tem que log explicitamente erros e também retorna todo o traceback e tempo para o arquivo ou transmissão.
Uma forma limpa de o fazer é usando format_exc()
e, em seguida, 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]
Relativamente a