Como imprimir o rastreamento completo sem interromper o programa?

779

Estou escrevendo um programa que analisa 10 sites, localiza arquivos de dados, salva os arquivos e os analisa para criar dados que podem ser facilmente usados ​​na biblioteca NumPy. Existem muitos erros que esse arquivo encontra por meio de links incorretos, XML mal formado, entradas ausentes e outras coisas que ainda não categorizei. Inicialmente, fiz este programa para lidar com erros como este:

try:
    do_stuff()
except:
    pass

Mas agora eu quero registrar erros:

try:
    do_stuff()
except Exception, err:
    print Exception, err

Observe que isso está sendo impresso em um arquivo de log para posterior revisão. Isso geralmente imprime dados muito inúteis. O que eu quero é imprimir exatamente as mesmas linhas impressas quando o erro for disparado sem a tentativa, exceto interceptar a exceção, mas não quero que ele interrompa meu programa, pois está aninhado em uma série de loops que gostaria de veja a conclusão.

chriscauley
fonte

Respostas:

583

Alguma outra resposta já apontou o módulo traceback .

Observe que print_exc, em alguns casos de canto, você não obterá o que esperaria. No Python 2.x:

import traceback

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_exc()

... exibirá o rastreamento da última exceção:

Traceback (most recent call last):
  File "e.py", line 7, in <module>
    raise TypeError("Again !?!")
TypeError: Again !?!

Se você realmente precisar acessar o traceback original, uma solução é armazenar em cache as informações da exceção retornadas exc_infoem uma variável local e exibi-la usando print_exception:

import traceback
import sys

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        exc_info = sys.exc_info()

        # do you usefull stuff here
        # (potentially raising an exception)
        try:
            raise TypeError("Again !?!")
        except:
            pass
        # end of useful stuff


    finally:
        # Display the *original* exception
        traceback.print_exception(*exc_info)
        del exc_info

Produção:

Traceback (most recent call last):
  File "t.py", line 6, in <module>
    raise TypeError("Oups!")
TypeError: Oups!

Algumas armadilhas com isso, porém:

  • Do documento de sys_info:

    Atribuir o valor de retorno do traceback a uma variável local em uma função que está manipulando uma exceção causará uma referência circular . Isso impedirá que qualquer coisa referenciada por uma variável local na mesma função ou pelo retorno seja coletada como lixo. [...] Se você precisar do traceback, exclua-o após o uso (melhor com uma tentativa ... finalmente)

  • mas, do mesmo documento:

    A partir do Python 2.2, esses ciclos são recuperados automaticamente quando a coleta de lixo é ativada e se tornam inacessíveis, mas permanece mais eficiente para evitar a criação de ciclos.


Por outro lado, ao permitir o acesso ao retorno associado a uma exceção, o Python 3 produz um resultado menos surpreendente:

import traceback

try:
    raise TypeError("Oups!")
except Exception as err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_tb(err.__traceback__)

... Exibirá:

  File "e3.py", line 4, in <module>
    raise TypeError("Oups!")
Sylvain Leroux
fonte
710

traceback.format_exc()ou sys.exc_info()trará mais informações, se é isso que você deseja.

import traceback
import sys

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    # or
    print(sys.exc_info()[2])
