Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions livekit-agents/livekit/agents/telemetry/trace_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
# session start
ATTR_JOB_ID = "lk.job_id"
ATTR_AGENT_NAME = "lk.agent_name"
ATTR_CLOUD_AGENT_ID = "lk.cloud_agent_id"
ATTR_DEPLOYMENT_ID = "lk.deployment_id"
ATTR_ROOM_NAME = "lk.room_name"
ATTR_SESSION_OPTIONS = "lk.session_options"

Expand Down
8 changes: 8 additions & 0 deletions livekit-agents/livekit/agents/telemetry/traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import asyncio
import json
import logging
import os
import threading
import time
from collections.abc import Callable, Iterator
Expand Down Expand Up @@ -280,6 +281,13 @@ def __call__(self) -> dict[str, str]:
# only; the default dispatch has no agent name). Included in both the
# resource (traces) and the session metadata (spans + logs).
base_metadata[trace_types.ATTR_AGENT_NAME] = agent_name
# cloud agent id and deployment provided by LiveKit Cloud via env vars.
# Included in both the resource and the session metadata like agent_name;
# omitted when unset.
if cloud_agent_id := os.environ.get("LIVEKIT_AGENT_ID"):
base_metadata[trace_types.ATTR_CLOUD_AGENT_ID] = cloud_agent_id
if deployment_id := os.environ.get("LIVEKIT_AGENT_DEPLOYMENT"):
base_metadata[trace_types.ATTR_DEPLOYMENT_ID] = deployment_id
session_metadata = dict(base_metadata)
if metadata:
session_metadata.update(metadata)
Expand Down
64 changes: 64 additions & 0 deletions tests/test_recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,70 @@ def test_setup_cloud_tracer_logger_provider_always_created() -> None:
mock_blrp.assert_not_called()


def _resource_attrs_for_env(env: dict[str, str]) -> dict[str, Any]:
"""Run _setup_cloud_tracer under the given os.environ and return the dict
passed to Resource.create."""
from livekit.agents.telemetry.traces import _setup_cloud_tracer

with (
patch.dict("os.environ", env, clear=True),
patch(f"{_TRACES_MOD}.api.AccessToken") as mock_at,
patch(f"{_TRACES_MOD}.get_logger_provider", return_value=MagicMock()),
patch(f"{_TRACES_MOD}.set_logger_provider"),
patch(f"{_TRACES_MOD}.OTLPLogExporter"),
patch(f"{_TRACES_MOD}.BatchLogRecordProcessor"),
patch(f"{_TRACES_MOD}.Resource.create") as mock_resource_create,
patch(f"{_TRACES_MOD}.logging"),
):
mock_token = MagicMock()
mock_token.with_observability_grants.return_value = mock_token
mock_token.with_ttl.return_value = mock_token
mock_token.to_jwt.return_value = "test-jwt"
mock_at.return_value = mock_token

_setup_cloud_tracer(
room_id="room-1",
job_id="job-1",
**_observability_endpoint_arg(_setup_cloud_tracer),
enable_traces=False,
enable_logs=False,
)
# Resource.create is also called internally by LoggerProvider() with an
# empty dict, so select the call that built the tracing resource (the one
# carrying service.name) rather than relying on call ordering.
for call in mock_resource_create.call_args_list:
attrs = call.args[0]
if "service.name" in attrs:
return attrs
raise AssertionError("Resource.create was not called with the service resource")


def test_setup_cloud_tracer_adds_identity_from_env() -> None:
"""LIVEKIT_AGENT_ID / LIVEKIT_AGENT_DEPLOYMENT become
lk.cloud_agent_id / lk.deployment_id on the tracing resource."""
attrs = _resource_attrs_for_env(
{"LIVEKIT_AGENT_ID": "CA_test123", "LIVEKIT_AGENT_DEPLOYMENT": "canary"}
)
assert attrs["lk.cloud_agent_id"] == "CA_test123"
assert attrs["lk.deployment_id"] == "canary"


def test_setup_cloud_tracer_omits_identity_when_env_unset() -> None:
"""Neither identity attr is set when the env vars are absent."""
attrs = _resource_attrs_for_env({})
assert "lk.cloud_agent_id" not in attrs
assert "lk.deployment_id" not in attrs


def test_setup_cloud_tracer_omits_empty_deployment() -> None:
"""An empty LIVEKIT_AGENT_DEPLOYMENT is omitted rather than emitted."""
attrs = _resource_attrs_for_env(
{"LIVEKIT_AGENT_ID": "CA_test123", "LIVEKIT_AGENT_DEPLOYMENT": ""}
)
assert attrs["lk.cloud_agent_id"] == "CA_test123"
assert "lk.deployment_id" not in attrs


# ---------------------------------------------------------------------------
# Group 4: RecorderIO conditional creation
# ---------------------------------------------------------------------------
Expand Down