Simple Tracing in Python

Custom tracing for debugging in Python
Date
Author @asyncze

Contents

Tracing made easy #

In python, print() is the quick–and-dirty always-available debug tool that everyone uses but no one is talking about. A few thousand lines and a couple of weeks or months into a project and there's very likely print statements nested into just about everything (most which needs to be removed before releasing the software).

I do this a lot, but have lately started to do something similar but more informative than a simple print().

import inspect

def trace(*args):
    frame = inspect.currentframe()
    caller_frame = frame.f_back

    instance = caller_frame.f_locals.get('self', None)

    class_name = instance.__class__.__name__ if instance else "Global"
    instance_id = id(instance) if instance else 0
    function_name = caller_frame.f_code.co_name

    # _, _, _, values = inspect.getargvalues(caller_frame)
    # for arg in args: print(f"{ arg } = { values[arg] }")

    print(f"{ class_name } @ { instance_id } : { function_name } : { args[0:] }")

    del frame
    del caller_frame

So, the inspect module is a great way to look into what's going on under the hood of Python programs, but also the automate some parts of tracing. In the above function, trace(), we're getting class name, instance id (in case of multiple instances of same class), function name, and any arguments passed to function. This is all from just placing a single call to trace().

And that's it. Just include a call to trace(arguments), with whatever arguments interested in from anywhere in the source code.

trace(event)

All traces can later be disabled by replacing function with pass or just adding debug toggle to trace function, or avoiding the call completely by adding debug toggle to line of call.

def trace(*args): pass
if DEBUG: trace(event)

Examples #

Here's an example trace from another project:

class CodeEditor(QsciScintilla):
    ...

    def keyPressEvent(self, event):
        if DEBUG: trace(f"modifiers : { int(event.modifiers()) }", f"key : { event.key() }")

        ...
CodeEditor @ 4335861440 : keyPressEvent : ('modifiers : 67108864', 'key : 16777249')

This is a trace originating in the keyPressEvent function with parts of event in arguments (showing which keys are pressed) from class CodeEditor with id 4335861440.

Here's another:

def load_config():
    ...

    if DEBUG: trace(user_config_path)

    ...
Global @ 0 : load_config : ('/Users/michael/Library/Application Support/Hackerman Text/.hackerman',)

This is a trace from Global, as seen in class_name = instance.__class__.__name__ if instance else "Global", which is functions outside classes, so global functions.