How does logging.exception gets hold of the exception? - python

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

Related

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.

Python logging exceptions with traceback, but without displaying messages twice

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.

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)

Get reference to the current exception

$ ./runtests.py -v tests/managers/test_customer.py:CustomerManagerTest.test_register_without_subscription --ipdb
...
test_register_without_subscription (tests.managers.test_customer.CustomerManagerTest) ...
- TRACEBACK --------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python2.7/unittest/case.py", line 331, in run
testMethod()
File "*****/tests/managers/test_customer.py", line 198, in test_register_without_subscription
1/0
ZeroDivisionError: integer division or modulo by zero
--------------------------------------------------------------------------------
> *****/tests/managers/test_customer.py(198)test_register_without_subscription()
197 def test_register_without_subscription(self):
--> 198 1/0
199 ...
ipdb> import sys
ipdb> sys.exc_info()
(<type 'exceptions.AttributeError'>, AttributeError("Pdb instance has no attribute 'do_sys'",), <traceback object at 0x47eb908>)
ipdb>
I could not find any command in ipdb help that shows me current exception.
Doing import sys; print sys.exc_info() doesn't work.
Currently I do:
try:
do_something_that_raises_an_exception()
except Exception as exc:
import ipdb; ipdb.set_trace()
then I can work with exc to analyze it.
How to easily get a reference to the currently effective exception?
This has frustrated me too for a while. I eventually found the answer here, along with a good detailed explanation.
The short answer is, use the ! magic prefix (!sys.exc_info()):
In [4]: 1/0
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
...
ipdb> !sys.exc_info()
(<type 'exceptions.AttributeError'>, AttributeError("'exceptions.ZeroDivisionError' object has no attribute '_render_traceback_'",), <traceback object at 0x101c55fc8>)
This basically tells the debugger: "guessing wouldn't be necessary. it is python code I'm typing", thus preventing it from trying to guess what you mean by "sys", a process during which some internal exception is raised, overwriting sys.exc_info(), which used to hold your original exception.

Why doesn't an undefined name in an "except" raise a NameError?

I was surprised today to see that the following works with no exceptions (in Python 2.7.3 at least):
>>> try:
... pass
... except ThingThatDoesNotExist:
... print "bad"
...
>>>
I would have thought that this should raise a NameError in the REPL, similar to how the following would:
>>> x = ThingThatDoesNotExist
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'ThingThatDoesNotExist' is not defined
Anyone have any idea what's going on here?
The same reason this does not raise a exception:
>>> True or ThingThatDoesNotExist
Python looks up names exactly the moment they need to be evaluated. Names that don't need to be evaluated are not looked up and it's the failed lookup that raises the exception.

Categories