I have the following code:
# exc is a local variable of type Exception
# This is not inside an except block
if isinstance(exc, ClientError):
logging.debug("ClientError raised while loading %s:\n%s", package.id, traceback.format_exc())
continue
When this code is run and exc
is of type ClientError
, format_exc()
just prints out NoneType: None
because no exception is currently being handled (the code is not inside an except
block). Luckily there appears to be the format_exception
method on traceback
that isn't coupled to whatever the current exception being handled is, but in order to call it I need to extract the type, values, and tb from my exception variable. How do I do this?
How is exc
being produced? If it is being returned from some function without the corresponding stack then it is not possible to produce the correct frames anyway. On top of that, it is not possible to generate a Traceback
object without going deep into ctypes
, so this is likely not what is desired.
If what you are after is actually the stack at where the exception was logged, making use of inspect.currentframe
and traceback.format_stack
may produce what you might be after. However, as mentioned, you will need to get the frames as close to where the error occurred. Consider this example:
import traceback
import inspect
import logging
class Client:
pass
class ClientError(Exception):
pass
def get_client(name):
if name is None:
return ClientError('client must have a name')
return Client()
def connect(target, name=None):
exc = get_client(name)
if isinstance(exc, ClientError):
frames = inspect.currentframe()
logging.debug("ClientError raised while loading %s:\n%s",
target, ''.join(traceback.format_stack(frames)))
def main():
connect('somewhere')
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
main()
Executing this will produce the following output:
DEBUG:root:ClientError raised while loading somewhere:
File "foo.py", line 34, in <module>
main()
File "foo.py", line 30, in main
connect('somewhere')
File "foo.py", line 26, in connect
target, ''.join(traceback.format_stack(frames)))
Note that the stack ends exactly where the call is done, as the return value of the current_frame
is bounded to frames
. This is why the stack should be generated and formatted at where it was produced, and step back one level. Consider these updated functions:
def get_client(name):
if name is None:
return (
ClientError('client must have a name'),
traceback.format_stack(inspect.currentframe().f_back),
)
return Client(), None
def connect(target, name=None):
exc, frames = get_client(name)
if isinstance(exc, ClientError):
stack = ''.join(frames)
logging.debug("ClientError raised while loading %s:\n%s",
target, stack)
Execution
$ python foo.py
DEBUG:root:ClientError raised while loading somewhere:
File "foo.py", line 37, in <module>
main()
File "foo.py", line 33, in main
connect('somewhere')
File "foo.py", line 25, in connect
exc, frames = get_client(name)
Note how the trace ends at the function that produced the exception.