0

I am implementing a custom Handler with Python. Naturally, I need to override emit(self, record) to do that. An example:

from logging import Handler, LogRecord

class FooHandler(Handler):
    def emit(self, record: LogRecord):
        # handler logic

As you can see, everytime I log something with a Logger instance, this will provide a LogRecord to emit method.

I see the current implementation of LogRecord from CPython source, you can also see it from here.

Assuming I have Logger instance called logger. Later somewhere in the code, I do these:

# either this
logger.exception(Exception("foo"))
# it does not have to be an instance of Exception, it's for the sake of simplicity

# or this
logger.error("foo", exc_info=True)
# this also provides a stack trace on the console handler

Since @thebjorn has commented about traceback module, I think I can work around from that. However I have three questions right now:

  1. How do I get the exception from LogRecord instance?
  2. If I do logger.error("message", exc_info=True), then I do not pass any exception instance. In this case, how do I get the traceback since I do not simply have any exception instance?

Thanks in advance.

Environment

  • Python 3.5 and above
Eray Erdin
  • 2,633
  • 1
  • 32
  • 66
  • 1
    the `traceback` module..? – thebjorn May 15 '19 at 17:50
  • Actually, `SteamHandler` implementation has given me a bit of insight but I still might need an explanation about [handleError](https://github.com/python/cpython/blob/master/Lib/logging/__init__.py#L987). – Eray Erdin May 15 '19 at 17:51
  • @thebjorn, that was what I was looking for. I didn't know such a thing existed in standard library. – Eray Erdin May 15 '19 at 17:53

3 Answers3

1

I am the OP and this may not be the best answer, in practice or more Pythonic way but, for Googlers, there is a method.

As @thebjorn stated in the comments of the question, you need traceback built-in module.

Then you need to make sure that the exception you are targeting is the latest exception your software has raised. Then you can simply call:

traceback.print_last()

If there is no exception, then you will get below as a string:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.7/traceback.py", line 173, in print_last
    raise ValueError("no last exception")
ValueError: no last exception

In other case, you will get the latest exception's traceback:

raise Exception("foo")
traceback.print_last()

# will return a string similar to below

Traceback (most recent call last):
  File "/foo/bar/baz.py", line 3296, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "/foo/bar/biz.py", line 1, in <module>
    raise Exception("foo")
Exception: foo

Hope this helps to Googlers.

Things to Be Aware Of

As I've mentioned, you will need to make sure that the last exception that has got raised is the exception that you are targeting. This might not be a viable solution

  • for multithreaded codebase because you must be extra careful about which thread your code is running on or
  • a codebase that is structured upon a framework such as Django because the exception handling of such frameworks might be quite complex and you might get a different Exception rather than the one you were excepting to get
Eray Erdin
  • 2,633
  • 1
  • 32
  • 66
  • 1
    If there's not any answer with detail and with different approach in two days, I will accept this answer as valid. – Eray Erdin May 15 '19 at 18:16
1

The LogRecord object has an exc_text attribute that looks to be identical to the text provided in a traceback. It returns None when there isn't an exception.

So I think the following would get the OP what they were originally requesting:

from logging import Handler, LogRecord

class FooHandler(Handler):
    def emit(self, record: LogRecord):
        print(record.exc_text)
        # other handler logic
sazerac
  • 311
  • 2
  • 7
0

Expanding upon sazerac's code, exec_text didn't have any value when I ran it with a sample try except block, but exc_info WAS populated. This is what I used to work around my scenario.

from logging import Handler, LogRecord
import traceback

class FooHandler(Handler):
    def emit(self, record: LogRecord):
        if record.exc_info:
            errType, errValue, errTraceback = record.exc_info
            msg = "".join(traceback.format_exception(errType, errValue, errTraceback))
        else:
            msg = ''
        print(msg)

Ryan McGrath
  • 2,180
  • 1
  • 11
  • 10