Integrating Telemetry into Your Package

This guide walks through adding access-py-telemetry to a Python package from scratch.

Step 1: Add the dependency

In your pyproject.toml:

[project]
dependencies = [
    "access-py-telemetry",
]

Step 2: Define your config.yaml

Create a config.yaml inside your package directory. This file defines which functions to track and maps them to API endpoint paths.

mypackage:
  run:
    - Experiment.run
    - Experiment.submit
  results:
    - Results.save
    - Results.export

See Configuration Reference for the full schema and endpoint naming rules.

Step 3: Register functions using decorators

For functions used primarily inside Jupyter notebooks, use @ipy_register_func:

from access_py_telemetry.decorators import ipy_register_func

@ipy_register_func("mypackage_run")
def run(config):
    ...

For functions used outside Jupyter (e.g. in scripts or CLI tools), use @register_func:

from access_py_telemetry.decorators import register_func

@register_func("mypackage_run")
def run(config):
    ...

See Decorator Reference for full decorator options including extra_fields and pop_fields.

Step 4: Register functions dynamically (alternative to decorators)

You can also register functions at runtime using TelemetryRegister:

from access_py_telemetry.registry import TelemetryRegister

registry = TelemetryRegister("mypackage_run")
registry.register("run", "submit")

You can pass function objects directly as well:

registry.register(run, submit)

Step 5: Add extra fields

To capture additional context with each telemetry record, use ApiHandler.add_extra_fields():

from access_py_telemetry.api import ApiHandler

ApiHandler().add_extra_fields("mypackage_run", {"config_version": config.version})

ApiHandler is a singleton — any instance you create in any module points to the same object. You can safely call add_extra_fields from multiple modules without passing instances around:

# mypackage/component_a.py
from access_py_telemetry.api import ApiHandler
ApiHandler().add_extra_fields("mypackage_run", {"source": "component_a"})

# mypackage/component_b.py
from access_py_telemetry.api import ApiHandler
ApiHandler().add_extra_fields("mypackage_run", {"source": "component_b"})

Extra fields configured at import-time (module level) will be present in all telemetry records sent for that service.

Step 6: Request an endpoint

Raise an issue on ACCESS-NRI/tracking-services to request an API endpoint for your package. Include the service name(s) and the fields you expect to send.

Step 7: Update config.yaml in this repo

When you are ready to ship, open a PR to add your service and functions to the config.yaml in this repository:

# existing entries
intake:
  catalog:
    - esm_datastore.search

# your new entry
mypackage:
  run:
    - Experiment.run
    - Experiment.submit

Telemetry payload format

A typical record sent to the endpoint looks like:

{
    "id": 1,
    "timestamp": "2024-12-19T07:34:44.229048Z",
    "name": "username",
    "function": "run",
    "args": [],
    "kwargs": {"config": "my_config.yaml"},
    "session_id": "83006a25092df6bae313f1e4b6be93f8...",
    "config_version": "1.2"
}

Fields beyond the defaults (name, function, args, kwargs, session_id, timestamp) are any extras you have added.