Python logging exceptions with traceback, but without displaying messages twice - python

If I run the following code:
import logging
logger = logging.getLogger('creator')
try:
# some stuff
except Exception as exception:
logger.exception(exception)
I get the following output on the screen:
creator : ERROR division by zero
Traceback (most recent call last):
File "/graph_creator.py", line 21, in run
1/0
ZeroDivisionError: division by zero
Are there ways to get such a output?
creator : ERROR ZeroDivisionError: division by zero
Traceback (most recent call last):
File "/graph_creator.py", line 21, in run
1/0
Of course, I can get this (but I don't like it):
creator : ERROR Сaught exception (and etc...)
Traceback (most recent call last):
File "/graph_creator.py", line 21, in run
1/0
ZeroDivisionError: division by zero

If you called exception like this:
logger.exception('%s: %s', exception.__class__.__name__, exception)
then you could get the exception class name in the initial line.
If you need more precise changes, you can use a custom Formatter subclass which formats things exactly as you like. This would need to override format_exception to change the formatting of the traceback.

Related

python display type of raised exception

I define my own exception
class MyException(Exception):
pass
somewhere deep in project folder structure, i.e. project\subfolder_1\subfolder_2\etc...\exceptions.py and then use it
from project.subproject.subfolder_1.subfolder_2.\etc ... .exceptions import MyException as MyException and then raise it as raise MyException('bad stuff happened')
it is then displayed in output as
project.subproject.subfolder_1.subfolder_2.etc... .exceptions.MyException: bad stuff happened
can I somehow get rid of the full namespace? Since it's anyway 'imported as' and in code referred only as MyException, to display just
MyException: bad stuff happened
as with other built in exceptions?
When a Python exception has to be printed, its __str__ method gets called (or those of its parents if has not defined one).
file: so75195149/deeply_nested.py
class MyCustomException(Exception): pass
file: main.py
from so75195149.deeply_nested import MyCustomException
raise MyCustomException("bad stuff happened")
gives
Traceback (most recent call last):
File "main.py", line 3, in <module>
raise MyCustomException("bad stuff happened")
so75195149.deeply_nested.MyCustomException: bad stuff happened
If you define a __str__ method :
file: so75195149/deeply_nested.py
class MyCustomException(Exception):
def __str__(self) -> str:
return "Hello"
you get instead
Traceback (most recent call last):
File "/home/stack_overflow/main.py", line 3, in <module>
raise MyCustomException("bad stuff happened")
so75195149.deeply_nested.MyCustomException: Hello
because you can only choose how the exception is printed, but all the rest comes from the fact that your program crashed (uncaught exception), so Python nicely give you information about that by calling its sys.excepthook :
This function prints out a given traceback and exception to sys.stderr.
When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. [...] in a Python program this happens just before the program exits. The handling of such top-level exceptions can be customized by assigning another three-argument function to sys.excepthook.
Here we go :
file: main.py
import sys
def my_custom_excepthook(exception_class, exception_instance, traceback_object):
print(f"sys.excepthook called\n{exception_class=!r}\n{exception_instance=!s}\n{traceback_object=!s}", file=sys.stderr)
sys.excepthook = my_custom_excepthook
from so75195149.deeply_nested import MyCustomException
raise MyCustomException("bad stuff happened")
produces
sys.excepthook called
exception_class=<class 'so75195149.deeply_nested.MyCustomException'>
exception_instance=Hello
traceback_object=<traceback object at 0x7f79515fe200>
So if you want something that looks like the usual way Python prints, using previous answers :
file: main.py
import sys
import traceback
def my_custom_excepthook(exception_class, exception_instance, traceback_object):
# do not use traceback.format_exception
print("Traceback (most recent call last):", file=sys.stderr)
traceback.print_tb(traceback_object, file=sys.stderr)
print(f"{exception_class.__name__}: {exception_instance!s}", file=sys.stderr)
sys.excepthook = my_custom_excepthook
from so75195149.deeply_nested import MyCustomException
raise MyCustomException("bad stuff happened")
gives
Traceback (most recent call last):
File "/home/stack_overflow/main.py", line 15, in <module>
raise MyCustomException("bad stuff happened")
MyCustomException: Hello
versus the original :
Traceback (most recent call last):
File "/home/stack_overflow/main.py", line 13, in <module>
raise MyCustomException("bad stuff happened")
so75195149.deeply_nested.MyCustomException: Hello
See traceback.format_exception and qualified name (__qualname__) for more information.
But I advise you to be careful in setting a custom excepthook. If another exception crashes the program, you won't get its qualified name, which is very often useful (the reason it is included by default). It is global for the whole Pyhton app.
I recommend instead to wrap your custom exceptions in a non-deeply-neested-defined exception. Or do not use custom-defined exceptions, use the standard ones.

How to suppress irrelevant errors in a try block

Suppose I want to check that a certain entry is in a Series. I would like to try to access that entry, and if that fails, raise a simple, short ValueError.
For example, I have a series that doesn't have entry C - I want a check to halt the script. Example:
s = {'A': 1, 'B': 2}
s = pd.Series(s)
try:
s['C']
except:
raise ValueError('C is missing.')
But this code throws a long KeyError before spitting out the ValueError. It works, but is verbose.
(I know that I can use an assert statement instaead.)
Why doesn't the try block suppress the KeyError - isn't that part of its purpose? Is there a way to get my intended behavior?
You are seeing exception chaining. This extra information can be suppressed with a from None clause in your raise statement. Consider this (totally contrived) case where I am suppressing a ZeroDivisionError and raising a KeyError:
>>> try:
... 1/0
... except ZeroDivisionError:
... raise KeyError
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
KeyError
But if I use from none:
>>> try:
... 1/0
... except ZeroDivisionError:
... raise KeyError from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
KeyError
>>>
Also note, you really should not use a bare except clause. Catch as specific an error as possible.

Push a custom string to current Python stack / traceback

How can I implement a context manager with the following API:
s = "this is my message"
with PushStackFrame(s):
raise RuntimeError("something")
such that when RuntimeError is raised, I get the following message:
Traceback (most recent call last):
File "foo.py", line 4, in <module>
## PushStackFrame: this is my message ## KEY LINE
File "foo.py", line 5, in <module>
raise RuntimeError("something")
RuntimeError: something
Most importantly, I want the string passed to PushStackFrame to be inserted verbatim into the stack trace, I don't want to see just the code.
One way to do this is to catch the exception on the way out of the context manager, figure out where in the traceback the context manager was called, and insert a new traceback frame, before rethrowing the exception with traceback. I'd prefer not to do this.

How does logging.exception gets hold of the exception?

Given the following code, could some python experts tell me how does logging.exception gets reference to the exception to print it out?
>>> try:
... 1/0
... except:
... logging.exception("message")
...
ERROR:root:message
Traceback (most recent call last):
File "<console>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
I am assuming it's going through the traceback to get that exception message, but would be good to hear from the experts.
The logging module got the exception via sys.exc_info(). You could take a look at the source code in logging module

Raise an exception with traceback starting from caller

I'm trying to make an automated test framework for a side-project and could use some help creating the assertion checks.
Running this in python...
assert(False)
Gives you this...
Traceback (most recent call last):
File "test.py", line 1, in <module>
assert(False)
AssertionError
As you can see the traceback lowest level is assert(False). So I made my custom assert that prints when the assert succeeds.
def custom_assert(condition):
if condition:
print("Yay! It werks!")
else:
raise Exception("Nay, it don't werks...")
custom_assert(False)
But instead of what assert gives, custom_assert gives me this.
Traceback (most recent call last):
File "test.py", line 14, in <module>
custom_assert(False)
File "test.py", line 12, in custom_assert
raise Exception("Nay, it don't werks...")
Exception: Nay, it don't werks...
Which is of course the default behavior. Perfectly useful 99.9999% of the time, but this is that one time it could be improved. It's not useful to know that the method I called to raise an error when the condition is false raised the error.
How can I make my custom_assert raise an exception with a traceback starting from the caller, the same way assert does?
P.S.: I don't want to print it, I want the exception to have properly modified traceback so it works properly with debuggers and other tools too!
Edit
To clarify, the traceback I want would be like this.
Traceback (most recent call last):
File "test.py", line 14, in <module>
custom_assert(False)
Exception: Nay, it don't werks...
Essentially what you want to do is something similar to this:
tb = None
try:
raise Exception('foo')
except Exception:
tb = sys.exc_info()[2]
tb.tb_frame = tb.tb_frame.f_back # This line doesn't work
raise Exception('Nay it doesnt werks').with_traceback(tb)
but you can't assign tb_frame, and from mucking around in the CPython code, this is C-generated data structures (not python) (see sys._getframe())
So your only option left is to mock the entire machinery and then convince python to use your stack. This looks like what jinja2 is doing. If that's what you choose to do, good luck! (It's out of my scope at that point)

Categories