volting
fonte
1
print(sys.exc_info()[0]impressões <class 'Exception'>.
Weberc2 28/08/19
2
não faça uso exc ... o rastreamento contém todas as informações stackoverflow.com/questions/4564559/...
qrtLs
258

Se você estiver depurando e quiser apenas ver o rastreamento da pilha atual, basta chamar:

traceback.print_stack()

Não há necessidade de criar manualmente uma exceção apenas para capturá-la novamente.

dimo414
fonte
9
O módulo traceback faz exatamente isso - gera e captura uma exceção.
pppery
3
A saída vai para STDERR por padrão BTW. Não estava aparecendo nos meus logs porque estava sendo redirecionado para outro lugar.
MPEN
101

Como imprimir o rastreamento completo sem interromper o programa?

Quando você não deseja interromper seu programa com um erro, você precisa lidar com esse erro com uma tentativa / exceto:

try:
    do_something_that_might_error()
except Exception as error:
    handle_the_error(error)

Para extrair o rastreamento completo, usaremos o tracebackmódulo da biblioteca padrão:

import traceback

E para criar um stacktrace decentemente complicado para demonstrar que temos o stacktrace completo:

def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

Impressão

Para imprimir o rastreamento completo, use o traceback.print_excmétodo:

try:
    do_something_that_might_error()
except Exception as error:
    traceback.print_exc()

Que imprime:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Melhor do que imprimir, registrar:

No entanto, uma prática recomendada é ter um criador de logs configurado para o seu módulo. Ele saberá o nome do módulo e poderá alterar os níveis (entre outros atributos, como manipuladores)

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

Nesse caso, você desejará a logger.exceptionfunção:

try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

Quais logs:

ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Ou talvez você queira apenas a string; nesse caso, você desejará a traceback.format_excfunção:

try:
    do_something_that_might_error()
except Exception as error:
    logger.debug(traceback.format_exc())

Quais logs:

DEBUG:__main__:Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Conclusão

E para todas as três opções, vemos que obtemos a mesma saída de quando temos um erro:

>>> do_something_that_might_error()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!
Aaron Hall
fonte
2
como dito acima e para mim também, traceback.print_exc()retornos somente a última chamada: como você ter sucesso para retornar vários nível da pilha (e possivelmente todos levele s?)
Herve-Guerin
@geekobi Não sei ao certo o que você está perguntando aqui. Demonstro que obtemos o retorno até o ponto de entrada do programa / intérprete. Em que você não está claro?
Aaron Hall
1
O que @geekobi está dizendo é que se você capturar e aumentar novamente, traceback.print_exc () retornará apenas a pilha de aumento, não a pilha original.
Fizloki 22/08/19
@fizloki como você está "reraising"? Você está fazendo um raiseencadeamento simples ou de exceção, ou está escondendo o rastreio original? veja stackoverflow.com/questions/2052390/…
Aaron Hall
21

Primeiro, não use prints para registro, há astable, comprovada e bem pensado módulo stdlib de fazer isso: logging. Você definitivamente deveria usá-lo.

Segundo, não fique tentado a mexer com ferramentas não relacionadas quando houver uma abordagem simples e nativa. Aqui está:

log = logging.getLogger(__name__)

try:
    call_code_that_fails()
except MyError:
    log.exception('Any extra info you want to see in your logs')

É isso aí. Você terminou agora.

Explicação para quem estiver interessado em como as coisas funcionam sob o capô

log.exceptionNa verdade, o que está fazendo é apenas uma chamada para log.error(ou seja, registrar evento com nível ERROR) e imprimir o rastreamento depois.

Por que é melhor?

Bem, aqui estão algumas considerações:

  • está certo ;
  • é direto;
  • é simples

Por que ninguém deve usar tracebackou chamar o registrador exc_info=Trueou sujar as mãos sys.exc_info?

Bem, só porque! Todos eles existem para diferentes propósitos. Por exemplo, traceback.print_exca saída de é um pouco diferente dos rastreamentos produzidos pelo próprio intérprete. Se você usá-lo, confundirá qualquer um que ler seus registros, eles estarão batendo com a cabeça contra eles.

Passar exc_info=Truepara registrar chamadas é apenas inapropriado. Porém , é útil ao capturar erros recuperáveis ​​e você deseja registrá-los (usando, por exemplo, INFOnível) com rastreamentos também, porque log.exceptionproduz logs de apenas um nível - ERROR.

E você definitivamente deve evitar mexer com sys.exc_infoo máximo que puder. Não é apenas uma interface pública, é uma interface interna - você pode usá-la se souber definitivamente o que está fazendo. Não se destina apenas a imprimir exceções.

tosh
fonte
4
Também não funciona como está. Não é isso. Eu não terminei agora: esta resposta apenas perde tempo.
A. Rager
Eu também acrescentaria que você pode fazer logging.exception(). Não há necessidade de criar instância do log, a menos que você tenha requisitos especiais.
Shital Shah 18/05/19
9

Além da resposta do @Aaron Hall, se você estiver registrando, mas não quiser usá- logging.exception()lo (já que registra no nível ERROR), você pode usar um nível mais baixo e passar exc_info=True. por exemplo

try:
    do_something_that_might_error()
except Exception:
    logger.info('General exception noted.', exc_info=True)
Mark McDonald
fonte
7

Para obter o rastreio preciso da pilha, como uma string, que teria sido aumentada se não houvesse tentativa / exceção, basta colocá-lo no bloco de exceção que captura a exceção incorreta.

desired_trace = traceback.format_exc(sys.exc_info())

Veja como usá-lo (supondo que flaky_funcesteja definido e logchame seu sistema de registro favorito):

import traceback
import sys

try:
    flaky_func()
except KeyboardInterrupt:
    raise
except Exception:
    desired_trace = traceback.format_exc(sys.exc_info())
    log(desired_trace)

É uma boa ideia capturar e aumentar novamente KeyboardInterrupts, para que você ainda possa matar o programa usando Ctrl-C. O log está fora do escopo da pergunta, mas uma boa opção é o log . Documentação para os módulos sys e traceback .

Edward Newell
fonte
4
Isso não funciona no Python 3 e precisa ser alterado para desired_trace = traceback.format_exc(). Passar sys.exc_info()como o argumento nunca foi a coisa correta a se fazer, mas é ignorado silenciosamente no Python 2 - mas não no Python 3 (3.6.4 de qualquer maneira).
martineau
2
KeyboardInterruptnão é derivado (direta ou indiretamente) de Exception. (Ambos são derivados de BaseException.) Isso significa except Exception:que nunca será capturado a KeyboardInterrupte, portanto, except KeyboardInterrupt: raiseé completamente desnecessário.
AJNeufeld 17/08/18
traceback.format_exc(sys.exc_info())não está funcionando para mim com python 3.6.10
Nam G VU
6

Você precisará colocar a tentativa / exceto dentro do loop mais interno onde o erro pode ocorrer, ou seja,

for i in something:
    for j in somethingelse:
        for k in whatever:
            try:
                something_complex(i, j, k)
            except Exception, e:
                print e
        try:
            something_less_complex(i, j)
        except Exception, e:
            print e

... e assim por diante

Em outras palavras, você precisará agrupar as instruções que podem falhar no try / exceto o mais específico possível, no loop mais interno possível.

Ivo van der Wijk
fonte
6

Uma observação sobre os comentários desta resposta : print(traceback.format_exc())faz um trabalho melhor para mim do que traceback.print_exc(). Com o último, às hellovezes é estranhamente "misturado" com o texto de rastreamento, como se ambos quisessem escrever para stdout ou stderr ao mesmo tempo, produzindo resultados estranhos (pelo menos ao construir de dentro de um editor de texto e visualizar a saída no Painel "Criar resultados").

Traceback (última chamada mais recente):
Arquivo "C: \ Usuários \ Usuário \ Desktop \ test.py", linha 7, no
inferno do_stuff ()
Arquivo "C: \ Usuários \ Usuário \ Desktop \ test.py", linha 4 , em do_stuff
1/0
ZeroDivisionError: divisão inteira ou módulo por zero
o
[Concluído em 0.1s]

Então eu uso:

import traceback, sys

def do_stuff():
    1/0

try:
    do_stuff()
except Exception:
    print(traceback.format_exc())
    print('hello')
Basj
fonte
5

Não vejo isso mencionado em nenhuma das outras respostas. Se você estiver passando um objeto Exception por qualquer motivo ...

No Python 3.5+, é possível rastrear um objeto Exception usando traceback.TracebackException.from_exception () . Por exemplo:

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    try:
        stack_lvl_3()
    except Exception as e:
        # raise
        return e


def stack_lvl_1():
    e = stack_lvl_2()
    return e

e = stack_lvl_1()

tb1 = traceback.TracebackException.from_exception(e)
print(''.join(tb1.format()))

No entanto, o código acima resulta em:

Traceback (most recent call last):
  File "exc.py", line 10, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')
Exception: ('a1', 'b2', 'c3')

São apenas dois níveis da pilha, em oposição ao que seria impresso na tela se a exceção tivesse sido levantada stack_lvl_2()e não interceptada (remova o comentário da # raiselinha).

Pelo que entendi, isso ocorre porque uma exceção registra apenas o nível atual da pilha quando é aumentada, stack_lvl_3()neste caso. À medida que passa pela pilha, mais níveis estão sendo adicionados a ela __traceback__. Mas nós o interceptamos stack_lvl_2(), o que significa que tudo o que foi registrado foi nos níveis 3 e 2. Para obter o rastreio completo impresso no stdout, teríamos que capturá-lo no nível mais alto (mais baixo?):

import traceback


def stack_lvl_3():
    raise Exception('a1', 'b2', 'c3')


def stack_lvl_2():
    stack_lvl_3()


def stack_lvl_1():
    stack_lvl_2()


try:
    stack_lvl_1()
except Exception as exc:
    tb = traceback.TracebackException.from_exception(exc)

print('Handled at stack lvl 0')
print(''.join(tb.stack.format()))

O que resulta em:

Handled at stack lvl 0
  File "exc.py", line 17, in <module>
    stack_lvl_1()
  File "exc.py", line 13, in stack_lvl_1
    stack_lvl_2()
  File "exc.py", line 9, in stack_lvl_2
    stack_lvl_3()
  File "exc.py", line 5, in stack_lvl_3
    raise Exception('a1', 'b2', 'c3')

Observe que a impressão da pilha é diferente, faltam a primeira e a última linhas. Porque é diferenteformat() .

Interceptar a exceção o mais longe possível do ponto em que foi gerada facilita o código mais simples e também fornece mais informações.

bgdnlp
fonte
Isso é muito melhor que o (s) método (s) anterior (es), mas ainda é ridiculamente complicado apenas para imprimir um rastreamento de pilha. Java leva menos código FGS.
elhefe 20/02
4

Obtenha o rastreamento completo como uma sequência do objeto de exceção com traceback.format_exception

Se você tiver apenas o objeto de exceção, poderá obter o traceback como uma string a partir de qualquer ponto do código no Python 3 com:

import traceback

''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))

