You can't debug a program that ran in the past, but you can read its info-level logs.
import logging
import threading
logger = logging.getLogger(__name__)
def log(fn):
def log_inner(*a, **kw):
logger.info('%s(%s, %s) @ %s', fn, a, kw, threading.get_native_id())
return fn(*a, **kw)
return log_inner
It's pretty easy to grab all the arguments and thread, but grabbing the return would depend on you not mutating anything permanent, so that probably needs some special casing.In critical systems, it's not unheard of to transaction log every input and every response to external requests, have strictly deterministic code, so that you specifically can replay or reconstruct a system state.
It's a pretty demanding way to build software, but you also get extremely resilient software as a result.