Apart from sys.monitoring
, PEP 578 proposes a trace mechanism named sys.audit
in CPython, providing more flexible interfaces for users to invoke a callback.
For instance, you can raise a audit event each time a function gets called to count its frequency:
import sys
count = 0
def fib_hook(event, args):
global count
if event == "fib.audit":
count += 1
print(f"event: {event}, args: {args}, count: {count}")
def fib(x: int) -> int:
sys.audit("fib.audit")
if x == 0 or x == 1:
return 1
return fib(x - 1) + fib(x - 2)
sys.addaudithook(fib_hook)
print(fib(4))
We can see fib
is called 9 times when calculating fib(4)
.
In this post, we'll briefly go through the underlying implementation of sys.audit
.
sys.addaudithook
It does nothing but appends the received hook function into a per-interpreter list, see the details in function sys_addaudithook_impl
.
It should be mentioned that if the hook list has been allocated in the IS, it will trigger a event named sys.addaudithook
first.
If there is any previous raised error, the newly added hook will NOT be appended into the IS list.
sys.audit
Intuitively, sys.audit
just iterates the IS hook list and use vector calling convention to invoke each iterated hook.
You can trigger the audit via Python level interface sys.audit
or C API PySys_Audit
.
Note there are some system audit raised by some internal system routines:
static PyObject *
os_chdir_impl(PyObject *module, path_t *path)
/*[clinic end generated code: output=3be6400eee26eaae input=1a4a15b4d12cb15d]*/
{
int result;
if (PySys_Audit("os.chdir", "(O)", path->object) < 0) {
return NULL;
}
/*
...
*/
}
We can see whenever the os.chdir
gets called, it will raise a audit event with the path passed through.
To check all the audit events, see the audit event table.