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_typeto"background_job".Set
return_typeto"background_job_end".Set
subtypeto the name of your job runner.Add a
processhandler that returns a dictionary withname,argsandkwargskeys.nameis the name of the background job andargsandkwargsare the data passed to it.
Outbound http requests¶
To implement a processor for a http library like requests, httpx, urllib3 or urllib:
Set
call_typeto"outbound_http_request".Set
return_typeto"outbound_http_response".Set
subtypeto the name of the http library.Add a
processhandler:
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",
]