Can I make Python output exceptions in one line / via logging? - python

I am using AWS and use AWS cloudwatch to view logs. While things should not break on AWS, they could. I just had such a case. Then I searched for Traceback and just got the lines
Traceback (most recent call last):
without the actual traceback. I have a working structured logging setup (see other question) and I would like to get tracebacks in a similar way.
So instead of:
Traceback (most recent call last):
File "/home/math/Desktop/test.py", line 32, in <module>
adf
NameError: name 'adf' is not defined
something like
{"message": "Traceback (most recent call last):\n File \"/home/math/Desktop/test.py\", line 32, in <module>\n adf\n NameError: name 'adf' is not defined", "lineno": 35, "pathname": "/home/math/Desktop/test.py"}
or even better also with the string in a JSON format.
The only way to achieve this I can think of is a giant try-except block. Pokemon-style. Is there a better solution?

You can use sys.excepthook. It is invoked whenever an exception occurs in your script.
import logging
import sys
import traceback
def exception_logging(exctype, value, tb):
"""
Log exception by using the root logger.
Parameters
----------
exctype : type
value : NameError
tb : traceback
"""
write_val = {'exception_type': str(exctype),
'message': str(traceback.format_tb(tb, 10))}
logging.exception(str(write_val))
Then in your script you have to override the value of sys.excepthook.
sys.excepthook = exception_logging
Now whenever an exception occurs it will be logged with your logger handler.
Note: Don't forget to setup logger before running this

In case somebody wants the exception logged in its default format, but in one line (for any reason), based on the accepted answer:
def exception_logging(exctype, value, tb):
"""
Log exception in one line by using the root logger.
Parameters
----------
exctype : exception type
value : seems to be the Exception object (with its message)
tb : traceback
"""
logging.error(''.join(traceback.format_exception(exctype, value, tb)))
Please also note, that it uses logging.error() instead of logging.exception() which also printed some extra "NoneType: None" line.
Also note that it only seems to work with uncaught exceptions.
For logging caught exceptions, visit How do I can format exception stacktraces in Python logging? and see also my answer.

A slight variation: If you run a Flask application, you can do this:
#app.errorhandler(Exception)
def exception_logger(error):
"""Log the exception."""
logger.exception(str(error))
return str(error)

Related

Why doesn't python logging.exception method log traceback by default?

