Michael
Michael

Reputation: 83

How to determine the origin of a Python exception

I'm debugging a complex program that has a block like this:

try:
  lots()
  of()
  deeply()
  nested()
  code()
except BaseException as e:
  log_error(str(e))

The error message that comes out is just Config file missing but that's not much help to me.

I'd really like to see exactly were that message comes from. (Note that the error string is from an external program, so it's not searchable.)

If I use traceback I only get to see the stack trace after it's been wound back to the handler, which is not useful. I'd like to see the traceback at the source of the exception.

Any ideas?
Thanks!

Upvotes: 0

Views: 2471

Answers (2)

First and foremost, you should absolutely stick with the best practices and use the builtin module traceback, as sugested by Roman.

But i found it quite a fun and interesting challenge to try and avoid traceback, so i gave it a try to implement it myself.

Exceptions store their traces in the traceback attribute, which is a linked list. Each tb_next represents the next item in that list (in this case, it represents the previous place where the exception was handled).

Here's what i came up with:

def extract_stack_from_exception(exception):
    """
    Extracts and returns the stack trace from the provided exception.
    
    Args:
        exception (Exception): The exception to extract the stack trace from.
        
    Returns:
        list[dict[str, str]]: A list of dictionaries containing the filename, 
                            function name, and line number of each stack frame.
    """
    stack = []

    # Only interact with the traceback, if there is any traceback
    if hasattr(exception, '__traceback__'):
        
        # Grabs the traceback object
        frame = exception.__traceback__

        # Keep the loop alive if the frame is valid and has a "tb_next" 
        while frame and hasattr(frame, 'tb_next'):
            stack.append({
                'filename': frame.tb_frame.f_code.co_filename,
                'function': frame.tb_frame.f_code.co_qualname,
                'lineno': frame.tb_lineno,
            })

            # Moves the frame to the next "tb"
            frame = frame.tb_next

    return stack

It is a similar approach to the methods used inside the actual traceback module (traceback.walk_tb and traceback._walk_tb_with_full_positions), where it iterates over that linked list structure reading those 3 items (filename, function, and line number).

Each tb has a frame, it stores every attribute associated with the place where the exception was raised. So, for a simple trace, we just need the filename and method name.

You can read more about that method, and about the traceback module itself here.

Upvotes: 0

Roman
Roman

Reputation: 543

For debugging purposes you can use traceback module to print stack trace

import traceback

try:
    lots()
    of()
    deeply()
    nested()
    code()
except BaseException as e:
    print traceback.format_exc()
    log_error(str(e))

Upvotes: 2

Related Questions