From a756db02c04dae95fdf3239cc4ceaa2c65006a9e Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Sun, 1 Nov 2020 21:46:03 -0800 Subject: [PATCH 01/12] Zipkin exporter v2 api support for protobuf format --- .../opentelemetry-exporter-zipkin/setup.cfg | 1 + .../opentelemetry/exporter/zipkin/__init__.py | 125 ++++- .../exporter/zipkin/zipkin_pb2.py | 458 ++++++++++++++++++ .../exporter/zipkin/zipkin_pb2.pyi | 214 ++++++++ .../tests/test_zipkin_exporter.py | 275 ++++++++++- 5 files changed, 1051 insertions(+), 22 deletions(-) create mode 100644 exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.py create mode 100644 exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.pyi diff --git a/exporter/opentelemetry-exporter-zipkin/setup.cfg b/exporter/opentelemetry-exporter-zipkin/setup.cfg index a071288c14..189c4a817e 100644 --- a/exporter/opentelemetry-exporter-zipkin/setup.cfg +++ b/exporter/opentelemetry-exporter-zipkin/setup.cfg @@ -39,6 +39,7 @@ package_dir= =src packages=find_namespace: install_requires = + protobuf == 3.12.2 requests ~= 2.7 opentelemetry-api == 0.15.dev0 opentelemetry-sdk == 0.15.dev0 diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py index bd3cdbb26a..05ee0c06b5 100644 --- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py @@ -64,25 +64,37 @@ import json import logging import os -from typing import Optional, Sequence +from typing import Optional, Sequence, Union from urllib.parse import urlparse import requests +from opentelemetry.exporter.zipkin import zipkin_pb2 from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult from opentelemetry.trace import Span, SpanContext, SpanKind +TRANSPORT_FORMAT_JSON = "application/json" +TRANSPORT_FORMAT_PROTOBUF = "application/x-protobuf" + DEFAULT_RETRY = False DEFAULT_URL = "http://localhost:9411/api/v2/spans" DEFAULT_MAX_TAG_VALUE_LENGTH = 128 -ZIPKIN_HEADERS = {"Content-Type": "application/json"} SPAN_KIND_MAP = { - SpanKind.INTERNAL: None, - SpanKind.SERVER: "SERVER", - SpanKind.CLIENT: "CLIENT", - SpanKind.PRODUCER: "PRODUCER", - SpanKind.CONSUMER: "CONSUMER", + TRANSPORT_FORMAT_JSON: { + SpanKind.INTERNAL: None, + SpanKind.SERVER: "SERVER", + SpanKind.CLIENT: "CLIENT", + SpanKind.PRODUCER: "PRODUCER", + SpanKind.CONSUMER: "CONSUMER", + }, + TRANSPORT_FORMAT_PROTOBUF: { + SpanKind.INTERNAL: zipkin_pb2.Span.Kind.SPAN_KIND_UNSPECIFIED, + SpanKind.SERVER: zipkin_pb2.Span.Kind.SERVER, + SpanKind.CLIENT: zipkin_pb2.Span.Kind.CLIENT, + SpanKind.PRODUCER: zipkin_pb2.Span.Kind.PRODUCER, + SpanKind.CONSUMER: zipkin_pb2.Span.Kind.CONSUMER, + } } SUCCESS_STATUS_CODES = (200, 202) @@ -100,6 +112,7 @@ class ZipkinSpanExporter(SpanExporter): ipv4: Primary IPv4 address associated with this connection. ipv6: Primary IPv6 address associated with this connection. retry: Set to True to configure the exporter to retry on failure. + transport_format: transport interchange format to use """ def __init__( @@ -110,6 +123,7 @@ def __init__( ipv6: Optional[str] = None, retry: Optional[str] = DEFAULT_RETRY, max_tag_value_length: Optional[int] = DEFAULT_MAX_TAG_VALUE_LENGTH, + transport_format: Union[TRANSPORT_FORMAT_JSON, TRANSPORT_FORMAT_PROTOBUF, None] = TRANSPORT_FORMAT_JSON ): self.service_name = service_name if url is None: @@ -125,11 +139,13 @@ def __init__( self.ipv6 = ipv6 self.retry = retry self.max_tag_value_length = max_tag_value_length + self.transport_format = transport_format def export(self, spans: Sequence[Span]) -> SpanExportResult: - zipkin_spans = self._translate_to_zipkin(spans) result = requests.post( - url=self.url, data=json.dumps(zipkin_spans), headers=ZIPKIN_HEADERS + url=self.url, + data=self._translate_to_transport_format(spans), + headers={"Content-Type": self.transport_format} ) if result.status_code not in SUCCESS_STATUS_CODES: @@ -147,8 +163,11 @@ def export(self, spans: Sequence[Span]) -> SpanExportResult: def shutdown(self) -> None: pass - def _translate_to_zipkin(self, spans: Sequence[Span]): + def _translate_to_transport_format(self, spans: Sequence[Span]): + return self._translate_to_json(spans) \ + if self.transport_format == TRANSPORT_FORMAT_JSON else self._translate_to_protobuf(spans) + def _translate_to_json(self, spans: Sequence[Span]): local_endpoint = {"serviceName": self.service_name, "port": self.port} if self.ipv4 is not None: @@ -165,8 +184,8 @@ def _translate_to_zipkin(self, spans: Sequence[Span]): # Timestamp in zipkin spans is int of microseconds. # see: https://zipkin.io/pages/instrumenting.html - start_timestamp_mus = _nsec_to_usec_round(span.start_time) - duration_mus = _nsec_to_usec_round(span.end_time - span.start_time) + start_timestamp_mus = nsec_to_usec_round(span.start_time) + duration_mus = nsec_to_usec_round(span.end_time - span.start_time) zipkin_span = { # Ensure left-zero-padding of traceId, spanId, parentId @@ -176,7 +195,7 @@ def _translate_to_zipkin(self, spans: Sequence[Span]): "timestamp": start_timestamp_mus, "duration": duration_mus, "localEndpoint": local_endpoint, - "kind": SPAN_KIND_MAP[span.kind], + "kind": SPAN_KIND_MAP[TRANSPORT_FORMAT_JSON][span.kind], "tags": self._extract_tags_from_span(span), "annotations": self._extract_annotations_from_events( span.events @@ -211,7 +230,81 @@ def _translate_to_zipkin(self, spans: Sequence[Span]): zipkin_span["parentId"] = format(span.parent.span_id, "016x") zipkin_spans.append(zipkin_span) - return zipkin_spans + + return json.dumps(zipkin_spans) + + def _translate_to_protobuf(self, spans: Sequence[Span]): + + local_endpoint = zipkin_pb2.Endpoint(service_name=self.service_name, port=self.port) + + if self.ipv4 is not None: + local_endpoint.ipv4 = self.ipv4 + + if self.ipv6 is not None: + local_endpoint.ipv6 = self.ipv6 + + pbuf_spans = zipkin_pb2.ListOfSpans() + + for span in spans: + context = span.get_span_context() + trace_id = context.trace_id.to_bytes(length=16, byteorder="big", signed=False) + span_id = self.format_pbuf_span_id(context.span_id) + + # Timestamp in zipkin spans is int of microseconds. + # see: https://zipkin.io/pages/instrumenting.html + start_timestamp_mus = nsec_to_usec_round(span.start_time) + duration_mus = nsec_to_usec_round(span.end_time - span.start_time) + + pbuf_span = zipkin_pb2.Span( + trace_id=trace_id, + id=span_id, + name=span.name, + timestamp=start_timestamp_mus, + duration=duration_mus, + local_endpoint=local_endpoint, + kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][span.kind], + tags=self._extract_tags_from_span(span) + ) + + annotations = self._extract_annotations_from_events(span.events) + + if annotations is not None: + for annotation in annotations: + pbuf_span.annotations.append( + zipkin_pb2.Annotation( + timestamp=annotation["timestamp"], + value=annotation["value"], + ) + ) + + if span.instrumentation_info is not None: + pbuf_span.tags.update({ + "otel.instrumentation_library.name": span.instrumentation_info.name, + "otel.instrumentation_library.version": span.instrumentation_info.version + }) + + if span.status is not None: + pbuf_span.tags.update({"otel.status_code": str(span.status.status_code.value)}) + if span.status.description is not None: + pbuf_span.tags.update({"otel.status_description": span.status.description}) + + if context.trace_flags.sampled: + pbuf_span.debug = True + + if isinstance(span.parent, Span): + pbuf_span.parent_id = self.format_pbuf_span_id( + span.parent.get_span_context().span_id + ) + elif isinstance(span.parent, SpanContext): + pbuf_span.parent_id = self.format_pbuf_span_id(span.parent.span_id) + + pbuf_spans.spans.append(pbuf_span) + + return pbuf_spans.SerializeToString() + + @staticmethod + def format_pbuf_span_id(span_id: int): + return span_id.to_bytes(length=8, byteorder="big", signed=False) def _extract_tags_from_dict(self, tags_dict): tags = {} @@ -251,13 +344,13 @@ def _extract_annotations_from_events(self, events): annotations.append( { - "timestamp": _nsec_to_usec_round(event.timestamp), + "timestamp": nsec_to_usec_round(event.timestamp), "value": json.dumps({event.name: attrs}), } ) return annotations -def _nsec_to_usec_round(nsec): +def nsec_to_usec_round(nsec): """Round nanoseconds to microseconds""" return (nsec + 500) // 10 ** 3 diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.py new file mode 100644 index 0000000000..7b578febc1 --- /dev/null +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.py @@ -0,0 +1,458 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: zipkin.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='zipkin.proto', + package='zipkin.proto3', + syntax='proto3', + serialized_options=b'\n\016zipkin2.proto3P\001', + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x0czipkin.proto\x12\rzipkin.proto3\"\xf5\x03\n\x04Span\x12\x10\n\x08trace_id\x18\x01 \x01(\x0c\x12\x11\n\tparent_id\x18\x02 \x01(\x0c\x12\n\n\x02id\x18\x03 \x01(\x0c\x12&\n\x04kind\x18\x04 \x01(\x0e\x32\x18.zipkin.proto3.Span.Kind\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\x06\x12\x10\n\x08\x64uration\x18\x07 \x01(\x04\x12/\n\x0elocal_endpoint\x18\x08 \x01(\x0b\x32\x17.zipkin.proto3.Endpoint\x12\x30\n\x0fremote_endpoint\x18\t \x01(\x0b\x32\x17.zipkin.proto3.Endpoint\x12.\n\x0b\x61nnotations\x18\n \x03(\x0b\x32\x19.zipkin.proto3.Annotation\x12+\n\x04tags\x18\x0b \x03(\x0b\x32\x1d.zipkin.proto3.Span.TagsEntry\x12\r\n\x05\x64\x65\x62ug\x18\x0c \x01(\x08\x12\x0e\n\x06shared\x18\r \x01(\x08\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"U\n\x04Kind\x12\x19\n\x15SPAN_KIND_UNSPECIFIED\x10\x00\x12\n\n\x06\x43LIENT\x10\x01\x12\n\n\x06SERVER\x10\x02\x12\x0c\n\x08PRODUCER\x10\x03\x12\x0c\n\x08\x43ONSUMER\x10\x04\"J\n\x08\x45ndpoint\x12\x14\n\x0cservice_name\x18\x01 \x01(\t\x12\x0c\n\x04ipv4\x18\x02 \x01(\x0c\x12\x0c\n\x04ipv6\x18\x03 \x01(\x0c\x12\x0c\n\x04port\x18\x04 \x01(\x05\".\n\nAnnotation\x12\x11\n\ttimestamp\x18\x01 \x01(\x06\x12\r\n\x05value\x18\x02 \x01(\t\"1\n\x0bListOfSpans\x12\"\n\x05spans\x18\x01 \x03(\x0b\x32\x13.zipkin.proto3.Span\"\x10\n\x0eReportResponse2T\n\x0bSpanService\x12\x45\n\x06Report\x12\x1a.zipkin.proto3.ListOfSpans\x1a\x1d.zipkin.proto3.ReportResponse\"\x00\x42\x12\n\x0ezipkin2.proto3P\x01\x62\x06proto3' +) + + + +_SPAN_KIND = _descriptor.EnumDescriptor( + name='Kind', + full_name='zipkin.proto3.Span.Kind', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='SPAN_KIND_UNSPECIFIED', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='CLIENT', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SERVER', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='PRODUCER', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='CONSUMER', index=4, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=448, + serialized_end=533, +) +_sym_db.RegisterEnumDescriptor(_SPAN_KIND) + + +_SPAN_TAGSENTRY = _descriptor.Descriptor( + name='TagsEntry', + full_name='zipkin.proto3.Span.TagsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='zipkin.proto3.Span.TagsEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='zipkin.proto3.Span.TagsEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=b'8\001', + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=403, + serialized_end=446, +) + +_SPAN = _descriptor.Descriptor( + name='Span', + full_name='zipkin.proto3.Span', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='trace_id', full_name='zipkin.proto3.Span.trace_id', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='parent_id', full_name='zipkin.proto3.Span.parent_id', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='id', full_name='zipkin.proto3.Span.id', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='kind', full_name='zipkin.proto3.Span.kind', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='name', full_name='zipkin.proto3.Span.name', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='timestamp', full_name='zipkin.proto3.Span.timestamp', index=5, + number=6, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='duration', full_name='zipkin.proto3.Span.duration', index=6, + number=7, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='local_endpoint', full_name='zipkin.proto3.Span.local_endpoint', index=7, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='remote_endpoint', full_name='zipkin.proto3.Span.remote_endpoint', index=8, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='annotations', full_name='zipkin.proto3.Span.annotations', index=9, + number=10, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='tags', full_name='zipkin.proto3.Span.tags', index=10, + number=11, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='debug', full_name='zipkin.proto3.Span.debug', index=11, + number=12, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='shared', full_name='zipkin.proto3.Span.shared', index=12, + number=13, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[_SPAN_TAGSENTRY, ], + enum_types=[ + _SPAN_KIND, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=32, + serialized_end=533, +) + + +_ENDPOINT = _descriptor.Descriptor( + name='Endpoint', + full_name='zipkin.proto3.Endpoint', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='service_name', full_name='zipkin.proto3.Endpoint.service_name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='ipv4', full_name='zipkin.proto3.Endpoint.ipv4', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='ipv6', full_name='zipkin.proto3.Endpoint.ipv6', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='port', full_name='zipkin.proto3.Endpoint.port', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=535, + serialized_end=609, +) + + +_ANNOTATION = _descriptor.Descriptor( + name='Annotation', + full_name='zipkin.proto3.Annotation', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='timestamp', full_name='zipkin.proto3.Annotation.timestamp', index=0, + number=1, type=6, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='value', full_name='zipkin.proto3.Annotation.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=611, + serialized_end=657, +) + + +_LISTOFSPANS = _descriptor.Descriptor( + name='ListOfSpans', + full_name='zipkin.proto3.ListOfSpans', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='spans', full_name='zipkin.proto3.ListOfSpans.spans', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=659, + serialized_end=708, +) + + +_REPORTRESPONSE = _descriptor.Descriptor( + name='ReportResponse', + full_name='zipkin.proto3.ReportResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=710, + serialized_end=726, +) + +_SPAN_TAGSENTRY.containing_type = _SPAN +_SPAN.fields_by_name['kind'].enum_type = _SPAN_KIND +_SPAN.fields_by_name['local_endpoint'].message_type = _ENDPOINT +_SPAN.fields_by_name['remote_endpoint'].message_type = _ENDPOINT +_SPAN.fields_by_name['annotations'].message_type = _ANNOTATION +_SPAN.fields_by_name['tags'].message_type = _SPAN_TAGSENTRY +_SPAN_KIND.containing_type = _SPAN +_LISTOFSPANS.fields_by_name['spans'].message_type = _SPAN +DESCRIPTOR.message_types_by_name['Span'] = _SPAN +DESCRIPTOR.message_types_by_name['Endpoint'] = _ENDPOINT +DESCRIPTOR.message_types_by_name['Annotation'] = _ANNOTATION +DESCRIPTOR.message_types_by_name['ListOfSpans'] = _LISTOFSPANS +DESCRIPTOR.message_types_by_name['ReportResponse'] = _REPORTRESPONSE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Span = _reflection.GeneratedProtocolMessageType('Span', (_message.Message,), { + + 'TagsEntry' : _reflection.GeneratedProtocolMessageType('TagsEntry', (_message.Message,), { + 'DESCRIPTOR' : _SPAN_TAGSENTRY, + '__module__' : 'zipkin_pb2' + # @@protoc_insertion_point(class_scope:zipkin.proto3.Span.TagsEntry) + }) + , + 'DESCRIPTOR' : _SPAN, + '__module__' : 'zipkin_pb2' + # @@protoc_insertion_point(class_scope:zipkin.proto3.Span) + }) +_sym_db.RegisterMessage(Span) +_sym_db.RegisterMessage(Span.TagsEntry) + +Endpoint = _reflection.GeneratedProtocolMessageType('Endpoint', (_message.Message,), { + 'DESCRIPTOR' : _ENDPOINT, + '__module__' : 'zipkin_pb2' + # @@protoc_insertion_point(class_scope:zipkin.proto3.Endpoint) + }) +_sym_db.RegisterMessage(Endpoint) + +Annotation = _reflection.GeneratedProtocolMessageType('Annotation', (_message.Message,), { + 'DESCRIPTOR' : _ANNOTATION, + '__module__' : 'zipkin_pb2' + # @@protoc_insertion_point(class_scope:zipkin.proto3.Annotation) + }) +_sym_db.RegisterMessage(Annotation) + +ListOfSpans = _reflection.GeneratedProtocolMessageType('ListOfSpans', (_message.Message,), { + 'DESCRIPTOR' : _LISTOFSPANS, + '__module__' : 'zipkin_pb2' + # @@protoc_insertion_point(class_scope:zipkin.proto3.ListOfSpans) + }) +_sym_db.RegisterMessage(ListOfSpans) + +ReportResponse = _reflection.GeneratedProtocolMessageType('ReportResponse', (_message.Message,), { + 'DESCRIPTOR' : _REPORTRESPONSE, + '__module__' : 'zipkin_pb2' + # @@protoc_insertion_point(class_scope:zipkin.proto3.ReportResponse) + }) +_sym_db.RegisterMessage(ReportResponse) + + +DESCRIPTOR._options = None +_SPAN_TAGSENTRY._options = None + +_SPANSERVICE = _descriptor.ServiceDescriptor( + name='SpanService', + full_name='zipkin.proto3.SpanService', + file=DESCRIPTOR, + index=0, + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_start=728, + serialized_end=812, + methods=[ + _descriptor.MethodDescriptor( + name='Report', + full_name='zipkin.proto3.SpanService.Report', + index=0, + containing_service=None, + input_type=_LISTOFSPANS, + output_type=_REPORTRESPONSE, + serialized_options=None, + create_key=_descriptor._internal_create_key, + ), +]) +_sym_db.RegisterServiceDescriptor(_SPANSERVICE) + +DESCRIPTOR.services_by_name['SpanService'] = _SPANSERVICE + +# @@protoc_insertion_point(module_scope) diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.pyi b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.pyi new file mode 100644 index 0000000000..1624d7d595 --- /dev/null +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.pyi @@ -0,0 +1,214 @@ +# @generated by generate_proto_mypy_stubs.py. Do not edit! +import sys +from google.protobuf.descriptor import ( + Descriptor as google___protobuf___descriptor___Descriptor, + EnumDescriptor as google___protobuf___descriptor___EnumDescriptor, + FileDescriptor as google___protobuf___descriptor___FileDescriptor, +) + +from google.protobuf.internal.containers import ( + RepeatedCompositeFieldContainer as google___protobuf___internal___containers___RepeatedCompositeFieldContainer, +) + +from google.protobuf.message import ( + Message as google___protobuf___message___Message, +) + +from typing import ( + Iterable as typing___Iterable, + List as typing___List, + Mapping as typing___Mapping, + MutableMapping as typing___MutableMapping, + NewType as typing___NewType, + Optional as typing___Optional, + Text as typing___Text, + Tuple as typing___Tuple, + Union as typing___Union, + cast as typing___cast, +) + +from typing_extensions import ( + Literal as typing_extensions___Literal, +) + + +builtin___bool = bool +builtin___bytes = bytes +builtin___float = float +builtin___int = int +builtin___str = str +if sys.version_info < (3,): + builtin___buffer = buffer + builtin___unicode = unicode + + +DESCRIPTOR: google___protobuf___descriptor___FileDescriptor = ... + +class Span(google___protobuf___message___Message): + DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... + KindValue = typing___NewType('KindValue', builtin___int) + type___KindValue = KindValue + class Kind(object): + DESCRIPTOR: google___protobuf___descriptor___EnumDescriptor = ... + @classmethod + def Name(cls, number: builtin___int) -> builtin___str: ... + @classmethod + def Value(cls, name: builtin___str) -> Span.KindValue: ... + @classmethod + def keys(cls) -> typing___List[builtin___str]: ... + @classmethod + def values(cls) -> typing___List[Span.KindValue]: ... + @classmethod + def items(cls) -> typing___List[typing___Tuple[builtin___str, Span.KindValue]]: ... + SPAN_KIND_UNSPECIFIED = typing___cast(Span.KindValue, 0) + CLIENT = typing___cast(Span.KindValue, 1) + SERVER = typing___cast(Span.KindValue, 2) + PRODUCER = typing___cast(Span.KindValue, 3) + CONSUMER = typing___cast(Span.KindValue, 4) + SPAN_KIND_UNSPECIFIED = typing___cast(Span.KindValue, 0) + CLIENT = typing___cast(Span.KindValue, 1) + SERVER = typing___cast(Span.KindValue, 2) + PRODUCER = typing___cast(Span.KindValue, 3) + CONSUMER = typing___cast(Span.KindValue, 4) + type___Kind = Kind + + class TagsEntry(google___protobuf___message___Message): + DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... + key: typing___Text = ... + value: typing___Text = ... + + def __init__(self, + *, + key : typing___Optional[typing___Text] = None, + value : typing___Optional[typing___Text] = None, + ) -> None: ... + if sys.version_info >= (3,): + @classmethod + def FromString(cls, s: builtin___bytes) -> Span.TagsEntry: ... + else: + @classmethod + def FromString(cls, s: typing___Union[builtin___bytes, builtin___buffer, builtin___unicode]) -> Span.TagsEntry: ... + def ClearField(self, field_name: typing_extensions___Literal[u"key",b"key",u"value",b"value"]) -> None: ... + type___TagsEntry = TagsEntry + + trace_id: builtin___bytes = ... + parent_id: builtin___bytes = ... + id: builtin___bytes = ... + kind: type___Span.KindValue = ... + name: typing___Text = ... + timestamp: builtin___int = ... + duration: builtin___int = ... + debug: builtin___bool = ... + shared: builtin___bool = ... + + @property + def local_endpoint(self) -> type___Endpoint: ... + + @property + def remote_endpoint(self) -> type___Endpoint: ... + + @property + def annotations(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[type___Annotation]: ... + + @property + def tags(self) -> typing___MutableMapping[typing___Text, typing___Text]: ... + + def __init__(self, + *, + trace_id : typing___Optional[builtin___bytes] = None, + parent_id : typing___Optional[builtin___bytes] = None, + id : typing___Optional[builtin___bytes] = None, + kind : typing___Optional[type___Span.KindValue] = None, + name : typing___Optional[typing___Text] = None, + timestamp : typing___Optional[builtin___int] = None, + duration : typing___Optional[builtin___int] = None, + local_endpoint : typing___Optional[type___Endpoint] = None, + remote_endpoint : typing___Optional[type___Endpoint] = None, + annotations : typing___Optional[typing___Iterable[type___Annotation]] = None, + tags : typing___Optional[typing___Mapping[typing___Text, typing___Text]] = None, + debug : typing___Optional[builtin___bool] = None, + shared : typing___Optional[builtin___bool] = None, + ) -> None: ... + if sys.version_info >= (3,): + @classmethod + def FromString(cls, s: builtin___bytes) -> Span: ... + else: + @classmethod + def FromString(cls, s: typing___Union[builtin___bytes, builtin___buffer, builtin___unicode]) -> Span: ... + def HasField(self, field_name: typing_extensions___Literal[u"local_endpoint",b"local_endpoint",u"remote_endpoint",b"remote_endpoint"]) -> builtin___bool: ... + def ClearField(self, field_name: typing_extensions___Literal[u"annotations",b"annotations",u"debug",b"debug",u"duration",b"duration",u"id",b"id",u"kind",b"kind",u"local_endpoint",b"local_endpoint",u"name",b"name",u"parent_id",b"parent_id",u"remote_endpoint",b"remote_endpoint",u"shared",b"shared",u"tags",b"tags",u"timestamp",b"timestamp",u"trace_id",b"trace_id"]) -> None: ... +type___Span = Span + +class Endpoint(google___protobuf___message___Message): + DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... + service_name: typing___Text = ... + ipv4: builtin___bytes = ... + ipv6: builtin___bytes = ... + port: builtin___int = ... + + def __init__(self, + *, + service_name : typing___Optional[typing___Text] = None, + ipv4 : typing___Optional[builtin___bytes] = None, + ipv6 : typing___Optional[builtin___bytes] = None, + port : typing___Optional[builtin___int] = None, + ) -> None: ... + if sys.version_info >= (3,): + @classmethod + def FromString(cls, s: builtin___bytes) -> Endpoint: ... + else: + @classmethod + def FromString(cls, s: typing___Union[builtin___bytes, builtin___buffer, builtin___unicode]) -> Endpoint: ... + def ClearField(self, field_name: typing_extensions___Literal[u"ipv4",b"ipv4",u"ipv6",b"ipv6",u"port",b"port",u"service_name",b"service_name"]) -> None: ... +type___Endpoint = Endpoint + +class Annotation(google___protobuf___message___Message): + DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... + timestamp: builtin___int = ... + value: typing___Text = ... + + def __init__(self, + *, + timestamp : typing___Optional[builtin___int] = None, + value : typing___Optional[typing___Text] = None, + ) -> None: ... + if sys.version_info >= (3,): + @classmethod + def FromString(cls, s: builtin___bytes) -> Annotation: ... + else: + @classmethod + def FromString(cls, s: typing___Union[builtin___bytes, builtin___buffer, builtin___unicode]) -> Annotation: ... + def ClearField(self, field_name: typing_extensions___Literal[u"timestamp",b"timestamp",u"value",b"value"]) -> None: ... +type___Annotation = Annotation + +class ListOfSpans(google___protobuf___message___Message): + DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... + + @property + def spans(self) -> google___protobuf___internal___containers___RepeatedCompositeFieldContainer[type___Span]: ... + + def __init__(self, + *, + spans : typing___Optional[typing___Iterable[type___Span]] = None, + ) -> None: ... + if sys.version_info >= (3,): + @classmethod + def FromString(cls, s: builtin___bytes) -> ListOfSpans: ... + else: + @classmethod + def FromString(cls, s: typing___Union[builtin___bytes, builtin___buffer, builtin___unicode]) -> ListOfSpans: ... + def ClearField(self, field_name: typing_extensions___Literal[u"spans",b"spans"]) -> None: ... +type___ListOfSpans = ListOfSpans + +class ReportResponse(google___protobuf___message___Message): + DESCRIPTOR: google___protobuf___descriptor___Descriptor = ... + + def __init__(self, + ) -> None: ... + if sys.version_info >= (3,): + @classmethod + def FromString(cls, s: builtin___bytes) -> ReportResponse: ... + else: + @classmethod + def FromString(cls, s: typing___Union[builtin___bytes, builtin___buffer, builtin___unicode]) -> ReportResponse: ... +type___ReportResponse = ReportResponse diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py index 12d85a0dc4..7aeadd0884 100644 --- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py +++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py @@ -18,12 +18,19 @@ from unittest.mock import MagicMock, patch from opentelemetry import trace as trace_api -from opentelemetry.exporter.zipkin import ZipkinSpanExporter +from opentelemetry.exporter.zipkin import ( + nsec_to_usec_round, + SPAN_KIND_MAP, + TRANSPORT_FORMAT_JSON, + TRANSPORT_FORMAT_PROTOBUF, + zipkin_pb2, + ZipkinSpanExporter, +) from opentelemetry.sdk import trace from opentelemetry.sdk.trace import Resource from opentelemetry.sdk.trace.export import SpanExportResult from opentelemetry.sdk.util.instrumentation import InstrumentationInfo -from opentelemetry.trace import TraceFlags +from opentelemetry.trace import SpanKind, TraceFlags from opentelemetry.trace.status import Status, StatusCode @@ -59,12 +66,14 @@ def test_constructor_env_var(self): exporter = ZipkinSpanExporter(service_name) ipv4 = None ipv6 = None + transport_format = TRANSPORT_FORMAT_JSON self.assertEqual(exporter.service_name, service_name) self.assertEqual(exporter.ipv4, ipv4) self.assertEqual(exporter.ipv6, ipv6) self.assertEqual(exporter.url, url) self.assertEqual(exporter.port, port) + self.assertEqual(exporter.transport_format, transport_format) def test_constructor_default(self): """Test the default values assigned by constructor.""" @@ -74,12 +83,14 @@ def test_constructor_default(self): ipv4 = None ipv6 = None url = "http://localhost:9411/api/v2/spans" + transport_format = TRANSPORT_FORMAT_JSON self.assertEqual(exporter.service_name, service_name) self.assertEqual(exporter.port, port) self.assertEqual(exporter.ipv4, ipv4) self.assertEqual(exporter.ipv6, ipv6) self.assertEqual(exporter.url, url) + self.assertEqual(exporter.transport_format, transport_format) def test_constructor_explicit(self): """Test the constructor passing all the options.""" @@ -88,8 +99,14 @@ def test_constructor_explicit(self): ipv4 = "1.2.3.4" ipv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" url = "https://opentelemetry.io:15875/myapi/traces?format=zipkin" + transport_format = TRANSPORT_FORMAT_PROTOBUF + exporter = ZipkinSpanExporter( - service_name=service_name, url=url, ipv4=ipv4, ipv6=ipv6, + service_name=service_name, + url=url, + ipv4=ipv4, + ipv6=ipv6, + transport_format=transport_format ) self.assertEqual(exporter.service_name, service_name) @@ -97,9 +114,10 @@ def test_constructor_explicit(self): self.assertEqual(exporter.ipv4, ipv4) self.assertEqual(exporter.ipv6, ipv6) self.assertEqual(exporter.url, url) + self.assertEqual(exporter.transport_format, transport_format) # pylint: disable=too-many-locals,too-many-statements - def test_export(self): + def test_export_json(self): span_names = ("test1", "test2", "test3", "test4") trace_id = 0x6E0C63257DE34C926F9EFCD03927272E span_id = 0x34BF92DEEFC58C92 @@ -294,6 +312,7 @@ def test_export(self): kwargs = mock_post.call_args[1] self.assertEqual(kwargs["url"], "http://localhost:9411/api/v2/spans") + self.assertEqual(kwargs["headers"]["Content-Type"], TRANSPORT_FORMAT_JSON) actual_spans = sorted( json.loads(kwargs["data"]), key=lambda span: span["timestamp"] ) @@ -307,7 +326,7 @@ def test_export(self): self.assertEqual(expected_annotations, actual_annotations) # pylint: disable=too-many-locals - def test_zero_padding(self): + def test_export_json_zero_padding(self): """test that hex ids starting with 0 are properly padded to 16 or 32 hex chars when exported @@ -383,7 +402,7 @@ def test_invalid_response(self, mock_post): status = exporter.export(spans) self.assertEqual(SpanExportResult.FAILURE, status) - def test_max_tag_length(self): + def test_export_json_max_tag_length(self): service_name = "test-service" span_context = trace_api.SpanContext( @@ -427,3 +446,247 @@ def test_max_tag_length(self): tags = json.loads(kwargs["data"])[0]["tags"] self.assertEqual(len(tags["k1"]), 2) self.assertEqual(len(tags["k2"]), 2) + + # pylint: disable=too-many-locals,too-many-statements + def test_export_protobuf(self): + span_names = ("test1", "test2", "test3", "test4") + trace_id = 0x6E0C63257DE34C926F9EFCD03927272E + span_id = 0x34BF92DEEFC58C92 + parent_id = 0x1111111111111111 + other_id = 0x2222222222222222 + + base_time = 683647322 * 10 ** 9 # in ns + start_times = ( + base_time, + base_time + 150 * 10 ** 6, + base_time + 300 * 10 ** 6, + base_time + 400 * 10 ** 6, + ) + durations = (50 * 10 ** 6, 100 * 10 ** 6, 200 * 10 ** 6, 300 * 10 ** 6) + end_times = ( + start_times[0] + durations[0], + start_times[1] + durations[1], + start_times[2] + durations[2], + start_times[3] + durations[3], + ) + + span_context = trace_api.SpanContext( + trace_id, + span_id, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + ) + parent_span_context = trace_api.SpanContext( + trace_id, parent_id, is_remote=False + ) + other_context = trace_api.SpanContext( + trace_id, other_id, is_remote=False + ) + + event_attributes = { + "annotation_bool": True, + "annotation_string": "annotation_test", + "key_float": 0.3, + } + + event_timestamp = base_time + 50 * 10 ** 6 + event = trace.Event( + name="event0", + timestamp=event_timestamp, + attributes=event_attributes, + ) + + link_attributes = {"key_bool": True} + + link = trace_api.Link( + context=other_context, attributes=link_attributes + ) + + otel_spans = [ + trace._Span( + name=span_names[0], + context=span_context, + parent=parent_span_context, + events=(event,), + links=(link,), + ), + trace._Span( + name=span_names[1], context=parent_span_context, parent=None + ), + trace._Span( + name=span_names[2], context=other_context, parent=None + ), + trace._Span( + name=span_names[3], context=other_context, parent=None + ), + ] + + otel_spans[0].start(start_time=start_times[0]) + otel_spans[0].resource = Resource({}) + # added here to preserve order + otel_spans[0].set_attribute("key_bool", False) + otel_spans[0].set_attribute("key_string", "hello_world") + otel_spans[0].set_attribute("key_float", 111.22) + otel_spans[0].set_status( + Status(StatusCode.ERROR, "Example description") + ) + otel_spans[0].end(end_time=end_times[0]) + + otel_spans[1].start(start_time=start_times[1]) + otel_spans[1].resource = Resource( + attributes={"key_resource": "some_resource"} + ) + otel_spans[1].end(end_time=end_times[1]) + + otel_spans[2].start(start_time=start_times[2]) + otel_spans[2].set_attribute("key_string", "hello_world") + otel_spans[2].resource = Resource( + attributes={"key_resource": "some_resource"} + ) + otel_spans[2].end(end_time=end_times[2]) + + otel_spans[3].start(start_time=start_times[3]) + otel_spans[3].resource = Resource({}) + otel_spans[3].end(end_time=end_times[3]) + otel_spans[3].instrumentation_info = InstrumentationInfo( + name="name", version="version" + ) + + service_name = "test-service" + local_endpoint = zipkin_pb2.Endpoint(service_name=service_name, port=9411) + + expected_spans = zipkin_pb2.ListOfSpans(spans=[ + zipkin_pb2.Span( + trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), + id=ZipkinSpanExporter.format_pbuf_span_id(span_id), + name=span_names[0], + timestamp=nsec_to_usec_round(start_times[0]), + duration=nsec_to_usec_round(durations[0]), + local_endpoint=local_endpoint, + kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][SpanKind.INTERNAL], + tags={ + "key_bool": "False", + "key_string": "hello_world", + "key_float": "111.22", + "otel.status_code": "2", + "otel.status_description": "Example description", + }, + debug=True, + parent_id=ZipkinSpanExporter.format_pbuf_span_id(parent_id), + annotations=[ + zipkin_pb2.Annotation( + timestamp=nsec_to_usec_round(event_timestamp), + value=json.dumps({ + "event0": { + "annotation_bool": True, + "annotation_string": "annotation_test", + "key_float": 0.3, + } + }) + ), + ] + ), + zipkin_pb2.Span( + trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), + id=ZipkinSpanExporter.format_pbuf_span_id(parent_id), + name=span_names[1], + timestamp=nsec_to_usec_round(start_times[1]), + duration=nsec_to_usec_round(durations[1]), + local_endpoint=local_endpoint, + kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][SpanKind.INTERNAL], + tags={ + "key_resource": "some_resource", + "otel.status_code": "1", + } + ), + zipkin_pb2.Span( + trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), + id=ZipkinSpanExporter.format_pbuf_span_id(other_id), + name=span_names[2], + timestamp=nsec_to_usec_round(start_times[2]), + duration=nsec_to_usec_round(durations[2]), + local_endpoint=local_endpoint, + kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][SpanKind.INTERNAL], + tags={ + "key_string": "hello_world", + "key_resource": "some_resource", + "otel.status_code": "1", + } + ), + zipkin_pb2.Span( + trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), + id=ZipkinSpanExporter.format_pbuf_span_id(other_id), + name=span_names[3], + timestamp=nsec_to_usec_round(start_times[3]), + duration=nsec_to_usec_round(durations[3]), + local_endpoint=local_endpoint, + kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][SpanKind.INTERNAL], + tags={ + "otel.instrumentation_library.name": "name", + "otel.instrumentation_library.version": "version", + "otel.status_code": "1", + } + ), + ]) + + exporter = ZipkinSpanExporter(service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF) + mock_post = MagicMock() + with patch("requests.post", mock_post): + mock_post.return_value = MockResponse(200) + status = exporter.export(otel_spans) + self.assertEqual(SpanExportResult.SUCCESS, status) + + # pylint: disable=unsubscriptable-object + kwargs = mock_post.call_args[1] + + self.assertEqual(kwargs["url"], "http://localhost:9411/api/v2/spans") + self.assertEqual(kwargs["headers"]["Content-Type"], TRANSPORT_FORMAT_PROTOBUF) + self.assertEqual(zipkin_pb2.ListOfSpans.FromString(kwargs["data"]), expected_spans) + + def test_export_protobuf_max_tag_length(self): + service_name = "test-service" + + span_context = trace_api.SpanContext( + 0x0E0C63257DE34C926F9EFCD03927272E, + 0x04BF92DEEFC58C92, + is_remote=False, + trace_flags=TraceFlags(TraceFlags.SAMPLED), + ) + + span = trace._Span(name="test-span", context=span_context,) + + span.start() + span.resource = Resource({}) + # added here to preserve order + span.set_attribute("k1", "v" * 500) + span.set_attribute("k2", "v" * 50) + span.set_status(Status(StatusCode.ERROR, "Example description")) + span.end() + + exporter = ZipkinSpanExporter(service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF) + mock_post = MagicMock() + with patch("requests.post", mock_post): + mock_post.return_value = MockResponse(200) + status = exporter.export([span]) + self.assertEqual(SpanExportResult.SUCCESS, status) + + kwargs = mock_post.call_args[1] + actual_spans = zipkin_pb2.ListOfSpans.FromString(kwargs["data"]) + span_tags = actual_spans.spans[0].tags + + self.assertEqual(len(span_tags["k1"]), 128) + self.assertEqual(len(span_tags["k2"]), 50) + + exporter = ZipkinSpanExporter(service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF, max_tag_value_length=2) + mock_post = MagicMock() + with patch("requests.post", mock_post): + mock_post.return_value = MockResponse(200) + status = exporter.export([span]) + self.assertEqual(SpanExportResult.SUCCESS, status) + + kwargs = mock_post.call_args[1] + actual_spans = zipkin_pb2.ListOfSpans.FromString(kwargs["data"]) + span_tags = actual_spans.spans[0].tags + + self.assertEqual(len(span_tags["k1"]), 2) + self.assertEqual(len(span_tags["k2"]), 2) From 019786628c84987ca927c897817dd20316966a29 Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Mon, 2 Nov 2020 11:42:29 -0800 Subject: [PATCH 02/12] adding env var OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT and tidying up TRANSPORT_FORMAT_JSON/PROTOBUF vals to not overload w/ Content-Type --- .../opentelemetry/exporter/zipkin/__init__.py | 24 +++++++++++++++---- .../tests/test_zipkin_exporter.py | 10 ++++---- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py index 05ee0c06b5..7e43e63095 100644 --- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py @@ -73,8 +73,8 @@ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult from opentelemetry.trace import Span, SpanContext, SpanKind -TRANSPORT_FORMAT_JSON = "application/json" -TRANSPORT_FORMAT_PROTOBUF = "application/x-protobuf" +TRANSPORT_FORMAT_JSON = "json" +TRANSPORT_FORMAT_PROTOBUF = "protobuf" DEFAULT_RETRY = False DEFAULT_URL = "http://localhost:9411/api/v2/spans" @@ -123,7 +123,7 @@ def __init__( ipv6: Optional[str] = None, retry: Optional[str] = DEFAULT_RETRY, max_tag_value_length: Optional[int] = DEFAULT_MAX_TAG_VALUE_LENGTH, - transport_format: Union[TRANSPORT_FORMAT_JSON, TRANSPORT_FORMAT_PROTOBUF, None] = TRANSPORT_FORMAT_JSON + transport_format: Union[TRANSPORT_FORMAT_JSON, TRANSPORT_FORMAT_PROTOBUF, None] = None ): self.service_name = service_name if url is None: @@ -139,13 +139,27 @@ def __init__( self.ipv6 = ipv6 self.retry = retry self.max_tag_value_length = max_tag_value_length - self.transport_format = transport_format + + if transport_format is None: + self.transport_format = os.environ.get( + "OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT", TRANSPORT_FORMAT_JSON + ) + else: + self.transport_format = transport_format def export(self, spans: Sequence[Span]) -> SpanExportResult: + if self.transport_format == TRANSPORT_FORMAT_JSON: + content_type = "application/json" + elif self.transport_format == TRANSPORT_FORMAT_PROTOBUF: + content_type = "application/x-protobuf" + else: + logger.error("Invalid transport format %s", self.transport_format) + return SpanExportResult.FAILURE + result = requests.post( url=self.url, data=self._translate_to_transport_format(spans), - headers={"Content-Type": self.transport_format} + headers={"Content-Type": content_type} ) if result.status_code not in SUCCESS_STATUS_CODES: diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py index 7aeadd0884..7d28062e6c 100644 --- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py +++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py @@ -56,24 +56,26 @@ def setUp(self): def tearDown(self): if "OTEL_EXPORTER_ZIPKIN_ENDPOINT" in os.environ: del os.environ["OTEL_EXPORTER_ZIPKIN_ENDPOINT"] + if "OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT" in os.environ: + del os.environ["OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT"] def test_constructor_env_var(self): """Test the default values assigned by constructor.""" url = "https://foo:9911/path" os.environ["OTEL_EXPORTER_ZIPKIN_ENDPOINT"] = url + os.environ["OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT"] = TRANSPORT_FORMAT_PROTOBUF service_name = "my-service-name" port = 9911 exporter = ZipkinSpanExporter(service_name) ipv4 = None ipv6 = None - transport_format = TRANSPORT_FORMAT_JSON self.assertEqual(exporter.service_name, service_name) self.assertEqual(exporter.ipv4, ipv4) self.assertEqual(exporter.ipv6, ipv6) self.assertEqual(exporter.url, url) self.assertEqual(exporter.port, port) - self.assertEqual(exporter.transport_format, transport_format) + self.assertEqual(exporter.transport_format, TRANSPORT_FORMAT_PROTOBUF) def test_constructor_default(self): """Test the default values assigned by constructor.""" @@ -312,7 +314,7 @@ def test_export_json(self): kwargs = mock_post.call_args[1] self.assertEqual(kwargs["url"], "http://localhost:9411/api/v2/spans") - self.assertEqual(kwargs["headers"]["Content-Type"], TRANSPORT_FORMAT_JSON) + self.assertEqual(kwargs["headers"]["Content-Type"], "application/json") actual_spans = sorted( json.loads(kwargs["data"]), key=lambda span: span["timestamp"] ) @@ -640,7 +642,7 @@ def test_export_protobuf(self): kwargs = mock_post.call_args[1] self.assertEqual(kwargs["url"], "http://localhost:9411/api/v2/spans") - self.assertEqual(kwargs["headers"]["Content-Type"], TRANSPORT_FORMAT_PROTOBUF) + self.assertEqual(kwargs["headers"]["Content-Type"], "application/x-protobuf") self.assertEqual(zipkin_pb2.ListOfSpans.FromString(kwargs["data"]), expected_spans) def test_export_protobuf_max_tag_length(self): From 131cbea5120fc6561306466b7f554584924fca69 Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Mon, 2 Nov 2020 13:38:30 -0800 Subject: [PATCH 03/12] lint corrections --- .../opentelemetry/exporter/zipkin/__init__.py | 47 +++-- .../tests/test_zipkin_exporter.py | 198 +++++++++++------- 2 files changed, 150 insertions(+), 95 deletions(-) diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py index 7e43e63095..a2e08d4277 100644 --- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py @@ -94,7 +94,7 @@ SpanKind.CLIENT: zipkin_pb2.Span.Kind.CLIENT, SpanKind.PRODUCER: zipkin_pb2.Span.Kind.PRODUCER, SpanKind.CONSUMER: zipkin_pb2.Span.Kind.CONSUMER, - } + }, } SUCCESS_STATUS_CODES = (200, 202) @@ -123,7 +123,9 @@ def __init__( ipv6: Optional[str] = None, retry: Optional[str] = DEFAULT_RETRY, max_tag_value_length: Optional[int] = DEFAULT_MAX_TAG_VALUE_LENGTH, - transport_format: Union[TRANSPORT_FORMAT_JSON, TRANSPORT_FORMAT_PROTOBUF, None] = None + transport_format: Union[ + TRANSPORT_FORMAT_JSON, TRANSPORT_FORMAT_PROTOBUF, None + ] = None, ): self.service_name = service_name if url is None: @@ -159,7 +161,7 @@ def export(self, spans: Sequence[Span]) -> SpanExportResult: result = requests.post( url=self.url, data=self._translate_to_transport_format(spans), - headers={"Content-Type": content_type} + headers={"Content-Type": content_type}, ) if result.status_code not in SUCCESS_STATUS_CODES: @@ -178,8 +180,11 @@ def shutdown(self) -> None: pass def _translate_to_transport_format(self, spans: Sequence[Span]): - return self._translate_to_json(spans) \ - if self.transport_format == TRANSPORT_FORMAT_JSON else self._translate_to_protobuf(spans) + return ( + self._translate_to_json(spans) + if self.transport_format == TRANSPORT_FORMAT_JSON + else self._translate_to_protobuf(spans) + ) def _translate_to_json(self, spans: Sequence[Span]): local_endpoint = {"serviceName": self.service_name, "port": self.port} @@ -249,7 +254,9 @@ def _translate_to_json(self, spans: Sequence[Span]): def _translate_to_protobuf(self, spans: Sequence[Span]): - local_endpoint = zipkin_pb2.Endpoint(service_name=self.service_name, port=self.port) + local_endpoint = zipkin_pb2.Endpoint( + service_name=self.service_name, port=self.port + ) if self.ipv4 is not None: local_endpoint.ipv4 = self.ipv4 @@ -261,7 +268,9 @@ def _translate_to_protobuf(self, spans: Sequence[Span]): for span in spans: context = span.get_span_context() - trace_id = context.trace_id.to_bytes(length=16, byteorder="big", signed=False) + trace_id = context.trace_id.to_bytes( + length=16, byteorder="big", signed=False, + ) span_id = self.format_pbuf_span_id(context.span_id) # Timestamp in zipkin spans is int of microseconds. @@ -277,7 +286,7 @@ def _translate_to_protobuf(self, spans: Sequence[Span]): duration=duration_mus, local_endpoint=local_endpoint, kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][span.kind], - tags=self._extract_tags_from_span(span) + tags=self._extract_tags_from_span(span), ) annotations = self._extract_annotations_from_events(span.events) @@ -292,15 +301,21 @@ def _translate_to_protobuf(self, spans: Sequence[Span]): ) if span.instrumentation_info is not None: - pbuf_span.tags.update({ - "otel.instrumentation_library.name": span.instrumentation_info.name, - "otel.instrumentation_library.version": span.instrumentation_info.version - }) + pbuf_span.tags.update( + { + "otel.instrumentation_library.name": span.instrumentation_info.name, + "otel.instrumentation_library.version": span.instrumentation_info.version, + } + ) if span.status is not None: - pbuf_span.tags.update({"otel.status_code": str(span.status.status_code.value)}) + pbuf_span.tags.update( + {"otel.status_code": str(span.status.status_code.value)} + ) if span.status.description is not None: - pbuf_span.tags.update({"otel.status_description": span.status.description}) + pbuf_span.tags.update( + {"otel.status_description": span.status.description} + ) if context.trace_flags.sampled: pbuf_span.debug = True @@ -310,7 +325,9 @@ def _translate_to_protobuf(self, spans: Sequence[Span]): span.parent.get_span_context().span_id ) elif isinstance(span.parent, SpanContext): - pbuf_span.parent_id = self.format_pbuf_span_id(span.parent.span_id) + pbuf_span.parent_id = self.format_pbuf_span_id( + span.parent.span_id + ) pbuf_spans.spans.append(pbuf_span) diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py index 7d28062e6c..7605f62479 100644 --- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py +++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py @@ -63,7 +63,9 @@ def test_constructor_env_var(self): """Test the default values assigned by constructor.""" url = "https://foo:9911/path" os.environ["OTEL_EXPORTER_ZIPKIN_ENDPOINT"] = url - os.environ["OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT"] = TRANSPORT_FORMAT_PROTOBUF + os.environ[ + "OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT" + ] = TRANSPORT_FORMAT_PROTOBUF service_name = "my-service-name" port = 9911 exporter = ZipkinSpanExporter(service_name) @@ -108,7 +110,7 @@ def test_constructor_explicit(self): url=url, ipv4=ipv4, ipv6=ipv6, - transport_format=transport_format + transport_format=transport_format, ) self.assertEqual(exporter.service_name, service_name) @@ -555,83 +557,109 @@ def test_export_protobuf(self): ) service_name = "test-service" - local_endpoint = zipkin_pb2.Endpoint(service_name=service_name, port=9411) + local_endpoint = zipkin_pb2.Endpoint( + service_name=service_name, port=9411 + ) - expected_spans = zipkin_pb2.ListOfSpans(spans=[ - zipkin_pb2.Span( - trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), - id=ZipkinSpanExporter.format_pbuf_span_id(span_id), - name=span_names[0], - timestamp=nsec_to_usec_round(start_times[0]), - duration=nsec_to_usec_round(durations[0]), - local_endpoint=local_endpoint, - kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][SpanKind.INTERNAL], - tags={ - "key_bool": "False", - "key_string": "hello_world", - "key_float": "111.22", - "otel.status_code": "2", - "otel.status_description": "Example description", - }, - debug=True, - parent_id=ZipkinSpanExporter.format_pbuf_span_id(parent_id), - annotations=[ - zipkin_pb2.Annotation( - timestamp=nsec_to_usec_round(event_timestamp), - value=json.dumps({ - "event0": { - "annotation_bool": True, - "annotation_string": "annotation_test", - "key_float": 0.3, - } - }) + expected_spans = zipkin_pb2.ListOfSpans( + spans=[ + zipkin_pb2.Span( + trace_id=trace_id.to_bytes( + length=16, byteorder="big", signed=False ), - ] - ), - zipkin_pb2.Span( - trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), - id=ZipkinSpanExporter.format_pbuf_span_id(parent_id), - name=span_names[1], - timestamp=nsec_to_usec_round(start_times[1]), - duration=nsec_to_usec_round(durations[1]), - local_endpoint=local_endpoint, - kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][SpanKind.INTERNAL], - tags={ - "key_resource": "some_resource", - "otel.status_code": "1", - } - ), - zipkin_pb2.Span( - trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), - id=ZipkinSpanExporter.format_pbuf_span_id(other_id), - name=span_names[2], - timestamp=nsec_to_usec_round(start_times[2]), - duration=nsec_to_usec_round(durations[2]), - local_endpoint=local_endpoint, - kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][SpanKind.INTERNAL], - tags={ - "key_string": "hello_world", - "key_resource": "some_resource", - "otel.status_code": "1", - } - ), - zipkin_pb2.Span( - trace_id=trace_id.to_bytes(length=16, byteorder="big", signed=False), - id=ZipkinSpanExporter.format_pbuf_span_id(other_id), - name=span_names[3], - timestamp=nsec_to_usec_round(start_times[3]), - duration=nsec_to_usec_round(durations[3]), - local_endpoint=local_endpoint, - kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][SpanKind.INTERNAL], - tags={ - "otel.instrumentation_library.name": "name", - "otel.instrumentation_library.version": "version", - "otel.status_code": "1", - } - ), - ]) + id=ZipkinSpanExporter.format_pbuf_span_id(span_id), + name=span_names[0], + timestamp=nsec_to_usec_round(start_times[0]), + duration=nsec_to_usec_round(durations[0]), + local_endpoint=local_endpoint, + kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][ + SpanKind.INTERNAL + ], + tags={ + "key_bool": "False", + "key_string": "hello_world", + "key_float": "111.22", + "otel.status_code": "2", + "otel.status_description": "Example description", + }, + debug=True, + parent_id=ZipkinSpanExporter.format_pbuf_span_id( + parent_id + ), + annotations=[ + zipkin_pb2.Annotation( + timestamp=nsec_to_usec_round(event_timestamp), + value=json.dumps( + { + "event0": { + "annotation_bool": True, + "annotation_string": "annotation_test", + "key_float": 0.3, + } + } + ), + ), + ], + ), + zipkin_pb2.Span( + trace_id=trace_id.to_bytes( + length=16, byteorder="big", signed=False + ), + id=ZipkinSpanExporter.format_pbuf_span_id(parent_id), + name=span_names[1], + timestamp=nsec_to_usec_round(start_times[1]), + duration=nsec_to_usec_round(durations[1]), + local_endpoint=local_endpoint, + kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][ + SpanKind.INTERNAL + ], + tags={ + "key_resource": "some_resource", + "otel.status_code": "1", + }, + ), + zipkin_pb2.Span( + trace_id=trace_id.to_bytes( + length=16, byteorder="big", signed=False + ), + id=ZipkinSpanExporter.format_pbuf_span_id(other_id), + name=span_names[2], + timestamp=nsec_to_usec_round(start_times[2]), + duration=nsec_to_usec_round(durations[2]), + local_endpoint=local_endpoint, + kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][ + SpanKind.INTERNAL + ], + tags={ + "key_string": "hello_world", + "key_resource": "some_resource", + "otel.status_code": "1", + }, + ), + zipkin_pb2.Span( + trace_id=trace_id.to_bytes( + length=16, byteorder="big", signed=False + ), + id=ZipkinSpanExporter.format_pbuf_span_id(other_id), + name=span_names[3], + timestamp=nsec_to_usec_round(start_times[3]), + duration=nsec_to_usec_round(durations[3]), + local_endpoint=local_endpoint, + kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][ + SpanKind.INTERNAL + ], + tags={ + "otel.instrumentation_library.name": "name", + "otel.instrumentation_library.version": "version", + "otel.status_code": "1", + }, + ), + ], + ) - exporter = ZipkinSpanExporter(service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF) + exporter = ZipkinSpanExporter( + service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF + ) mock_post = MagicMock() with patch("requests.post", mock_post): mock_post.return_value = MockResponse(200) @@ -642,8 +670,12 @@ def test_export_protobuf(self): kwargs = mock_post.call_args[1] self.assertEqual(kwargs["url"], "http://localhost:9411/api/v2/spans") - self.assertEqual(kwargs["headers"]["Content-Type"], "application/x-protobuf") - self.assertEqual(zipkin_pb2.ListOfSpans.FromString(kwargs["data"]), expected_spans) + self.assertEqual( + kwargs["headers"]["Content-Type"], "application/x-protobuf" + ) + self.assertEqual( + zipkin_pb2.ListOfSpans.FromString(kwargs["data"]), expected_spans + ) def test_export_protobuf_max_tag_length(self): service_name = "test-service" @@ -665,7 +697,9 @@ def test_export_protobuf_max_tag_length(self): span.set_status(Status(StatusCode.ERROR, "Example description")) span.end() - exporter = ZipkinSpanExporter(service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF) + exporter = ZipkinSpanExporter( + service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF, + ) mock_post = MagicMock() with patch("requests.post", mock_post): mock_post.return_value = MockResponse(200) @@ -679,7 +713,11 @@ def test_export_protobuf_max_tag_length(self): self.assertEqual(len(span_tags["k1"]), 128) self.assertEqual(len(span_tags["k2"]), 50) - exporter = ZipkinSpanExporter(service_name, transport_format=TRANSPORT_FORMAT_PROTOBUF, max_tag_value_length=2) + exporter = ZipkinSpanExporter( + service_name, + transport_format=TRANSPORT_FORMAT_PROTOBUF, + max_tag_value_length=2, + ) mock_post = MagicMock() with patch("requests.post", mock_post): mock_post.return_value = MockResponse(200) From bd7eef71a49cbfec3b18816263f05069026d95a5 Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Mon, 2 Nov 2020 13:59:58 -0800 Subject: [PATCH 04/12] moving auto-gen'd files to separate dir in order to add exclusion entry in pyproject.toml for black --- .../src/opentelemetry/exporter/zipkin/__init__.py | 2 +- .../src/opentelemetry/exporter/zipkin/gen/__init__.py | 0 .../src/opentelemetry/exporter/zipkin/{ => gen}/zipkin_pb2.py | 0 .../opentelemetry/exporter/zipkin/{ => gen}/zipkin_pb2.pyi | 0 .../tests/test_zipkin_exporter.py | 4 ++-- pyproject.toml | 1 + 6 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/gen/__init__.py rename exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/{ => gen}/zipkin_pb2.py (100%) rename exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/{ => gen}/zipkin_pb2.pyi (100%) diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py index a2e08d4277..c6a472e4a1 100644 --- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py @@ -69,7 +69,7 @@ import requests -from opentelemetry.exporter.zipkin import zipkin_pb2 +from opentelemetry.exporter.zipkin.gen import zipkin_pb2 from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult from opentelemetry.trace import Span, SpanContext, SpanKind diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/gen/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/gen/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/gen/zipkin_pb2.py similarity index 100% rename from exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.py rename to exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/gen/zipkin_pb2.py diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.pyi b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/gen/zipkin_pb2.pyi similarity index 100% rename from exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/zipkin_pb2.pyi rename to exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/gen/zipkin_pb2.pyi diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py index 7605f62479..c6255f4d40 100644 --- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py +++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py @@ -19,13 +19,13 @@ from opentelemetry import trace as trace_api from opentelemetry.exporter.zipkin import ( - nsec_to_usec_round, SPAN_KIND_MAP, TRANSPORT_FORMAT_JSON, TRANSPORT_FORMAT_PROTOBUF, - zipkin_pb2, ZipkinSpanExporter, + nsec_to_usec_round, ) +from opentelemetry.exporter.zipkin.gen import zipkin_pb2 from opentelemetry.sdk import trace from opentelemetry.sdk.trace import Resource from opentelemetry.sdk.trace.export import SpanExportResult diff --git a/pyproject.toml b/pyproject.toml index 0c79406d05..73e41db223 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ exclude = ''' /( # generated files docs/examples/opentelemetry-example-app/src/opentelemetry_example_app/grpc/gen| exporter/opentelemetry-exporter-jaeger/src/opentelemetry/exporter/jaeger/gen| + exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/gen| opentelemetry-proto/src/opentelemetry/proto/collector| opentelemetry-proto/src/opentelemetry/proto/common| opentelemetry-proto/src/opentelemetry/proto/metrics| From a8a24076fb79927e4b6dad173b06e41d3beeaeac Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Mon, 2 Nov 2020 16:58:42 -0800 Subject: [PATCH 05/12] flake8 config update to exclude new auto-gen files --- .flake8 | 1 + 1 file changed, 1 insertion(+) diff --git a/.flake8 b/.flake8 index 2780677a64..dfc8eacd00 100644 --- a/.flake8 +++ b/.flake8 @@ -18,6 +18,7 @@ exclude = __pycache__ exporter/opentelemetry-exporter-jaeger/src/opentelemetry/exporter/jaeger/gen/ exporter/opentelemetry-exporter-jaeger/build/* + exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/gen docs/examples/opentelemetry-example-app/src/opentelemetry_example_app/grpc/gen/ docs/examples/opentelemetry-example-app/build/* opentelemetry-proto/build/* From f9c4ff883f605c69752e22ad9e33ba79091a119f Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Mon, 2 Nov 2020 18:40:00 -0800 Subject: [PATCH 06/12] pylint hints to help with auto-gen protobuf objects --- .pylintrc | 2 +- .../src/opentelemetry/exporter/zipkin/__init__.py | 1 + .../opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index ea54273868..58fe77eef1 100644 --- a/.pylintrc +++ b/.pylintrc @@ -165,7 +165,7 @@ contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. -generated-members= +generated-members=zipkin_pb2.* # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py index c6a472e4a1..0104fa0ce4 100644 --- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py @@ -278,6 +278,7 @@ def _translate_to_protobuf(self, spans: Sequence[Span]): start_timestamp_mus = nsec_to_usec_round(span.start_time) duration_mus = nsec_to_usec_round(span.end_time - span.start_time) + # pylint: disable=no-member pbuf_span = zipkin_pb2.Span( trace_id=trace_id, id=span_id, diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py index c6255f4d40..c08887f9d0 100644 --- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py +++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py @@ -706,6 +706,7 @@ def test_export_protobuf_max_tag_length(self): status = exporter.export([span]) self.assertEqual(SpanExportResult.SUCCESS, status) + # pylint: disable=unsubscriptable-object kwargs = mock_post.call_args[1] actual_spans = zipkin_pb2.ListOfSpans.FromString(kwargs["data"]) span_tags = actual_spans.spans[0].tags @@ -724,6 +725,7 @@ def test_export_protobuf_max_tag_length(self): status = exporter.export([span]) self.assertEqual(SpanExportResult.SUCCESS, status) + # pylint: disable=unsubscriptable-object kwargs = mock_post.call_args[1] actual_spans = zipkin_pb2.ListOfSpans.FromString(kwargs["data"]) span_tags = actual_spans.spans[0].tags From 8ce0dc9fb192202c79964387ad9f82c5654736e0 Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Mon, 2 Nov 2020 20:07:53 -0800 Subject: [PATCH 07/12] documentation update --- .../src/opentelemetry/exporter/zipkin/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py index 0104fa0ce4..2f0eedf361 100644 --- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py @@ -26,6 +26,9 @@ .. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/ .. _Specification: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-environment-variables.md#zipkin-exporter +.. envvar:: OTEL_EXPORTER_ZIPKIN_ENDPOINT +.. envvar:: OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT + .. code:: python from opentelemetry import trace @@ -55,7 +58,14 @@ with tracer.start_as_current_span("foo"): print("Hello world!") -The exporter supports endpoint configuration via the OTEL_EXPORTER_ZIPKIN_ENDPOINT environment variables as defined in the `Specification`_ +The exporter supports the following environment variables for configuration: + +:envvar:`OTEL_EXPORTER_ZIPKIN_ENDPOINT`: target to which the exporter will +send data. This may include a path (e.g. http://example.com:9411/api/v2/spans). + +:envvar:`OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT`: transport interchange format +to use when sending data. Currently only Zipkin's v2 json and protobuf formats +are supported, with v2 json being the default. API --- From b6d7e6b740be509727bc34dcfbb09772643ffb79 Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Mon, 2 Nov 2020 20:44:04 -0800 Subject: [PATCH 08/12] switching exporter to use OTEL Configuration class instead of direct os.environ access for retrieving env vars --- .../src/opentelemetry/exporter/zipkin/__init__.py | 11 +++++------ .../tests/test_zipkin_exporter.py | 2 ++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py index 2f0eedf361..9b19e1e091 100644 --- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py @@ -73,12 +73,12 @@ import json import logging -import os from typing import Optional, Sequence, Union from urllib.parse import urlparse import requests +from opentelemetry.configuration import Configuration from opentelemetry.exporter.zipkin.gen import zipkin_pb2 from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult from opentelemetry.trace import Span, SpanContext, SpanKind @@ -139,9 +139,7 @@ def __init__( ): self.service_name = service_name if url is None: - self.url = os.environ.get( - "OTEL_EXPORTER_ZIPKIN_ENDPOINT", DEFAULT_URL - ) + self.url = Configuration().EXPORTER_ZIPKIN_ENDPOINT or DEFAULT_URL else: self.url = url @@ -153,8 +151,9 @@ def __init__( self.max_tag_value_length = max_tag_value_length if transport_format is None: - self.transport_format = os.environ.get( - "OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT", TRANSPORT_FORMAT_JSON + self.transport_format = ( + Configuration().EXPORTER_ZIPKIN_TRANSPORT_FORMAT + or TRANSPORT_FORMAT_JSON ) else: self.transport_format = transport_format diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py index c08887f9d0..5f9ccc9559 100644 --- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py +++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py @@ -18,6 +18,7 @@ from unittest.mock import MagicMock, patch from opentelemetry import trace as trace_api +from opentelemetry.configuration import Configuration from opentelemetry.exporter.zipkin import ( SPAN_KIND_MAP, TRANSPORT_FORMAT_JSON, @@ -58,6 +59,7 @@ def tearDown(self): del os.environ["OTEL_EXPORTER_ZIPKIN_ENDPOINT"] if "OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT" in os.environ: del os.environ["OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT"] + Configuration()._reset() def test_constructor_env_var(self): """Test the default values assigned by constructor.""" From 17bf5f7739c22d0a8779366e03f61f43d913fbb5 Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Tue, 3 Nov 2020 11:46:50 -0800 Subject: [PATCH 09/12] refactor of SPAN_KIND map structure and loosening of allowed protobuf pkg version --- .../opentelemetry-exporter-zipkin/setup.cfg | 2 +- .../opentelemetry/exporter/zipkin/__init__.py | 33 +++++++++---------- .../tests/test_zipkin_exporter.py | 31 ++++++++--------- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/exporter/opentelemetry-exporter-zipkin/setup.cfg b/exporter/opentelemetry-exporter-zipkin/setup.cfg index d5ebf488ec..d88717e13b 100644 --- a/exporter/opentelemetry-exporter-zipkin/setup.cfg +++ b/exporter/opentelemetry-exporter-zipkin/setup.cfg @@ -39,7 +39,7 @@ package_dir= =src packages=find_namespace: install_requires = - protobuf == 3.12.2 + protobuf >= 3.12 requests ~= 2.7 opentelemetry-api == 0.16.dev0 opentelemetry-sdk == 0.16.dev0 diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py index 9b19e1e091..51130d60b5 100644 --- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py +++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py @@ -90,21 +90,20 @@ DEFAULT_URL = "http://localhost:9411/api/v2/spans" DEFAULT_MAX_TAG_VALUE_LENGTH = 128 -SPAN_KIND_MAP = { - TRANSPORT_FORMAT_JSON: { - SpanKind.INTERNAL: None, - SpanKind.SERVER: "SERVER", - SpanKind.CLIENT: "CLIENT", - SpanKind.PRODUCER: "PRODUCER", - SpanKind.CONSUMER: "CONSUMER", - }, - TRANSPORT_FORMAT_PROTOBUF: { - SpanKind.INTERNAL: zipkin_pb2.Span.Kind.SPAN_KIND_UNSPECIFIED, - SpanKind.SERVER: zipkin_pb2.Span.Kind.SERVER, - SpanKind.CLIENT: zipkin_pb2.Span.Kind.CLIENT, - SpanKind.PRODUCER: zipkin_pb2.Span.Kind.PRODUCER, - SpanKind.CONSUMER: zipkin_pb2.Span.Kind.CONSUMER, - }, +SPAN_KIND_MAP_JSON = { + SpanKind.INTERNAL: None, + SpanKind.SERVER: "SERVER", + SpanKind.CLIENT: "CLIENT", + SpanKind.PRODUCER: "PRODUCER", + SpanKind.CONSUMER: "CONSUMER", +} + +SPAN_KIND_MAP_PROTOBUF = { + SpanKind.INTERNAL: zipkin_pb2.Span.Kind.SPAN_KIND_UNSPECIFIED, + SpanKind.SERVER: zipkin_pb2.Span.Kind.SERVER, + SpanKind.CLIENT: zipkin_pb2.Span.Kind.CLIENT, + SpanKind.PRODUCER: zipkin_pb2.Span.Kind.PRODUCER, + SpanKind.CONSUMER: zipkin_pb2.Span.Kind.CONSUMER, } SUCCESS_STATUS_CODES = (200, 202) @@ -223,7 +222,7 @@ def _translate_to_json(self, spans: Sequence[Span]): "timestamp": start_timestamp_mus, "duration": duration_mus, "localEndpoint": local_endpoint, - "kind": SPAN_KIND_MAP[TRANSPORT_FORMAT_JSON][span.kind], + "kind": SPAN_KIND_MAP_JSON[span.kind], "tags": self._extract_tags_from_span(span), "annotations": self._extract_annotations_from_events( span.events @@ -295,7 +294,7 @@ def _translate_to_protobuf(self, spans: Sequence[Span]): timestamp=start_timestamp_mus, duration=duration_mus, local_endpoint=local_endpoint, - kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][span.kind], + kind=SPAN_KIND_MAP_PROTOBUF[span.kind], tags=self._extract_tags_from_span(span), ) diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py index 5f9ccc9559..460329b77a 100644 --- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py +++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py @@ -20,7 +20,8 @@ from opentelemetry import trace as trace_api from opentelemetry.configuration import Configuration from opentelemetry.exporter.zipkin import ( - SPAN_KIND_MAP, + SPAN_KIND_MAP_JSON, + SPAN_KIND_MAP_PROTOBUF, TRANSPORT_FORMAT_JSON, TRANSPORT_FORMAT_PROTOBUF, ZipkinSpanExporter, @@ -229,6 +230,7 @@ def test_export_json(self): service_name = "test-service" local_endpoint = {"serviceName": service_name, "port": 9411} + span_kind = SPAN_KIND_MAP_JSON[SpanKind.INTERNAL] exporter = ZipkinSpanExporter(service_name) expected_spans = [ @@ -239,7 +241,7 @@ def test_export_json(self): "timestamp": start_times[0] // 10 ** 3, "duration": durations[0] // 10 ** 3, "localEndpoint": local_endpoint, - "kind": None, + "kind": span_kind, "tags": { "key_bool": "False", "key_string": "hello_world", @@ -269,7 +271,7 @@ def test_export_json(self): "timestamp": start_times[1] // 10 ** 3, "duration": durations[1] // 10 ** 3, "localEndpoint": local_endpoint, - "kind": None, + "kind": span_kind, "tags": { "key_resource": "some_resource", "otel.status_code": "1", @@ -283,7 +285,7 @@ def test_export_json(self): "timestamp": start_times[2] // 10 ** 3, "duration": durations[2] // 10 ** 3, "localEndpoint": local_endpoint, - "kind": None, + "kind": span_kind, "tags": { "key_string": "hello_world", "key_resource": "some_resource", @@ -298,7 +300,7 @@ def test_export_json(self): "timestamp": start_times[3] // 10 ** 3, "duration": durations[3] // 10 ** 3, "localEndpoint": local_endpoint, - "kind": None, + "kind": span_kind, "tags": { "otel.instrumentation_library.name": "name", "otel.instrumentation_library.version": "version", @@ -380,7 +382,7 @@ def test_export_json_zero_padding(self): "timestamp": start_time // 10 ** 3, "duration": duration // 10 ** 3, "localEndpoint": local_endpoint, - "kind": None, + "kind": SPAN_KIND_MAP_JSON[SpanKind.INTERNAL], "tags": {"otel.status_code": "1"}, "annotations": None, "debug": True, @@ -562,6 +564,7 @@ def test_export_protobuf(self): local_endpoint = zipkin_pb2.Endpoint( service_name=service_name, port=9411 ) + span_kind = SPAN_KIND_MAP_PROTOBUF[SpanKind.INTERNAL] expected_spans = zipkin_pb2.ListOfSpans( spans=[ @@ -574,9 +577,7 @@ def test_export_protobuf(self): timestamp=nsec_to_usec_round(start_times[0]), duration=nsec_to_usec_round(durations[0]), local_endpoint=local_endpoint, - kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][ - SpanKind.INTERNAL - ], + kind=span_kind, tags={ "key_bool": "False", "key_string": "hello_world", @@ -612,9 +613,7 @@ def test_export_protobuf(self): timestamp=nsec_to_usec_round(start_times[1]), duration=nsec_to_usec_round(durations[1]), local_endpoint=local_endpoint, - kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][ - SpanKind.INTERNAL - ], + kind=span_kind, tags={ "key_resource": "some_resource", "otel.status_code": "1", @@ -629,9 +628,7 @@ def test_export_protobuf(self): timestamp=nsec_to_usec_round(start_times[2]), duration=nsec_to_usec_round(durations[2]), local_endpoint=local_endpoint, - kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][ - SpanKind.INTERNAL - ], + kind=span_kind, tags={ "key_string": "hello_world", "key_resource": "some_resource", @@ -647,9 +644,7 @@ def test_export_protobuf(self): timestamp=nsec_to_usec_round(start_times[3]), duration=nsec_to_usec_round(durations[3]), local_endpoint=local_endpoint, - kind=SPAN_KIND_MAP[TRANSPORT_FORMAT_PROTOBUF][ - SpanKind.INTERNAL - ], + kind=span_kind, tags={ "otel.instrumentation_library.name": "name", "otel.instrumentation_library.version": "version", From 980b028300701f6071b1ea751f3edc239aeb9c5f Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Tue, 3 Nov 2020 11:59:24 -0800 Subject: [PATCH 10/12] pylint disable directive --- .../opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py index 460329b77a..bdbb3f7702 100644 --- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py +++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py @@ -60,7 +60,7 @@ def tearDown(self): del os.environ["OTEL_EXPORTER_ZIPKIN_ENDPOINT"] if "OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT" in os.environ: del os.environ["OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT"] - Configuration()._reset() + Configuration()._reset() # pylint: disable=protected-access def test_constructor_env_var(self): """Test the default values assigned by constructor.""" From c6ce2a431969bc91d0194f052421ca8e4f46b6b6 Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Tue, 3 Nov 2020 13:29:30 -0800 Subject: [PATCH 11/12] docstring format correction --- .../opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py index bdbb3f7702..bbc6ccf7ce 100644 --- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py +++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py @@ -60,7 +60,7 @@ def tearDown(self): del os.environ["OTEL_EXPORTER_ZIPKIN_ENDPOINT"] if "OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT" in os.environ: del os.environ["OTEL_EXPORTER_ZIPKIN_TRANSPORT_FORMAT"] - Configuration()._reset() # pylint: disable=protected-access + Configuration()._reset() # pylint: disable=protected-access def test_constructor_env_var(self): """Test the default values assigned by constructor.""" From d440819c9fe400b3b09a1375591c034adbd6e1f1 Mon Sep 17 00:00:00 2001 From: Rob Knox Date: Sun, 15 Nov 2020 17:30:12 -0800 Subject: [PATCH 12/12] changelog --- exporter/opentelemetry-exporter-zipkin/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exporter/opentelemetry-exporter-zipkin/CHANGELOG.md b/exporter/opentelemetry-exporter-zipkin/CHANGELOG.md index 60d7fbe2ec..972096a8ef 100644 --- a/exporter/opentelemetry-exporter-zipkin/CHANGELOG.md +++ b/exporter/opentelemetry-exporter-zipkin/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Support for v2 api protobuf format ([#1318](https://github.com/open-telemetry/opentelemetry-python/pull/1318)) + ## Version 0.14b0 Released 2020-10-13