3

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 Answers 2

2

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")
Sign up to request clarification or add additional context in comments.

4 Comments

+1 cause it's a bit better, but I don't get the line where the exception occurs, I only get the line where it's caught.
@e-satis I believe, it gives only the line which raises the exception.
Explicitly passing traceback object is unnecessary. How about accessing sys.exc_info() inside post_mortem?
Context managers is the way :) No catching / no need to re-raise / no risk of harming the exception
1

..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('[email protected]')

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)

8 Comments

Ah, this is beautiful. Actually, I'm going to make a decorator out of it.
Whooops, actually I do need to catch the exception, because I need to log the traceback as well.
A mix between the two solution is probably the way to go. I'm going to make some tests, but if somebody has an idea, I'll take it any day.
@e-satis the third argument to __exit__ (tb) is the traceback you're looking for :)
Great ! Can you access the block locals() as well ?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.