문제

I got a lot of code like this :

try:
   # do a lot of stuff
except StuffError as e:
    log.exception(e):
    send_mail_to_admin()
    raise e

For DRY, I wanted to refactor that into :

def post_mortem(log, e):
    log.exception(e):
    send_mail_to_admin() 
    # some other stuff
    raise e

Then do :

try:
   # do a lot of stuff
except StuffError as e:
    post_mortem(log, e)

The trouble is now I don't get the proper stack trace, since the exception is raised from another file.

How can I get the same stack trace I would have had with the first code ?

도움이 되었습니까?

해결책 2

..you wanted it DRY? Try this then :)

class ReportExceptions(object):
    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            print("Sending mail to admin about {0!r}".format(exc_value))

And use it like this:

with ReportExceptions():
    # your code here..

With a context manager, you don't have to care about re-raising exceptions, saving tracebacks or other things: the __exit__() method will be executed no matter what, passing you the necessary information. If you do nothing about it (eg. return True) the exception will just continue its way out..

Side note: you can instantiate the context manager only once, for example:

class ReportExceptions(object):
    def __init__(self, email):
        self.email = email

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            print("Sending mail to {0!r} about {1!r}"
                  "".format(self.email, exc_value))

report_exceptions = ReportExceptions('foo@bar.com')

then:

with report_exceptions:
    # ..your code here..

FYI: raising an exception with custom traceback

In case you really need to re-raise an exception, you can save the traceback and keep it for later..

try:
    100 / 0
except ZeroDivisionError, e:
    exc_info = sys.exc_info()

...later on...

raise exc_info[0], exc_info[1], exc_info[2]

(The syntax is actually raise expression, expression, expression, I couln't figure out a nicer way to use the tuple directly...)

Inspecting frames along the path

You can access tb attributes to inspect the execution frames along the path, for example the locals from the "most external" point would be tb.tb_frame.f_locals, the inner frame is at tb.tb_next.tb_frame, etc...

(you can also use the inspect module, see https://stackoverflow.com/a/10115462/148845)

다른 팁

Pass the exc_info() information also, as one of the parameters like this

post_mortem(log, e, sys.exc_info()[2])

And in the post_mortem

def post_mortem(log, e, traceBack):
    traceback.print_tb(traceBack)

To get the entire stacktrace, you can do like shown in this example

import traceback, sys

def post_mortem(log, e, tb):
    print "".join(traceback.format_list(traceback.extract_stack()[:-2]) + [traceback.format_tb(tb)[0]])

def throwError():
    try:
        raise NameError("I don't like your name")
    except NameError as e:
        post_mortem("", e, sys.exc_info()[2])

def callThrowError():
    throwError()

callThrowError()

Output

  File "/home/thefourtheye/Desktop/Test.py", line 15, in <module>
    callThrowError()
  File "/home/thefourtheye/Desktop/Test.py", line 13, in callThrowError
    throwError()
  File "/home/thefourtheye/Desktop/Test.py", line 8, in throwError
    raise NameError("I don't like your name")
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top