2626from litellm import completion
2727from litellm ._logging import verbose_logger
2828from litellm .integrations .datadog .datadog import *
29+ import litellm .integrations .datadog .datadog as datadog_module
2930from datetime import datetime , timedelta
3031from litellm .types .utils import (
3132 StandardLoggingPayload ,
@@ -90,6 +91,24 @@ def create_standard_logging_payload() -> StandardLoggingPayload:
9091 )
9192
9293
94+ class _DummySpan :
95+ def __init__ (self , trace_id = None , span_id = None ):
96+ self .trace_id = trace_id
97+ self .span_id = span_id
98+
99+
100+ class _DummyTracer :
101+ def __init__ (self , current_span = None , current_root_span = None ):
102+ self ._current_span = current_span
103+ self ._current_root_span = current_root_span
104+
105+ def current_span (self ):
106+ return self ._current_span
107+
108+ def current_root_span (self ):
109+ return self ._current_root_span
110+
111+
93112@pytest .mark .asyncio
94113async def test_create_datadog_logging_payload ():
95114 """Test creating a DataDog logging payload from a standard logging object"""
@@ -219,20 +238,35 @@ async def test_datadog_logging_http_request():
219238
220239 # Get the expected fields and their types from DatadogPayload
221240 expected_fields = DatadogPayload .__annotations__
222- # Assert that all elements in body have the fields of DatadogPayload with correct types
241+ required_fields = {
242+ "ddsource" : str ,
243+ "ddtags" : str ,
244+ "hostname" : str ,
245+ "message" : str ,
246+ "service" : str ,
247+ "status" : str ,
248+ }
249+ optional_fields = set (expected_fields .keys ()) - set (required_fields .keys ())
250+
251+ # Assert that all elements in body have the required fields with correct types
223252 for log in body :
224253 assert isinstance (log , dict ), "Each log should be a dictionary"
225- for field , expected_type in expected_fields .items ():
254+ for field , expected_type in required_fields .items ():
226255 assert field in log , f"Field '{ field } ' is missing from the log"
227256 assert isinstance (
228257 log [field ], expected_type
229258 ), f"Field '{ field } ' has incorrect type. Expected { expected_type } , got { type (log [field ])} "
230259
231- # Additional assertion to ensure no extra fields are present
232- for log in body :
233- assert set (log .keys ()) == set (
234- expected_fields .keys ()
235- ), f"Log contains unexpected fields: { set (log .keys ()) - set (expected_fields .keys ())} "
260+ for optional_field in optional_fields :
261+ if optional_field in log :
262+ assert isinstance (
263+ log [optional_field ], str
264+ ), f"Optional field '{ optional_field } ' must be a string"
265+
266+ unexpected_fields = set (log .keys ()) - set (expected_fields .keys ())
267+ assert (
268+ not unexpected_fields
269+ ), f"Log contains unexpected fields: { unexpected_fields } "
236270
237271 # Parse the 'message' field as JSON and check its structure
238272 message = json .loads (body [0 ]["message" ])
@@ -256,6 +290,96 @@ async def test_datadog_logging_http_request():
256290 pytest .fail (f"Test failed with exception: { str (e )} " )
257291
258292
293+ @pytest .mark .asyncio
294+ async def test_add_trace_context_uses_current_span (monkeypatch ):
295+ monkeypatch .setenv ("DD_SITE" , "https://fake.datadoghq.com" )
296+ monkeypatch .setenv ("DD_API_KEY" , "anything" )
297+ tracer = _DummyTracer (current_span = _DummySpan (trace_id = 123 , span_id = 456 ))
298+ monkeypatch .setattr (datadog_module , "tracer" , tracer )
299+
300+ dd_logger = DataDogLogger ()
301+ payload = DatadogPayload (
302+ ddsource = "litellm" ,
303+ ddtags = "env:test" ,
304+ hostname = "host" ,
305+ message = "{}" ,
306+ service = "svc" ,
307+ status = "info" ,
308+ )
309+
310+ dd_logger ._add_trace_context_to_payload (payload )
311+ assert payload ["dd.trace_id" ] == "123"
312+ assert payload ["dd.span_id" ] == "456"
313+
314+
315+ @pytest .mark .asyncio
316+ async def test_add_trace_context_falls_back_to_root_span (monkeypatch ):
317+ monkeypatch .setenv ("DD_SITE" , "https://fake.datadoghq.com" )
318+ monkeypatch .setenv ("DD_API_KEY" , "anything" )
319+ tracer = _DummyTracer (
320+ current_span = None ,
321+ current_root_span = _DummySpan (trace_id = 789 , span_id = None ),
322+ )
323+ monkeypatch .setattr (datadog_module , "tracer" , tracer )
324+
325+ dd_logger = DataDogLogger ()
326+ payload = DatadogPayload (
327+ ddsource = "litellm" ,
328+ ddtags = "env:test" ,
329+ hostname = "host" ,
330+ message = "{}" ,
331+ service = "svc" ,
332+ status = "info" ,
333+ )
334+
335+ dd_logger ._add_trace_context_to_payload (payload )
336+ assert payload ["dd.trace_id" ] == "789"
337+ assert "dd.span_id" not in payload
338+
339+
340+ @pytest .mark .asyncio
341+ async def test_add_trace_context_handles_missing_tracer (monkeypatch ):
342+ monkeypatch .setenv ("DD_SITE" , "https://fake.datadoghq.com" )
343+ monkeypatch .setenv ("DD_API_KEY" , "anything" )
344+ monkeypatch .setattr (datadog_module , "tracer" , object ())
345+
346+ dd_logger = DataDogLogger ()
347+ payload = DatadogPayload (
348+ ddsource = "litellm" ,
349+ ddtags = "env:test" ,
350+ hostname = "host" ,
351+ message = "{}" ,
352+ service = "svc" ,
353+ status = "info" ,
354+ )
355+
356+ dd_logger ._add_trace_context_to_payload (payload )
357+ assert "dd.trace_id" not in payload
358+ assert "dd.span_id" not in payload
359+
360+
361+ @pytest .mark .asyncio
362+ async def test_add_trace_context_ignores_span_without_trace_id (monkeypatch ):
363+ monkeypatch .setenv ("DD_SITE" , "https://fake.datadoghq.com" )
364+ monkeypatch .setenv ("DD_API_KEY" , "anything" )
365+ tracer = _DummyTracer (current_span = _DummySpan (trace_id = None , span_id = 555 ))
366+ monkeypatch .setattr (datadog_module , "tracer" , tracer )
367+
368+ dd_logger = DataDogLogger ()
369+ payload = DatadogPayload (
370+ ddsource = "litellm" ,
371+ ddtags = "env:test" ,
372+ hostname = "host" ,
373+ message = "{}" ,
374+ service = "svc" ,
375+ status = "info" ,
376+ )
377+
378+ dd_logger ._add_trace_context_to_payload (payload )
379+ assert "dd.trace_id" not in payload
380+ assert "dd.span_id" not in payload
381+
382+
259383@pytest .mark .asyncio
260384async def test_datadog_log_redis_failures ():
261385 """
@@ -701,4 +825,4 @@ def test_datadog_ignores_ddtrace_agent_host():
701825 )
702826
703827 # Verify API key is set correctly
704- assert dd_logger .DD_API_KEY == "fake-api-key"
828+ assert dd_logger .DD_API_KEY == "fake-api-key"
0 commit comments