# 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`: ```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: ```python # 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. ```python 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](https://docs.python.org/3/library/inspect.html#types-and-members) 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`](https://docs.python.org/3/library/inspect.html#types-and-members). 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: ```python 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`](https://docs.python.org/3/library/sys.html#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: ```python 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`](https://docs.python.org/3/library/sys.html#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 with `name`, `args` and `kwargs` keys. `name` is the name of the background job and `args` and `kwargs` 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: ```python 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`](../reference/config) instead: ```toml processors = [ "mypackage.processors:processor_config", "mypackage.processors:another_config", ] ```