@@ -48,28 +48,43 @@ def decode_logs_request(request_body: bytes):
48
48
return export_request
49
49
50
50
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."""
54
53
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
64
54
for scope_logs in resource_logs .scope_logs :
65
55
for record in scope_logs .log_records :
66
56
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
+
70
79
return attributes
71
80
72
81
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
+
73
88
@pytest .mark .skipif (API_VERSION >= (1 , 15 , 0 ), reason = "OpenTelemetry API >= 1.15.0 supports logs collection" )
74
89
def test_otel_api_version_not_supported (ddtrace_run_python_code_in_subprocess ):
75
90
"""Test error when OpenTelemetry API version is too old."""
@@ -294,6 +309,65 @@ def test_otel_logs_exporter_auto_configured_grpc():
294
309
), "Expected log message not found in exported gRPC payload"
295
310
296
311
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
+
297
371
@pytest .mark .skipif (
298
372
EXPORTER_VERSION < MINIMUM_SUPPORTED_VERSION ,
299
373
reason = f"OpenTelemetry exporter version { MINIMUM_SUPPORTED_VERSION } is required to export logs" ,
0 commit comments