@@ -48,28 +48,43 @@ def decode_logs_request(request_body: bytes):
4848 return export_request
4949
5050
51- def extract_log_correlation_attributes (captured_logs , log_message : str ):
52- """Extract log correlation attributes from captured logs."""
53- attributes = {}
51+ def find_log_record_by_message (captured_logs , log_message : str ):
52+ """Find a log record and its resource by matching log message content."""
5453 for resource_logs in captured_logs .resource_logs :
55- for attr in resource_logs .resource .attributes :
56- if attr .key == "service.name" :
57- attributes ["service" ] = attr .value .string_value
58- elif attr .key == "deployment.environment" :
59- attributes ["env" ] = attr .value .string_value
60- elif attr .key == "service.version" :
61- attributes ["version" ] = attr .value .string_value
62- elif attr .key == "host.name" :
63- attributes ["host_name" ] = attr .value .string_value
6454 for scope_logs in resource_logs .scope_logs :
6555 for record in scope_logs .log_records :
6656 if log_message in record .body .string_value :
67- attributes ["trace_id" ] = record .trace_id .hex ()
68- attributes ["span_id" ] = record .span_id .hex ()
69- break
57+ return record , resource_logs .resource
58+ return None , None
59+
60+
61+ def extract_resource_attributes (log_record , resource ) -> dict :
62+ """Extract resource attributes from log record and resource."""
63+ attributes = {}
64+
65+ for attr in resource .attributes :
66+ if attr .key == "service.name" :
67+ attributes ["service" ] = attr .value .string_value
68+ elif attr .key == "deployment.environment" :
69+ attributes ["env" ] = attr .value .string_value
70+ elif attr .key == "service.version" :
71+ attributes ["version" ] = attr .value .string_value
72+ elif attr .key == "host.name" :
73+ attributes ["host_name" ] = attr .value .string_value
74+
75+ if log_record :
76+ attributes ["trace_id" ] = log_record .trace_id .hex ()
77+ attributes ["span_id" ] = log_record .span_id .hex ()
78+
7079 return attributes
7180
7281
82+ def extract_log_correlation_attributes (captured_logs , log_message : str ) -> dict :
83+ """Extract log correlation attributes and trace/span IDs from captured logs."""
84+ log_record , resource = find_log_record_by_message (captured_logs , log_message )
85+ return extract_resource_attributes (log_record , resource )
86+
87+
7388@pytest .mark .skipif (API_VERSION >= (1 , 15 , 0 ), reason = "OpenTelemetry API >= 1.15.0 supports logs collection" )
7489def test_otel_api_version_not_supported (ddtrace_run_python_code_in_subprocess ):
7590 """Test error when OpenTelemetry API version is too old."""
@@ -294,6 +309,65 @@ def test_otel_logs_exporter_auto_configured_grpc():
294309 ), "Expected log message not found in exported gRPC payload"
295310
296311
312+ @pytest .mark .skipif (
313+ EXPORTER_VERSION < MINIMUM_SUPPORTED_VERSION ,
314+ reason = f"OpenTelemetry exporter version { MINIMUM_SUPPORTED_VERSION } is required to export logs" ,
315+ )
316+ @pytest .mark .subprocess (
317+ ddtrace_run = True ,
318+ env = {
319+ "DD_LOGS_OTEL_ENABLED" : "true" ,
320+ "DD_SERVICE" : "test_service" ,
321+ "DD_VERSION" : "1.0" ,
322+ "DD_ENV" : "test_env" ,
323+ },
324+ parametrize = {"DD_LOGS_INJECTION" : [None , "true" ]},
325+ )
326+ def test_ddtrace_log_injection_otlp_enabled ():
327+ """Test that ddtrace log injection is disabled when OpenTelemetry logs are enabled."""
328+ from logging import getLogger
329+
330+ from opentelemetry ._logs import get_logger_provider
331+
332+ from ddtrace import tracer
333+ from ddtrace .internal .constants import LOG_ATTR_ENV
334+ from ddtrace .internal .constants import LOG_ATTR_SERVICE
335+ from ddtrace .internal .constants import LOG_ATTR_SPAN_ID
336+ from ddtrace .internal .constants import LOG_ATTR_TRACE_ID
337+ from ddtrace .internal .constants import LOG_ATTR_VERSION
338+ from tests .opentelemetry .test_logs import create_mock_grpc_server
339+ from tests .opentelemetry .test_logs import find_log_record_by_message
340+
341+ log = getLogger ()
342+ mock_service , server = create_mock_grpc_server ()
343+
344+ try :
345+ server .start ()
346+ with tracer .trace ("test_trace" ):
347+ log .error ("test_ddtrace_log_correlation" )
348+ logger_provider = get_logger_provider ()
349+ logger_provider .force_flush ()
350+ finally :
351+ server .stop (0 )
352+
353+ log_record = None
354+ for request in mock_service .received_requests :
355+ log_record , _ = find_log_record_by_message (request , "test_ddtrace_log_correlation" )
356+ if log_record :
357+ break
358+ else :
359+ assert False , f"No log record with message 'test_ddtrace_log_correlation' found in the request: { request } "
360+
361+ ddtrace_attributes = {}
362+ for attr in log_record .attributes :
363+ if attr .key in (LOG_ATTR_ENV , LOG_ATTR_SERVICE , LOG_ATTR_VERSION , LOG_ATTR_TRACE_ID , LOG_ATTR_SPAN_ID ):
364+ ddtrace_attributes [attr .key ] = attr .value
365+
366+ assert (
367+ ddtrace_attributes == {}
368+ ), f"Log Injection attributes should not be present in the log record: { ddtrace_attributes } "
369+
370+
297371@pytest .mark .skipif (
298372 EXPORTER_VERSION < MINIMUM_SUPPORTED_VERSION ,
299373 reason = f"OpenTelemetry exporter version { MINIMUM_SUPPORTED_VERSION } is required to export logs" ,
0 commit comments