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",
]