Extending Kolo with plugins
Kolo supports defining plugins that add custom data into Kolo traces.
A plugin is a Python package that has one or more entry points in pyproject.toml
:
[project.entry-points."kolo.processors"]
myprocessor = "mypackage.processors:processor_config"
another = "mypackage.processors:another_config"
Each of these entry points should be a Python dictionary containing configuration data for the processor:
# mypackage/processors.py
processor_config = {
"co_names": ["send"],
"path_fragment": "/mypackage/client.py",
"call": call,
"call_type": "outbound_http_request",
"return_type": "outbound_http_response",
"subtype": "mypackage",
"process": process,
"build_context": build_context,
}
The entries are split into three categories: build_context
, those used for selecting which code to trace and those used when processing that code.
build_context
This optional function allows creating a dictionary that will be passed to call
and process
to allow sharing data between calls. Kolo doesn’t use this dictionary for any other purpose.
type Context = Dict[str, Any]
def build_context(config: Dict[str, Any]) -> Context {
return {"mydata": []}
}
Selecting code to trace
co_names
A list of names of code objects to match against. These are usually function or method names.
path_fragment
A fragment of a file path. This is used to match against a code object’s co_filename
. Path separators should be written using /
and will be converted to \
on Windows.
call
An optional function that allows for further control over when the processor runs. For example, to only run on return
events you could write:
def call(
frame: types.FrameType,
event: str,
arg: object,
context: Context,
) -> bool:
return event == "return"
frame
, event
and arg
are described in the documentation for sys.setprofile
. context
is the dictionary created by build_context
.
The call
function is used in addition to co_names
and path_fragment
, not instead of them.
Processing code
call_type
This string is included in the returned data under the type
key for call
events. It is used by other tools (e.g. VSCode) to identify how to handle the data.
return_type
This string is included in the returned data under the type
key for return
events. It is used by other tools (e.g. VSCode) to identify how to handle the data.
subtype
This optional string is included in the returned data under the subtype
key. It can be used to disambiguate between different tools sharing the same type
.
process
An optional function that allows for further control over the returned data. For example, to include all local variables you could write:
def process(
frame: types.FrameType,
event: str,
arg: object,
context: Context,
) -> Dict[str, Any]:
return {"locals": frame.f_locals}
frame
, event
and arg
are described in the documentation for sys.setprofile
. context
is the dictionary created by build_context
.
The output of process
is merged into the default return data, so can override keys such as type
and subtype
if necessary.
Integrating with VSCode
Currently we don’t provide a way to write a plugin for the VSCode extension. Instead, plugin authors are encouraged to reuse existing features.
Background jobs
To implement a processor for a background job runner like celery
or huey
:
Set
call_type
to"background_job"
.Set
return_type
to"background_job_end"
.Set
subtype
to the name of your job runner.Add a
process
handler that returns a dictionary withname
,args
andkwargs
keys.name
is the name of the background job andargs
andkwargs
are the data passed to it.
Outbound http requests
To implement a processor for a http library like requests
, httpx
, urllib3
or urllib
:
Set
call_type
to"outbound_http_request"
.Set
return_type
to"outbound_http_response"
.Set
subtype
to the name of the http library.Add a
process
handler:
def process(
frame: types.FrameType,
event: str,
arg: Any,
context: Context,
) -> Dict[str, Any]:
if event == "call":
return {
"body": request_body,
"headers": request_headers,
"method": method,
"method_and_full_url": f"{method} {url}",
"url": url
}
elif event == "return":
return {
"body": response_body,
"headers": response_headers,
"method": method,
"method_and_full_url": f"{method} {url}",
"status_code": status_code,
"url": url
}
Local processor plugins
You can also define a custom processor locally. The process is identical, except you edit .kolo/config.toml
instead:
processors = [
"mypackage.processors:processor_config",
"mypackage.processors:another_config",
]