diff --git a/CHANGELOG.md b/CHANGELOG.md index 750e9cc11a4..f6e0aba9033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `BoundedAttributes` to the API to make it available for `Link` which is defined in the API. Marked `BoundedDict` in the SDK as deprecated as a result. ([#1915](https://github.com/open-telemetry/opentelemetry-python/pull/1915)) +- Fix OTLP SpanExporter to distinguish spans based off Resource and InstrumentationInfo + ([#1927](https://github.com/open-telemetry/opentelemetry-python/pull/1927)) ### Fixed - Updated `opentelementry-opentracing-shim` `ScopeShim` to report exceptions in diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py index 7b9227fd07f..ba88dc1d7d8 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py @@ -157,9 +157,9 @@ def get_resource_data( resource_class( **{ "resource": collector_resource, - "instrumentation_library_{}".format(name): [ - instrumentation_library_data - ], + "instrumentation_library_{}".format( + name + ): instrumentation_library_data.values(), } ) ) diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py index cbaec907e5e..8fa98feb361 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py @@ -238,29 +238,42 @@ def _translate_data( sdk_resource_instrumentation_library_spans = {} for sdk_span in data: - - if sdk_span.resource not in ( - sdk_resource_instrumentation_library_spans.keys() - ): + instrumentation_library_spans_map = ( + sdk_resource_instrumentation_library_spans.get( + sdk_span.resource, {} + ) + ) + # If we haven't seen the Resource yet, add it to the map + if not instrumentation_library_spans_map: + sdk_resource_instrumentation_library_spans[ + sdk_span.resource + ] = instrumentation_library_spans_map + instrumentation_library_spans = ( + instrumentation_library_spans_map.get( + sdk_span.instrumentation_info + ) + ) + # If we haven't seen the InstrumentationInfo for this Resource yet, add it to the map + if not instrumentation_library_spans: if sdk_span.instrumentation_info is not None: - instrumentation_library_spans = ( - InstrumentationLibrarySpans( - instrumentation_library=InstrumentationLibrary( - name=sdk_span.instrumentation_info.name, - version=sdk_span.instrumentation_info.version, - ) + instrumentation_library_spans_map[ + sdk_span.instrumentation_info + ] = InstrumentationLibrarySpans( + instrumentation_library=InstrumentationLibrary( + name=sdk_span.instrumentation_info.name, + version=sdk_span.instrumentation_info.version, ) ) - else: - instrumentation_library_spans = ( - InstrumentationLibrarySpans() - ) - - sdk_resource_instrumentation_library_spans[ - sdk_span.resource - ] = instrumentation_library_spans - + # If no InstrumentationInfo, store in None key + instrumentation_library_spans_map[ + sdk_span.instrumentation_info + ] = InstrumentationLibrarySpans() + instrumentation_library_spans = ( + instrumentation_library_spans_map.get( + sdk_span.instrumentation_info + ) + ) self._collector_span_kwargs = {} self._translate_name(sdk_span) @@ -292,9 +305,9 @@ def _translate_data( "SPAN_KIND_{}".format(sdk_span.kind.name), ) - sdk_resource_instrumentation_library_spans[ - sdk_span.resource - ].spans.append(CollectorSpan(**self._collector_span_kwargs)) + instrumentation_library_spans.spans.append( + CollectorSpan(**self._collector_span_kwargs) + ) return ExportTraceServiceRequest( resource_spans=get_resource_data( diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py index 09dbda16825..1a244b00674 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py +++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py @@ -176,8 +176,44 @@ def setUp(self): ), ) + self.span2 = _Span( + "b", + context=Mock( + **{ + "trace_state": OrderedDict([("a", "b"), ("c", "d")]), + "span_id": 10217189687419569865, + "trace_id": 67545097771067222548457157018666467027, + } + ), + resource=SDKResource(OrderedDict([("a", 2), ("b", False)])), + parent=Mock(**{"span_id": 12345}), + instrumentation_info=InstrumentationInfo( + name="name", version="version" + ), + ) + + self.span3 = _Span( + "c", + context=Mock( + **{ + "trace_state": OrderedDict([("a", "b"), ("c", "d")]), + "span_id": 10217189687419569865, + "trace_id": 67545097771067222548457157018666467027, + } + ), + resource=SDKResource(OrderedDict([("a", 1), ("b", False)])), + parent=Mock(**{"span_id": 12345}), + instrumentation_info=InstrumentationInfo( + name="name2", version="version2" + ), + ) + self.span.start() self.span.end() + self.span2.start() + self.span2.end() + self.span3.start() + self.span3.end() def tearDown(self): self.server.stop(None) @@ -505,6 +541,181 @@ def test_translate_spans(self): # pylint: disable=protected-access self.assertEqual(expected, self.exporter._translate_data([self.span])) + def test_translate_spans_multi(self): + expected = ExportTraceServiceRequest( + resource_spans=[ + ResourceSpans( + resource=OTLPResource( + attributes=[ + KeyValue(key="a", value=AnyValue(int_value=1)), + KeyValue( + key="b", value=AnyValue(bool_value=False) + ), + ] + ), + instrumentation_library_spans=[ + InstrumentationLibrarySpans( + instrumentation_library=InstrumentationLibrary( + name="name", version="version" + ), + spans=[ + OTLPSpan( + # pylint: disable=no-member + name="a", + start_time_unix_nano=self.span.start_time, + end_time_unix_nano=self.span.end_time, + trace_state="a=b,c=d", + span_id=int.to_bytes( + 10217189687419569865, 8, "big" + ), + trace_id=int.to_bytes( + 67545097771067222548457157018666467027, + 16, + "big", + ), + parent_span_id=( + b"\000\000\000\000\000\00009" + ), + kind=( + OTLPSpan.SpanKind.SPAN_KIND_INTERNAL + ), + attributes=[ + KeyValue( + key="a", + value=AnyValue(int_value=1), + ), + KeyValue( + key="b", + value=AnyValue(bool_value=True), + ), + ], + events=[ + OTLPSpan.Event( + name="a", + time_unix_nano=1591240820506462784, + attributes=[ + KeyValue( + key="a", + value=AnyValue( + int_value=1 + ), + ), + KeyValue( + key="b", + value=AnyValue( + bool_value=False + ), + ), + ], + ) + ], + status=Status(code=0, message=""), + links=[ + OTLPSpan.Link( + trace_id=int.to_bytes( + 1, 16, "big" + ), + span_id=int.to_bytes(2, 8, "big"), + attributes=[ + KeyValue( + key="a", + value=AnyValue( + int_value=1 + ), + ), + KeyValue( + key="b", + value=AnyValue( + bool_value=False + ), + ), + ], + ) + ], + ) + ], + ), + InstrumentationLibrarySpans( + instrumentation_library=InstrumentationLibrary( + name="name2", version="version2" + ), + spans=[ + OTLPSpan( + # pylint: disable=no-member + name="c", + start_time_unix_nano=self.span3.start_time, + end_time_unix_nano=self.span3.end_time, + trace_state="a=b,c=d", + span_id=int.to_bytes( + 10217189687419569865, 8, "big" + ), + trace_id=int.to_bytes( + 67545097771067222548457157018666467027, + 16, + "big", + ), + parent_span_id=( + b"\000\000\000\000\000\00009" + ), + kind=( + OTLPSpan.SpanKind.SPAN_KIND_INTERNAL + ), + status=Status(code=0, message=""), + ) + ], + ), + ], + ), + ResourceSpans( + resource=OTLPResource( + attributes=[ + KeyValue(key="a", value=AnyValue(int_value=2)), + KeyValue( + key="b", value=AnyValue(bool_value=False) + ), + ] + ), + instrumentation_library_spans=[ + InstrumentationLibrarySpans( + instrumentation_library=InstrumentationLibrary( + name="name", version="version" + ), + spans=[ + OTLPSpan( + # pylint: disable=no-member + name="b", + start_time_unix_nano=self.span2.start_time, + end_time_unix_nano=self.span2.end_time, + trace_state="a=b,c=d", + span_id=int.to_bytes( + 10217189687419569865, 8, "big" + ), + trace_id=int.to_bytes( + 67545097771067222548457157018666467027, + 16, + "big", + ), + parent_span_id=( + b"\000\000\000\000\000\00009" + ), + kind=( + OTLPSpan.SpanKind.SPAN_KIND_INTERNAL + ), + status=Status(code=0, message=""), + ) + ], + ) + ], + ), + ] + ) + + # pylint: disable=protected-access + self.assertEqual( + expected, + self.exporter._translate_data([self.span, self.span2, self.span3]), + ) + def _check_translated_status( self, translated: ExportTraceServiceRequest,