Skip to content

Commit ee1b008

Browse files
OTLP exporter is encoding invalid span/trace IDs in the logs fix (#4006)
1 parent 61ea97d commit ee1b008

File tree

5 files changed

+158
-4
lines changed

5 files changed

+158
-4
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
- OTLP exporter is encoding invalid span/trace IDs in the logs fix
11+
([#4006](https://github.com/open-telemetry/opentelemetry-python/pull/4006))
1012
- Update sdk process resource detector `process.command_args` attribute to also include the executable itself
1113
([#4032](https://github.com/open-telemetry/opentelemetry-python/pull/4032))
1214
- Fix `start_time_unix_nano` for delta collection for explicit bucket histogram aggregation

exporter/opentelemetry-exporter-otlp-proto-common/src/opentelemetry/exporter/otlp/proto/common/_internal/_log_encoder/__init__.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,21 @@ def encode_logs(batch: Sequence[LogData]) -> ExportLogsServiceRequest:
3939

4040

4141
def _encode_log(log_data: LogData) -> PB2LogRecord:
42+
span_id = (
43+
None
44+
if log_data.log_record.span_id == 0
45+
else _encode_span_id(log_data.log_record.span_id)
46+
)
47+
trace_id = (
48+
None
49+
if log_data.log_record.trace_id == 0
50+
else _encode_trace_id(log_data.log_record.trace_id)
51+
)
4252
return PB2LogRecord(
4353
time_unix_nano=log_data.log_record.timestamp,
4454
observed_time_unix_nano=log_data.log_record.observed_timestamp,
45-
span_id=_encode_span_id(log_data.log_record.span_id),
46-
trace_id=_encode_trace_id(log_data.log_record.trace_id),
55+
span_id=span_id,
56+
trace_id=trace_id,
4757
flags=int(log_data.log_record.trace_flags),
4858
body=_encode_value(log_data.log_record.body),
4959
severity_text=log_data.log_record.severity_text,

exporter/opentelemetry-exporter-otlp-proto-common/tests/test_log_encoder.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,8 @@ def get_test_logs(
239239
PB2LogRecord(
240240
time_unix_nano=1644650249738562048,
241241
observed_time_unix_nano=1644650249738562049,
242-
trace_id=_encode_trace_id(0),
243-
span_id=_encode_span_id(0),
242+
trace_id=None,
243+
span_id=None,
244244
flags=int(TraceFlags.DEFAULT),
245245
severity_text="WARN",
246246
severity_number=SeverityNumber.WARN.value,

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py

+68
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from google.protobuf.duration_pb2 import ( # pylint: disable=no-name-in-module
2222
Duration,
2323
)
24+
from google.protobuf.json_format import MessageToDict
2425
from google.rpc.error_details_pb2 import RetryInfo
2526
from grpc import ChannelCredentials, Compression, StatusCode, server
2627

@@ -167,6 +168,36 @@ def setUp(self):
167168
"third_name", "third_version"
168169
),
169170
)
171+
self.log_data_4 = LogData(
172+
log_record=LogRecord(
173+
timestamp=int(time.time() * 1e9),
174+
trace_id=0,
175+
span_id=5213367945872657629,
176+
trace_flags=TraceFlags(0x01),
177+
severity_text="ERROR",
178+
severity_number=SeverityNumber.WARN,
179+
body="Invalid trace id check",
180+
resource=SDKResource({"service": "myapp"}),
181+
),
182+
instrumentation_scope=InstrumentationScope(
183+
"fourth_name", "fourth_version"
184+
),
185+
)
186+
self.log_data_5 = LogData(
187+
log_record=LogRecord(
188+
timestamp=int(time.time() * 1e9),
189+
trace_id=2604504634922341076776623263868986801,
190+
span_id=0,
191+
trace_flags=TraceFlags(0x01),
192+
severity_text="ERROR",
193+
severity_number=SeverityNumber.WARN,
194+
body="Invalid span id check",
195+
resource=SDKResource({"service": "myapp"}),
196+
),
197+
instrumentation_scope=InstrumentationScope(
198+
"fifth_name", "fifth_version"
199+
),
200+
)
170201

171202
def tearDown(self):
172203
self.server.stop(None)
@@ -342,6 +373,43 @@ def test_failure(self):
342373
self.exporter.export([self.log_data_1]), LogExportResult.FAILURE
343374
)
344375

376+
def export_log_and_deserialize(self, log_data):
377+
# pylint: disable=protected-access
378+
translated_data = self.exporter._translate_data([log_data])
379+
request_dict = MessageToDict(translated_data)
380+
log_records = (
381+
request_dict.get("resourceLogs")[0]
382+
.get("scopeLogs")[0]
383+
.get("logRecords")
384+
)
385+
return log_records
386+
387+
def test_exported_log_without_trace_id(self):
388+
log_records = self.export_log_and_deserialize(self.log_data_4)
389+
if log_records:
390+
log_record = log_records[0]
391+
self.assertIn("spanId", log_record)
392+
self.assertNotIn(
393+
"traceId",
394+
log_record,
395+
"traceId should not be present in the log record",
396+
)
397+
else:
398+
self.fail("No log records found")
399+
400+
def test_exported_log_without_span_id(self):
401+
log_records = self.export_log_and_deserialize(self.log_data_5)
402+
if log_records:
403+
log_record = log_records[0]
404+
self.assertIn("traceId", log_record)
405+
self.assertNotIn(
406+
"spanId",
407+
log_record,
408+
"spanId should not be present in the log record",
409+
)
410+
else:
411+
self.fail("No log records found")
412+
345413
def test_translate_log_data(self):
346414

347415
expected = ExportLogsServiceRequest(

exporter/opentelemetry-exporter-otlp-proto-http/tests/test_proto_log_exporter.py

+74
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import requests
2222
import responses
23+
from google.protobuf.json_format import MessageToDict
2324

2425
from opentelemetry._logs import SeverityNumber
2526
from opentelemetry.exporter.otlp.proto.http import Compression
@@ -31,6 +32,9 @@
3132
OTLPLogExporter,
3233
)
3334
from opentelemetry.exporter.otlp.proto.http.version import __version__
35+
from opentelemetry.proto.collector.logs.v1.logs_service_pb2 import (
36+
ExportLogsServiceRequest,
37+
)
3438
from opentelemetry.sdk._logs import LogData
3539
from opentelemetry.sdk._logs import LogRecord as SDKLogRecord
3640
from opentelemetry.sdk._logs.export import LogExportResult
@@ -167,6 +171,76 @@ def test_exporter_env(self):
167171
)
168172
self.assertIsInstance(exporter._session, requests.Session)
169173

174+
@staticmethod
175+
def export_log_and_deserialize(log):
176+
with patch("requests.Session.post") as mock_post:
177+
exporter = OTLPLogExporter()
178+
exporter.export([log])
179+
request_body = mock_post.call_args[1]["data"]
180+
request = ExportLogsServiceRequest()
181+
request.ParseFromString(request_body)
182+
request_dict = MessageToDict(request)
183+
log_records = (
184+
request_dict.get("resourceLogs")[0]
185+
.get("scopeLogs")[0]
186+
.get("logRecords")
187+
)
188+
return log_records
189+
190+
def test_exported_log_without_trace_id(self):
191+
log = LogData(
192+
log_record=SDKLogRecord(
193+
timestamp=1644650195189786182,
194+
trace_id=0,
195+
span_id=1312458408527513292,
196+
trace_flags=TraceFlags(0x01),
197+
severity_text="WARN",
198+
severity_number=SeverityNumber.WARN,
199+
body="Invalid trace id check",
200+
resource=SDKResource({"first_resource": "value"}),
201+
attributes={"a": 1, "b": "c"},
202+
),
203+
instrumentation_scope=InstrumentationScope("name", "version"),
204+
)
205+
log_records = TestOTLPHTTPLogExporter.export_log_and_deserialize(log)
206+
if log_records:
207+
log_record = log_records[0]
208+
self.assertIn("spanId", log_record)
209+
self.assertNotIn(
210+
"traceId",
211+
log_record,
212+
"trace_id should not be present in the log record",
213+
)
214+
else:
215+
self.fail("No log records found")
216+
217+
def test_exported_log_without_span_id(self):
218+
log = LogData(
219+
log_record=SDKLogRecord(
220+
timestamp=1644650195189786360,
221+
trace_id=89564621134313219400156819398935297696,
222+
span_id=0,
223+
trace_flags=TraceFlags(0x01),
224+
severity_text="WARN",
225+
severity_number=SeverityNumber.WARN,
226+
body="Invalid span id check",
227+
resource=SDKResource({"first_resource": "value"}),
228+
attributes={"a": 1, "b": "c"},
229+
),
230+
instrumentation_scope=InstrumentationScope("name", "version"),
231+
)
232+
log_records = TestOTLPHTTPLogExporter.export_log_and_deserialize(log)
233+
if log_records:
234+
log_record = log_records[0]
235+
self.assertIn("traceId", log_record)
236+
self.assertNotIn(
237+
"spanId",
238+
log_record,
239+
"spanId should not be present in the log record",
240+
)
241+
else:
242+
self.fail("No log records found")
243+
170244
@responses.activate
171245
@patch("opentelemetry.exporter.otlp.proto.http._log_exporter.sleep")
172246
def test_exponential_backoff(self, mock_sleep):

0 commit comments

Comments
 (0)