Skip to content

Commit 806df5d

Browse files
authored
(Feat) datadog_llm_observability callback - emit request_tags on logs (#7883)
* dd - emit tags on llm obs payload * dd - show requester tags on traces * test_get_datadog_tags * _get_datadog_tags * fix dd POD_NAME * test_get_datadog_tags
1 parent 4b88635 commit 806df5d

File tree

5 files changed

+82
-3
lines changed

5 files changed

+82
-3
lines changed

litellm/integrations/datadog/datadog.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,9 @@ def create_datadog_logging_payload(
274274

275275
dd_payload = DatadogPayload(
276276
ddsource=self._get_datadog_source(),
277-
ddtags=self._get_datadog_tags(),
277+
ddtags=self._get_datadog_tags(
278+
standard_logging_object=standard_logging_object
279+
),
278280
hostname=self._get_datadog_hostname(),
279281
message=json_payload,
280282
service=self._get_datadog_service(),
@@ -444,8 +446,33 @@ def _create_v0_logging_payload(
444446
return dd_payload
445447

446448
@staticmethod
447-
def _get_datadog_tags():
448-
return f"env:{os.getenv('DD_ENV', 'unknown')},service:{os.getenv('DD_SERVICE', 'litellm')},version:{os.getenv('DD_VERSION', 'unknown')},HOSTNAME:{DataDogLogger._get_datadog_hostname()},POD_NAME:{os.getenv('POD_NAME', 'unknown')}"
449+
def _get_datadog_tags(
450+
standard_logging_object: Optional[StandardLoggingPayload] = None,
451+
) -> str:
452+
"""
453+
Get the datadog tags for the request
454+
455+
DD tags need to be as follows:
456+
- tags: ["user_handle:[email protected]", "app_version:1.0.0"]
457+
"""
458+
base_tags = {
459+
"env": os.getenv("DD_ENV", "unknown"),
460+
"service": os.getenv("DD_SERVICE", "litellm"),
461+
"version": os.getenv("DD_VERSION", "unknown"),
462+
"HOSTNAME": DataDogLogger._get_datadog_hostname(),
463+
"POD_NAME": os.getenv("POD_NAME", "unknown"),
464+
}
465+
466+
tags = [f"{k}:{v}" for k, v in base_tags.items()]
467+
468+
if standard_logging_object:
469+
_request_tags: List[str] = (
470+
standard_logging_object.get("request_tags", []) or []
471+
)
472+
request_tags = [f"request_tag:{tag}" for tag in _request_tags]
473+
tags.extend(request_tags)
474+
475+
return ",".join(tags)
449476

450477
@staticmethod
451478
def _get_datadog_source():

litellm/integrations/datadog/datadog_llm_obs.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ def create_llm_obs_payload(
159159
start_ns=int(start_time.timestamp() * 1e9),
160160
duration=int((end_time - start_time).total_seconds() * 1e9),
161161
metrics=metrics,
162+
tags=[
163+
self._get_datadog_tags(standard_logging_object=standard_logging_payload)
164+
],
162165
)
163166

164167
def _get_response_messages(self, response_obj: Any) -> List[Any]:

litellm/proxy/proxy_config.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ model_list:
1919

2020
general_settings:
2121
store_prompts_in_spend_logs: true
22+
23+
litellm_settings:
24+
callbacks: ["datadog_llm_observability"]

litellm/types/integrations/datadog_llm_obs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class LLMObsPayload(TypedDict):
4040
start_ns: int
4141
duration: int
4242
metrics: LLMMetrics
43+
tags: List
4344

4445

4546
class DDSpanAttributes(TypedDict):

tests/logging_callback_tests/test_datadog.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,3 +532,48 @@ async def test_datadog_non_serializable_messages():
532532
# Check that the non-serializable objects were converted to strings
533533
assert isinstance(dict_payload["messages"][0]["content"], str)
534534
assert isinstance(dict_payload["response"]["choices"][0]["message"]["content"], str)
535+
536+
537+
def test_get_datadog_tags():
538+
"""Test the _get_datadog_tags static method with various inputs"""
539+
# Test with no standard_logging_object and default env vars
540+
base_tags = DataDogLogger._get_datadog_tags()
541+
assert "env:" in base_tags
542+
assert "service:" in base_tags
543+
assert "version:" in base_tags
544+
assert "POD_NAME:" in base_tags
545+
assert "HOSTNAME:" in base_tags
546+
547+
# Test with custom env vars
548+
test_env = {
549+
"DD_ENV": "production",
550+
"DD_SERVICE": "custom-service",
551+
"DD_VERSION": "1.0.0",
552+
"HOSTNAME": "test-host",
553+
"POD_NAME": "pod-123",
554+
}
555+
with patch.dict(os.environ, test_env):
556+
custom_tags = DataDogLogger._get_datadog_tags()
557+
assert "env:production" in custom_tags
558+
assert "service:custom-service" in custom_tags
559+
assert "version:1.0.0" in custom_tags
560+
assert "HOSTNAME:test-host" in custom_tags
561+
assert "POD_NAME:pod-123" in custom_tags
562+
563+
# Test with standard_logging_object containing request_tags
564+
standard_logging_obj = create_standard_logging_payload()
565+
standard_logging_obj["request_tags"] = ["tag1", "tag2"]
566+
567+
tags_with_request = DataDogLogger._get_datadog_tags(standard_logging_obj)
568+
assert "request_tag:tag1" in tags_with_request
569+
assert "request_tag:tag2" in tags_with_request
570+
571+
# Test with empty request_tags
572+
standard_logging_obj["request_tags"] = []
573+
tags_empty_request = DataDogLogger._get_datadog_tags(standard_logging_obj)
574+
assert "request_tag:" not in tags_empty_request
575+
576+
# Test with None request_tags
577+
standard_logging_obj["request_tags"] = None
578+
tags_none_request = DataDogLogger._get_datadog_tags(standard_logging_obj)
579+
assert "request_tag:" not in tags_none_request

0 commit comments

Comments
 (0)