Exemplo completo:

#!/usr/bin/env python3

import traceback

def f():
    g()

def g():
    raise Exception('asdf')

try:
    g()
except Exception as e:
    exc = e

tb_str = ''.join(traceback.format_exception(None, exc_obj, exc_obj.__traceback__))
print(tb_str)

Resultado:

Traceback (most recent call last):
  File "./main.py", line 12, in <module>
    g()
  File "./main.py", line 9, in g
    raise Exception('asdf')
Exception: asdf

Documentação: https://docs.python.org/3.7/library/traceback.html#traceback.format_exception

Consulte também: Extrair informações de rastreamento de um objeto de exceção

Testado em Python 3.7.3.

Ciro Santilli adicionou uma nova foto
fonte
3

Você quer o módulo de rastreamento . Isso permitirá que você imprima dumps de pilha como o Python normalmente faz. Em particular, a função print_last imprimirá a última exceção e um rastreamento de pilha.

nmichaels
fonte
2

Se você já possui um objeto Error e deseja imprimir a coisa toda, precisa fazer esta chamada um pouco estranha:

import traceback
traceback.print_exception(type(err), err, err.__traceback__)

Isso mesmo, print_exceptionleva três argumentos posicionais: O tipo da exceção, o objeto de exceção real e a propriedade traceback interna da própria exceção.

No python 3.5 ou posterior, o type(err)é opcional ... mas é um argumento posicional, portanto, você ainda precisa passar explicitamente o None em seu lugar.

traceback.print_exception(None, err, err.__traceback__)

Não tenho ideia de por que tudo isso não é apenas traceback.print_exception(err). Por que você desejaria imprimir um erro, juntamente com um rastreio diferente daquele que pertence a esse erro, está além de mim.

nupanick
fonte