In VS.Code with the Python extension, I can break on "Uncaught Exceptions" and "Raised Exceptions".
But what if I don't want to break on all exceptions, only those that are uncaught in a certain function? (but which are caught, say by a framework like FastAPI, in a shallower frame?)
If debugging on the console, when I know there is a function that has a line causing an exception, I can change it from something like this:
def some_function():
line_causing_some_exception()
To this:
def some_function():
try:
line_causing_some_exception()
except:
import pdb
pdb.post_mortem()
# Let the framework handle the exception as usual:
raise
I can even debug graphically, instead of depending on the console, by using the excellent wdb, which is very handy for server apps, or for when I'm not launching the python code directly and have no access to its console:
def some_function():
try:
line_causing_some_exception()
except:
import wdb
wdb.post_mortem()
# Let the framework handle the exception as usual:
raise
In VS.Code, if I add a breakpoint after catching the exception, I don't have the "post mortem" traceback, only the current frame:
def some_function():
try:
line_causing_some_exception()
except:
import debugpy
debugpy.breakpoint()
# Let the framework handle the exception as usual:
raise
I couldn't find an equivalent debugpy.post_mortem() as there is for pdb and wdb.
How can I trigger a post-mortem debugging explicitly, similar to the "Uncaught Exceptions" checkbox, but for exceptions that are going to be caught on earlier frames?
I am using Python's logging module to generate an ERROR message in specific cases, followed by a sys.exit().
if platform is None:
logging.error(f'No platform provided!')
sys.exit()
Continue do other stuff
Now I am using pytest to unit test the specific error message. However the sys.exit() statement causes pytest to detect an error because of a SystemExit event, even if the error message passes the test.
And mocking the sys.exit makes that the rest of the code is being run ('Continue do other stuff') which then causes other problems.
I have tried the following:
LOGGER = logging.getLogger(__name__)
platform = None
data.set_platform(platform, logging=LOGGER)
assert "No platform provided!" in caplog.text
This question is a similar: How to assert both UserWarning and SystemExit in pytest , but it raises the error in a different way.
How do I make pytest ignore the SystemExit?
Here is one approach.
In your test module you could write the following test, where your_module is the name of the module where your actual code is defined, and function() is the function that is doing the logging and calling sys.exit().
import logging
from unittest import mock
from your_module import function
def test_function(caplog):
with pytest.raises(SystemExit):
function()
log_record = caplog.records[0]
assert log_record.levelno == logging.ERROR
assert log_record.message == "No platform provided!"
assert log_record.lineno == 8 # Replace with the line no. in which log is actually called in the main code.
(If you want to shorten this a little bit, you can use record_tuples instead of records.)
EDIT: Using caplog instead of mocking the log module.
I have a function to catch uncaught exceptions, below. Is there any way to write a unit test that will execute the uncaught_exception_handler() function, but exit the test normally?
import logging
def config_logger():
# logger setup here
def init_uncaught_exception_logger(logger):
'''Setup an exception handler to log uncaught exceptions.
This is typically called once per main executable.
This function only exists to provide a logger context to the nested function.
Args:
logger (Logger): The logger object to log uncaught exceptions with.
'''
def uncaught_exception_handler(*exc_args):
'''Log uncaught exceptions with logger.
Args:
exc_args: exception type, value, and traceback
'''
print("Triggered uncaught_exception_handler")
logger.error("uncaught: {}: {}\n{}".format(*exc_args))
sys.excepthook = uncaught_exception_handler
if __name__ == '__main__':
LOGGER = config_logger()
init_uncaught_exception_logger(LOGGER)
raise Exception("This is an intentional uncaught exception")
Instead of testing that your function is called for uncaught exceptions, it's probably best to instead test that the excepthook is installed, and that the function does the right thing when you call it manually. That gives you pretty good evidence that the excepthook will behave properly in real usage. You'll want to move your uncaught_exception_handler outside of init_uncaught_exception_logger so your tests can access it more easily.
assert sys.excepthook is uncaught_exception_handler
with your_preferred_output_capture_mechanism:
try:
1/0
except ZeroDivisionError:
uncaught_exception_handler(*sys.exc_info())
assert_something_about_captured_output()
If you want to actually invoke excepthook through an uncaught exception, then you'll need to launch a subprocess and examine its output. The subprocess module is the way to go for that.
In order to write assertions about raised exceptions, you can use pytest.raises as a context manager like this:
with raises(expected_exception: Exception[, match][, message])
import pytest
def test_which_will_raise_exception():
with pytest.raises(Exception):
# Your function to test.
Now, this unit test will pass only if any code under pytest.raises context manager will raise an exception provided as a parameter. In this case, it's Exception.
Trying to run robot code in python.
When running the following code, on the output console I get 'No handlers could be found for logger "RobotFramework"'
How do I log/print to console the raise AssertionError from another module?
Or log all logs sent to logger "RobotFramework"
from ExtendedSelenium2Library import ExtendedSelenium2Library
from robot.api import logger
class firsttest():
def googleit(self):
self.use_url = 'https://google.ca'
self.use_browser = 'chrome'
s2l = ExtendedSelenium2Library()
s2l.open_browser(self.use_url, self.use_browser)
s2l.maximize_browser_window()
try:
s2l.page_should_contain('this text does not exist on page')
except:
logger.console('failed')
runit = firsttest()
runit.googleit()
Get following console output:
failed
No handlers could be found for logger "RobotFramework"
How can I log my Python exceptions?
try:
do_something()
except:
# How can I log my exception here, complete with its traceback?
Use logging.exception from within the except: handler/block to log the current exception along with the trace information, prepended with a message.
import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)
logging.debug('This message should go to the log file')
try:
run_my_stuff()
except:
logging.exception('Got exception on main handler')
raise
Now looking at the log file, /tmp/logging_example.out:
DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
File "/tmp/teste.py", line 9, in <module>
run_my_stuff()
NameError: name 'run_my_stuff' is not defined
Use exc_info options may be better, remains warning or error title:
try:
# coode in here
except Exception as e:
logging.error(e, exc_info=True)
My job recently tasked me with logging all the tracebacks/exceptions from our application. I tried numerous techniques that others had posted online such as the one above but settled on a different approach. Overriding traceback.print_exception.
I have a write up at http://www.bbarrows.com/ That would be much easier to read but Ill paste it in here as well.
When tasked with logging all the exceptions that our software might encounter in the wild I tried a number of different techniques to log our python exception tracebacks. At first I thought that the python system exception hook, sys.excepthook would be the perfect place to insert the logging code. I was trying something similar to:
import traceback
import StringIO
import logging
import os, sys
def my_excepthook(excType, excValue, traceback, logger=logger):
logger.error("Logging an uncaught exception",
exc_info=(excType, excValue, traceback))
sys.excepthook = my_excepthook
This worked for the main thread but I soon found that the my sys.excepthook would not exist across any new threads my process started. This is a huge issue because most everything happens in threads in this project.
After googling and reading plenty of documentation the most helpful information I found was from the Python Issue tracker.
The first post on the thread shows a working example of the sys.excepthook NOT persisting across threads (as shown below). Apparently this is expected behavior.
import sys, threading
def log_exception(*args):
print 'got exception %s' % (args,)
sys.excepthook = log_exception
def foo():
a = 1 / 0
threading.Thread(target=foo).start()
The messages on this Python Issue thread really result in 2 suggested hacks. Either subclass Thread and wrap the run method in our own try except block in order to catch and log exceptions or monkey patch threading.Thread.run to run in your own try except block and log the exceptions.
The first method of subclassing Thread seems to me to be less elegant in your code as you would have to import and use your custom Thread class EVERYWHERE you wanted to have a logging thread. This ended up being a hassle because I had to search our entire code base and replace all normal Threads with this custom Thread. However, it was clear as to what this Thread was doing and would be easier for someone to diagnose and debug if something went wrong with the custom logging code. A custome logging thread might look like this:
class TracebackLoggingThread(threading.Thread):
def run(self):
try:
super(TracebackLoggingThread, self).run()
except (KeyboardInterrupt, SystemExit):
raise
except Exception, e:
logger = logging.getLogger('')
logger.exception("Logging an uncaught exception")
The second method of monkey patching threading.Thread.run is nice because I could just run it once right after __main__ and instrument my logging code in all exceptions. Monkey patching can be annoying to debug though as it changes the expected functionality of something. The suggested patch from the Python Issue tracker was:
def installThreadExcepthook():
"""
Workaround for sys.excepthook thread bug
From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html
(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
Call once from __main__ before creating any threads.
If using psyco, call psyco.cannotcompile(threading.Thread.run)
since this replaces a new-style class method.
"""
init_old = threading.Thread.__init__
def init(self, *args, **kwargs):
init_old(self, *args, **kwargs)
run_old = self.run
def run_with_except_hook(*args, **kw):
try:
run_old(*args, **kw)
except (KeyboardInterrupt, SystemExit):
raise
except:
sys.excepthook(*sys.exc_info())
self.run = run_with_except_hook
threading.Thread.__init__ = init
It was not until I started testing my exception logging I realized that I was going about it all wrong.
To test I had placed a
raise Exception("Test")
somewhere in my code. However, wrapping a a method that called this method was a try except block that printed out the traceback and swallowed the exception. This was very frustrating because I saw the traceback bring printed to STDOUT but not being logged. It was I then decided that a much easier method of logging the tracebacks was just to monkey patch the method that all python code uses to print the tracebacks themselves, traceback.print_exception.
I ended up with something similar to the following:
def add_custom_print_exception():
old_print_exception = traceback.print_exception
def custom_print_exception(etype, value, tb, limit=None, file=None):
tb_output = StringIO.StringIO()
traceback.print_tb(tb, limit, tb_output)
logger = logging.getLogger('customLogger')
logger.error(tb_output.getvalue())
tb_output.close()
old_print_exception(etype, value, tb, limit=None, file=None)
traceback.print_exception = custom_print_exception
This code writes the traceback to a String Buffer and logs it to logging ERROR. I have a custom logging handler set up the 'customLogger' logger which takes the ERROR level logs and send them home for analysis.
You can log all uncaught exceptions on the main thread by assigning a handler to sys.excepthook, perhaps using the exc_info parameter of Python's logging functions:
import sys
import logging
logging.basicConfig(filename='/tmp/foobar.log')
def exception_hook(exc_type, exc_value, exc_traceback):
logging.error(
"Uncaught exception",
exc_info=(exc_type, exc_value, exc_traceback)
)
sys.excepthook = exception_hook
raise Exception('Boom')
If your program uses threads, however, then note that threads created using threading.Thread will not trigger sys.excepthook when an uncaught exception occurs inside them, as noted in Issue 1230540 on Python's issue tracker. Some hacks have been suggested there to work around this limitation, like monkey-patching Thread.__init__ to overwrite self.run with an alternative run method that wraps the original in a try block and calls sys.excepthook from inside the except block. Alternatively, you could just manually wrap the entry point for each of your threads in try/except yourself.
You can get the traceback using a logger, at any level (DEBUG, INFO, ...). Note that using logging.exception, the level is ERROR.
# test_app.py
import sys
import logging
logging.basicConfig(level="DEBUG")
def do_something():
raise ValueError(":(")
try:
do_something()
except Exception:
logging.debug("Something went wrong", exc_info=sys.exc_info())
DEBUG:root:Something went wrong
Traceback (most recent call last):
File "test_app.py", line 10, in <module>
do_something()
File "test_app.py", line 7, in do_something
raise ValueError(":(")
ValueError: :(
EDIT:
This works too (using python 3.6)
logging.debug("Something went wrong", exc_info=True)
What I was looking for:
import sys
import traceback
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback_in_var = traceback.format_tb(exc_traceback)
See:
https://docs.python.org/3/library/traceback.html
Uncaught exception messages go to STDERR, so instead of implementing your logging in Python itself you could send STDERR to a file using whatever shell you're using to run your Python script. In a Bash script, you can do this with output redirection, as described in the BASH guide.
Examples
Append errors to file, other output to the terminal:
./test.py 2>> mylog.log
Overwrite file with interleaved STDOUT and STDERR output:
./test.py &> mylog.log
Here is a version that uses sys.excepthook
import traceback
import sys
logger = logging.getLogger()
def handle_excepthook(type, message, stack):
logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')
sys.excepthook = handle_excepthook
This is how I do it.
try:
do_something()
except:
# How can I log my exception here, complete with its traceback?
import traceback
traceback.format_exc() # this will print a complete trace to stout.
maybe not as stylish, but easier:
#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)
To key off of others that may be getting lost in here, the way that works best with capturing it in logs is to use the traceback.format_exc() call and then split this string for each line in order to capture in the generated log file:
import logging
import sys
import traceback
try:
...
except Exception as ex:
# could be done differently, just showing you can split it apart to capture everything individually
ex_t = type(ex).__name__
err = str(ex)
err_msg = f'[{ex_t}] - {err}'
logging.error(err_msg)
# go through the trackback lines and individually add those to the log as an error
for l in traceback.format_exc().splitlines():
logging.error(l)
Heres a simple example taken from the python 2.6 documentation:
import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)
logging.debug('This message should go to the log file')