Skip to content

Commit

Permalink
(Feat) datadog_llm_observability callback - emit request_tags on …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
ishaan-jaff authored Jan 21, 2025
1 parent 4b88635 commit 806df5d
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 3 deletions.
33 changes: 30 additions & 3 deletions litellm/integrations/datadog/datadog.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,9 @@ def create_datadog_logging_payload(

dd_payload = DatadogPayload(
ddsource=self._get_datadog_source(),
ddtags=self._get_datadog_tags(),
ddtags=self._get_datadog_tags(
standard_logging_object=standard_logging_object
),
hostname=self._get_datadog_hostname(),
message=json_payload,
service=self._get_datadog_service(),
Expand Down Expand Up @@ -444,8 +446,33 @@ def _create_v0_logging_payload(
return dd_payload

@staticmethod
def _get_datadog_tags():
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')}"
def _get_datadog_tags(
standard_logging_object: Optional[StandardLoggingPayload] = None,
) -> str:
"""
Get the datadog tags for the request
DD tags need to be as follows:
- tags: ["user_handle:[email protected]", "app_version:1.0.0"]
"""
base_tags = {
"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"),
}

tags = [f"{k}:{v}" for k, v in base_tags.items()]

if standard_logging_object:
_request_tags: List[str] = (
standard_logging_object.get("request_tags", []) or []
)
request_tags = [f"request_tag:{tag}" for tag in _request_tags]
tags.extend(request_tags)

return ",".join(tags)

@staticmethod
def _get_datadog_source():
Expand Down
3 changes: 3 additions & 0 deletions litellm/integrations/datadog/datadog_llm_obs.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ def create_llm_obs_payload(
start_ns=int(start_time.timestamp() * 1e9),
duration=int((end_time - start_time).total_seconds() * 1e9),
metrics=metrics,
tags=[
self._get_datadog_tags(standard_logging_object=standard_logging_payload)
],
)

def _get_response_messages(self, response_obj: Any) -> List[Any]:
Expand Down
3 changes: 3 additions & 0 deletions litellm/proxy/proxy_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ model_list:

general_settings:
store_prompts_in_spend_logs: true

litellm_settings:
callbacks: ["datadog_llm_observability"]
1 change: 1 addition & 0 deletions litellm/types/integrations/datadog_llm_obs.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class LLMObsPayload(TypedDict):
start_ns: int
duration: int
metrics: LLMMetrics
tags: List


class DDSpanAttributes(TypedDict):
Expand Down
45 changes: 45 additions & 0 deletions tests/logging_callback_tests/test_datadog.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,3 +532,48 @@ async def test_datadog_non_serializable_messages():
# Check that the non-serializable objects were converted to strings
assert isinstance(dict_payload["messages"][0]["content"], str)
assert isinstance(dict_payload["response"]["choices"][0]["message"]["content"], str)


def test_get_datadog_tags():
"""Test the _get_datadog_tags static method with various inputs"""
# Test with no standard_logging_object and default env vars
base_tags = DataDogLogger._get_datadog_tags()
assert "env:" in base_tags
assert "service:" in base_tags
assert "version:" in base_tags
assert "POD_NAME:" in base_tags
assert "HOSTNAME:" in base_tags

# Test with custom env vars
test_env = {
"DD_ENV": "production",
"DD_SERVICE": "custom-service",
"DD_VERSION": "1.0.0",
"HOSTNAME": "test-host",
"POD_NAME": "pod-123",
}
with patch.dict(os.environ, test_env):
custom_tags = DataDogLogger._get_datadog_tags()
assert "env:production" in custom_tags
assert "service:custom-service" in custom_tags
assert "version:1.0.0" in custom_tags
assert "HOSTNAME:test-host" in custom_tags
assert "POD_NAME:pod-123" in custom_tags

# Test with standard_logging_object containing request_tags
standard_logging_obj = create_standard_logging_payload()
standard_logging_obj["request_tags"] = ["tag1", "tag2"]

tags_with_request = DataDogLogger._get_datadog_tags(standard_logging_obj)
assert "request_tag:tag1" in tags_with_request
assert "request_tag:tag2" in tags_with_request

# Test with empty request_tags
standard_logging_obj["request_tags"] = []
tags_empty_request = DataDogLogger._get_datadog_tags(standard_logging_obj)
assert "request_tag:" not in tags_empty_request

# Test with None request_tags
standard_logging_obj["request_tags"] = None
tags_none_request = DataDogLogger._get_datadog_tags(standard_logging_obj)
assert "request_tag:" not in tags_none_request

0 comments on commit 806df5d

Please sign in to comment.