Search code examples
pythondebuggingstack-trace

Print argument values to functions in stack trace


How can we print the values of arguments passed to the functions in the call stack when an error stack trace is printed? I would like the output to be exactly as in the example below.

Example:

Traceback (most recent call last):
  File "./file.py", line 615, in func0 **(arg0) arg0 = 0 was passed**
    result = func1(arg1, arg2)
  File "./file.py", line 728, in func1 **(arg1, arg2) arg1 = 1 and arg2 = 2 was passed**
    return int_value[25]
TypeError: 'int' object is not iterable

I'd like the information inside the ** **s above to also be printed in addition to the normal output in the stack trace. What I envision is that the debugger automatically prints the passed arguments as well. That would give a clear picture of the "functional pipeline" that the data was passed through and what happened to it in the pipeline and which function did not do what it was supposed to do. This would help debugging a lot.

I searched quite a bit and found these related questions:

but the answers to neither of them worked for me: The answer to the 1st one led to ModuleNotFoundError: No module named 'stackdump'. The answer to the 2nd one crashed my ipython interpreter with a very long stack trace.

I also looked up:

There seems to be a capture_locals variable for TracebackExceptions, but I didn't quite understand how to make it work.


Solution

  • Pure Python3

    Talking about TracebackExceptions and it's capture_locals argument, we can use it as follows:

    #!/usr/bin/env python3
    
    import traceback
    
    c = ['Help me!']
    
    def bar(a = 3):
        d = {1,2,3}
        e = {}
        foo(a)
    
    def foo(a=4):
        b = 4
        if a != b:
            raise Exception("a is not equal to 4")
    
    try:
        bar(3)
    except Exception as ex:
        tb = traceback.TracebackException.from_exception(ex, capture_locals=True)
        print("".join(tb.format()))
    
    

    Which prints all local variables for each frame:

    $ ./test.py 
    Traceback (most recent call last):
      File "./test.py", line 21, in <module>
        bar(3)
        __annotations__ = {}
        __builtins__ = <module 'builtins' (built-in)>
        __cached__ = None
        __doc__ = None
        __file__ = './test.py'
        __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0x7f81073704c0>
        __name__ = '__main__'
        __package__ = None
        __spec__ = None
        bar = <function bar at 0x7f81073b11f0>
        c = ['Help me!']
        ex = Exception('a is not equal to 4')
        foo = <function foo at 0x7f810728a160>
        traceback = <module 'traceback' from '/usr/lib/python3.8/traceback.py'>
      File "./test.py", line 11, in bar
        foo(a)
        a = 3
        d = {1, 2, 3}
        e = {}
      File "./test.py", line 17, in foo
        raise Exception("a is not equal to 4")
        a = 3
        b = 4
    Exception: a is not equal to 4
    

    Looks a bit too verbose, but sometimes this data could be vital in debugging some crash.

    Loguru

    There is also a package loguru that prints "Fully descriptive exceptions":

    2018-07-17 01:38:43.975 | ERROR    | __main__:nested:10 - What?!
    Traceback (most recent call last):
    
      File "test.py", line 12, in <module>
        nested(0)
        └ <function nested at 0x7f5c755322f0>
    
    > File "test.py", line 8, in nested
        func(5, c)
        │       └ 0
        └ <function func at 0x7f5c79fc2e18>
    
      File "test.py", line 4, in func
        return a / b
               │   └ 0
               └ 5
    
    ZeroDivisionError: division by zero