When writing defensive code in python (e.g. you're handling some user input or whatever), I find it useful to return Exception objects alongside regular computation results, so they can be discarded/logged or processed in some other way. Consider the following snippet:
import logging
from traceback import TracebackException
from typing import Union
logging.basicConfig(level=logging.INFO)
def _compute(x) -> int:
return len(x)
def compute(x) -> Union[int, Exception]:
try:
return _compute(x)
except Exception as e:
return e
inputs = [
'whatever',
1,
'ooo',
None,
]
outputs = []
for i in inputs:
r = compute(i)
outputs.append(r)
for i, r in zip(inputs, outputs):
logging.info('compute(%s)', i)
if isinstance(r, Exception):
logging.exception(r)
else:
logging.info(r)
This results in the following output
INFO:root:compute(whatever)
INFO:root:8
INFO:root:compute(1)
ERROR:root:object of type 'int' has no len()
NoneType: None
INFO:root:compute(ooo)
INFO:root:3
INFO:root:compute(None)
ERROR:root:object of type 'NoneType' has no len()
NoneType: None
So you can see that useful exception information like stacktrace is lost, which makes it a bit hard to debug the cause of exception.
This can be fixed by logging exception as logging.exception(r, exc_info=r):
INFO:root:compute(whatever)
INFO:root:8
INFO:root:compute(1)
ERROR:root:object of type 'int' has no len()
Traceback (most recent call last):
File "/tmp/test.py", line 15, in compute
return _compute(x)
File "/tmp/test.py", line 10, in _compute
return len(x)
TypeError: object of type 'int' has no len()
INFO:root:compute(ooo)
INFO:root:3
INFO:root:compute(None)
ERROR:root:object of type 'NoneType' has no len()
Traceback (most recent call last):
File "/tmp/test.py", line 15, in compute
return _compute(x)
File "/tmp/test.py", line 10, in _compute
return len(x)
TypeError: object of type 'NoneType' has no len()
My question is -- why doesn't logging.exception method do this by default, if the argument passed to it happens to be an Exception? I tried searching in PEPs/etc but wasn't really fruitful.
My only guess is that logging.exception is essentially just a special case of logging.error, so in principle logging.exception method doesn't know whether is' passed an Exception object or something else. So supporting this would require some code, e.g. checking whether isinstance(msg, Exception), and perhaps the authors of logging library decided it's a bit too specific. But IMO it makes sense considering in practice in most cases logging.exception is passed an Exception object.
logging.exception does log the traceback by default. However, you're using it wrong. As the docs say,
This function should only be called from an exception handler.
logging.exception does not expect to be passed an exception instance, or any sort of exception information whatsoever. It uses sys.exc_info to gather info about the exception currently being handled, which only works if an exception is currently being handled. If you call it outside of an exception handler, it breaks.

print help text in try-exception in python

in a try-exception block in python, like shown below, I want my help message to be printed, instead of python's own error message. Is this possible?
def genpos(a):
''' Generate POSCAR :
Some error message'''
try:
tposcar = aio.read(os.path.join(root,"nPOSCAR"))
cell = (tposcar.get_cell())
cell[0][0] = a
cell[1][1] = math.sqrt(3)*float(a)
tposcar.set_cell(cell, scale_atoms=True)
aio.write("POSCAR", tposcar, direct=True)
except:
help(genpos)
sys.exit()
So, say, when this code is called without an argument, I want to get "Generate POSCAR : Some error message" instead of, python's
Traceback (most recent call last):
File "submit.py", line 41, in <module>
main()
File "submit.py", line 36, in __init__
ase_mod.genpos()
TypeError: genpos() missing 1 required positional argument: 'a'
You can define a new exception:
class CustomError(Exception): pass
raise CustomError('Generate POSCAR : Some error message')
Although, the error you're receiving has nothing to do with the try-except statement. Instead, your gen_pos() function is missing an argument.

How can I get the traceback object ( sys.exc_info()[2] , same as sys.exc_traceback ) as a string?

I have a function which catches all exceptions, and I want to be able to get the traceback as a string within this function.
So far this is not working:
def handle_errors(error_type, error_message, error_traceback):
"""catch errors"""
import traceback
error = {}
error['type'] = error_type.__name__
error['message'] = str(error_message)
error['file'] = os.path.split(error_traceback.tb_frame.f_code.co_filename)[1]
error['line'] = error_traceback.tb_lineno
error['traceback'] = repr(traceback.print_tb(error_traceback))
### finalise error handling and exit ###
sys.excepthook = handle_errors
It's the error['traceback'] line which is wrong. Do i even need to use the traceback module?
As per this other vaguely similar question, I have tried:
error['traceback'] = repr(error_traceback.print_exc())
...but this gives an error:
Error in sys.excepthook:
Traceback (most recent call last):
File "xxxxxxxxxxx", line 54, in handle_errors
error['traceback'] = repr(error_traceback.print_exc())
AttributeError: 'traceback' object has no attribute 'print_exc'
Use traceback.format_tb() instead of print_tb() to get the formatted stack trace (as a list of lines):
error['traceback'] = ''.join(traceback.format_tb(error_traceback))
print_tb() directly prints the traceback, that's why you get None as a result (that's the default for any Python function that doesn't return anything explicitely).
traceback.format_exc([limit])
This is like print_exc(limit) but
returns a string instead of printing to a file.
New in version 2.4.
error['traceback'] = traceback.format_exc(error_traceback)

python webbrowser

I've been using this module without problems by calling it as:
webbrowser.open("http link...")
now, however, I wanted to select a different browser, and according to the docs (http://docs.python.org/library/webbrowser.html#webbrowser.get) I wrote this
controller = webbrowser.get('firefox')
controller("http link...")
... and I get an error I'm unable to get rid of:
Exception in Tkinter callback
Traceback (most recent call last):
....
TypeError: 'Mozilla' object is not callable
any idea about it???
A Controller object is not callable. Do this:
controller.open(url)

AssertRaises fails even though exception is raised

I am running into the following rather strange problem:
I am developing a django app and in my models class I am defining an exception that should be raised when a validation fails:
class MissingValueException(Exception):
"""Raise when a required attribute is missing."""
def __init__(self, message):
super(MissingValueException, self).__init__()
self.message = message
def __str__(self):
return repr(self.message)
This code is called from a publication class in a validation method:
def validate_required_fields(self):
# Here is the validation code.
if all_fields_present:
return True
else:
raise MissingValueException(errors)
In my unit test I create a case where the exception should be raised:
def test_raise_exception_incomplete_publication(self):
publication = Publication(publication_type="book")
self.assertRaises(MissingValueException, publication.validate_required_fields)
This produces the following output:
======================================================================
ERROR: test_raise_exception_incomplete_publication (core_knowledge_platform.core_web_service.tests.logic_tests.BusinessLogicTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/media/data/Dokumente/Code/master_project/core_knowledge_platform/../core_knowledge_platform/core_web_service/tests/logic_tests.py", line 45, in test_raise_exception_incomplete_publication
self.assertRaises(MissingValueException, method, )
File "/usr/lib/python2.7/unittest/case.py", line 465, in assertRaises
callableObj(*args, **kwargs)
File "/media/data/Dokumente/Code/master_project/core_knowledge_platform/../core_knowledge_platform/core_web_service/models.py", line 150, in validate_required_fields
raise MissingValueException(errors)
MissingValueException: 'Publication of type book is missing field publisherPublication of type book is missing field titlePublication of type book is missing field year'
So it looks like the exception is raised (which is the case - I even checked it in an interactive IPython session), but it seems that assertRaises is not catching it.
Anyone has any idea why this might happen?
Thanks
This could happen if your tests and your product code are importing your exception class through two different paths, so asserRaises doesn't realize that the exception you got was the one you were looking for.
Look at your imports, make sure that they are the same in both places. Having the same directories available in two different ways in your PYTHONPATH can make this happen. Symbolic links in those entries can also confuse things.

Categories