def test_headers_with_tracestate(self): """When there is a traceparent and tracestate header, data from both should be addded to the SpanContext. """ traceparent_value = "00-{trace_id}-{span_id}-00".format( trace_id=format(self.TRACE_ID, "032x"), span_id=format(self.SPAN_ID, "016x"), ) tracestate_value = "foo=1,bar=2,baz=3" span_context = get_span_from_context( FORMAT.extract( get_as_list, { "traceparent": [traceparent_value], "tracestate": [tracestate_value], }, )).get_context() self.assertEqual(span_context.trace_id, self.TRACE_ID) self.assertEqual(span_context.span_id, self.SPAN_ID) self.assertEqual(span_context.trace_state, { "foo": "1", "bar": "2", "baz": "3" }) self.assertTrue(span_context.is_remote) output = {} # type:typing.Dict[str, str] span = trace.DefaultSpan(span_context) ctx = set_span_in_context(span) FORMAT.inject(dict.__setitem__, output, ctx) self.assertEqual(output["traceparent"], traceparent_value) for pair in ["foo=1", "bar=2", "baz=3"]: self.assertIn(pair, output["tracestate"]) self.assertEqual(output["tracestate"].count(","), 2)
def test_sampling_priority_auto_reject(self): """Test sampling priority rejected.""" parent_context = get_span_from_context( FORMAT.extract( get_as_list, { FORMAT.TRACE_ID_KEY: self.serialized_trace_id, FORMAT.PARENT_ID_KEY: self.serialized_parent_id, FORMAT.SAMPLING_PRIORITY_KEY: str(constants.AUTO_REJECT), }, ) ).get_context() self.assertEqual(parent_context.trace_flags, constants.AUTO_REJECT) child = trace.Span( "child", trace_api.SpanContext( parent_context.trace_id, trace.generate_span_id(), is_remote=False, trace_flags=parent_context.trace_flags, trace_state=parent_context.trace_state, ), parent=parent_context, ) child_carrier = {} child_context = set_span_in_context(child) FORMAT.inject(dict.__setitem__, child_carrier, context=child_context) self.assertEqual( child_carrier[FORMAT.SAMPLING_PRIORITY_KEY], str(constants.AUTO_REJECT), )
def inject( self, set_in_carrier: httptextformat.Setter[httptextformat.HTTPTextFormatT], carrier: httptextformat.HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: """Injects SpanContext into the carrier. See `opentelemetry.trace.propagation.httptextformat.HTTPTextFormat.inject` """ span_context = get_span_from_context(context).get_context() if span_context == trace.INVALID_SPAN_CONTEXT: return traceparent_string = "00-{:032x}-{:016x}-{:02x}".format( span_context.trace_id, span_context.span_id, span_context.trace_flags, ) set_in_carrier(carrier, self._TRACEPARENT_HEADER_NAME, traceparent_string) if span_context.trace_state: tracestate_string = _format_tracestate(span_context.trace_state) set_in_carrier(carrier, self._TRACESTATE_HEADER_NAME, tracestate_string)
def test_invalid_parent_id(self): """If the parent id is invalid, we must ignore the full traceparent header. Also ignore any tracestate. RFC 3.2.2.3 Vendors MUST ignore the traceparent when the parent-id is invalid (for example, if it contains non-lowercase hex characters). RFC 3.3 If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate. Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ span = get_span_from_context( FORMAT.extract( get_as_list, { "traceparent": [ "00-00000000000000000000000000000000-0000000000000000-00" ], "tracestate": ["foo=1,bar=2,foo=3"], }, )) self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
def test_invalid_trace_id(self): """If the trace id is invalid, we must ignore the full traceparent header, and return a random, valid trace. Also ignore any tracestate. RFC 3.2.2.3 If the trace-id value is invalid (for example if it contains non-allowed characters or all zeros), vendors MUST ignore the traceparent. RFC 3.3 If the vendor failed to parse traceparent, it MUST NOT attempt to parse tracestate. Note that the opposite is not true: failure to parse tracestate MUST NOT affect the parsing of traceparent. """ span = get_span_from_context( FORMAT.extract( get_as_list, { "traceparent": [ "00-00000000000000000000000000000000-1234567890123456-00" ], "tracestate": ["foo=1,bar=2,foo=3"], }, )) self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
def __init__(self, span=None, name="span", **kwargs): # type: (Optional[Span], Optional[str], Any) -> None current_tracer = self.get_current_tracer() ## kind value = kwargs.pop('kind', None) kind = ( OpenTelemetrySpanKind.CLIENT if value == SpanKind.CLIENT else OpenTelemetrySpanKind.PRODUCER if value == SpanKind.PRODUCER else OpenTelemetrySpanKind.SERVER if value == SpanKind.SERVER else OpenTelemetrySpanKind.CONSUMER if value == SpanKind.CONSUMER else OpenTelemetrySpanKind.INTERNAL if value == SpanKind.INTERNAL else OpenTelemetrySpanKind.INTERNAL if value == SpanKind.UNSPECIFIED else None) # type: SpanKind if value and kind is None: raise ValueError( "Kind {} is not supported in OpenTelemetry".format(value)) links = kwargs.pop('links', None) if links: try: ot_links = [] for link in links: ctx = extract(link.headers) span_ctx = get_span_from_context(ctx).get_span_context() ot_links.append( OpenTelemetryLink(span_ctx, link.attributes)) kwargs.setdefault('links', ot_links) except AttributeError: # we will just send the links as is if it's not ~azure.core.tracing.Link without any validation # assuming user knows what they are doing. kwargs.setdefault('links', links) self._span_instance = span or current_tracer.start_span( name=name, kind=kind, **kwargs) self._current_ctxt_manager = None
def inject( self, set_in_carrier: Setter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: span = get_span_from_context(context=context) sampled = (trace.TraceFlags.SAMPLED & span.context.trace_flags) != 0 set_in_carrier( carrier, self.TRACE_ID_KEY, format_trace_id(span.context.trace_id), ) set_in_carrier(carrier, self.PARENT_ID_KEY, format_span_id(span.context.span_id)) set_in_carrier( carrier, self.SAMPLING_PRIORITY_KEY, str(constants.AUTO_KEEP if sampled else constants.AUTO_REJECT), ) if constants.DD_ORIGIN in span.context.trace_state: set_in_carrier( carrier, self.ORIGIN_KEY, span.context.trace_state[constants.DD_ORIGIN], )
def test_propagation(self): traceparent_value = "00-{trace_id}-{span_id}-00".format( trace_id=format(self.TRACE_ID, "032x"), span_id=format(self.SPAN_ID, "016x"), ) tracestate_value = "foo=1,bar=2,baz=3" headers = { "otcorrelationcontext": ["key1=val1,key2=val2"], "traceparent": [traceparent_value], "tracestate": [tracestate_value], } ctx = extract(get_as_list, headers) correlations = correlationcontext.get_correlations(context=ctx) expected = {"key1": "val1", "key2": "val2"} self.assertEqual(correlations, expected) span_context = get_span_from_context(context=ctx).get_context() self.assertEqual(span_context.trace_id, self.TRACE_ID) self.assertEqual(span_context.span_id, self.SPAN_ID) span = trace.DefaultSpan(span_context) ctx = correlationcontext.set_correlation("key3", "val3") ctx = correlationcontext.set_correlation("key4", "val4", context=ctx) ctx = set_span_in_context(span, context=ctx) output = {} inject(dict.__setitem__, output, context=ctx) self.assertEqual(traceparent_value, output["traceparent"]) self.assertIn("key3=val3", output["otcorrelationcontext"]) self.assertIn("key4=val4", output["otcorrelationcontext"]) self.assertIn("foo=1", output["tracestate"]) self.assertIn("bar=2", output["tracestate"]) self.assertIn("baz=3", output["tracestate"])
def test_invalid_single_header(self): """If an invalid single header is passed, return an invalid SpanContext. """ carrier = {FORMAT.SINGLE_HEADER_KEY: "0-1-2-3-4-5-6-7"} ctx = FORMAT.extract(get_as_list, carrier) span_context = get_span_from_context(ctx).get_context() self.assertEqual(span_context.trace_id, trace_api.INVALID_TRACE_ID) self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID)
def test_missing_parent_id(self): """If a parent id is missing, populate an invalid trace id.""" carrier = { FORMAT.TRACE_ID_KEY: self.serialized_trace_id, } ctx = FORMAT.extract(get_as_list, carrier) span_context = get_span_from_context(ctx).get_context() self.assertEqual(span_context.span_id, trace_api.INVALID_SPAN_ID)
def test_context_propagation(self): """Test the propagation of Datadog headers.""" parent_context = get_span_from_context( FORMAT.extract( get_as_list, { FORMAT.TRACE_ID_KEY: self.serialized_trace_id, FORMAT.PARENT_ID_KEY: self.serialized_parent_id, FORMAT.SAMPLING_PRIORITY_KEY: str(constants.AUTO_KEEP), FORMAT.ORIGIN_KEY: self.serialized_origin, }, ) ).get_context() self.assertEqual( parent_context.trace_id, int(self.serialized_trace_id) ) self.assertEqual( parent_context.span_id, int(self.serialized_parent_id) ) self.assertEqual(parent_context.trace_flags, constants.AUTO_KEEP) self.assertEqual( parent_context.trace_state.get(constants.DD_ORIGIN), self.serialized_origin, ) self.assertTrue(parent_context.is_remote) child = trace.Span( "child", trace_api.SpanContext( parent_context.trace_id, trace.generate_span_id(), is_remote=False, trace_flags=parent_context.trace_flags, trace_state=parent_context.trace_state, ), parent=parent_context, ) child_carrier = {} child_context = set_span_in_context(child) FORMAT.inject(dict.__setitem__, child_carrier, context=child_context) self.assertEqual( child_carrier[FORMAT.TRACE_ID_KEY], self.serialized_trace_id ) self.assertEqual( child_carrier[FORMAT.PARENT_ID_KEY], str(child.context.span_id) ) self.assertEqual( child_carrier[FORMAT.SAMPLING_PRIORITY_KEY], str(constants.AUTO_KEEP), ) self.assertEqual( child_carrier.get(FORMAT.ORIGIN_KEY), self.serialized_origin )
def inject( self, set_in_carrier: Setter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: span = get_span_from_context(context) set_in_carrier(carrier, self.TRACE_ID_KEY, str(span.get_context().trace_id)) set_in_carrier(carrier, self.SPAN_ID_KEY, str(span.get_context().span_id))
def test_no_traceparent_header(self): """When tracecontext headers are not present, a new SpanContext should be created. RFC 4.2.2: If no traceparent header is received, the vendor creates a new trace-id and parent-id that represents the current request. """ output = {} # type:typing.Dict[str, typing.List[str]] span = get_span_from_context(FORMAT.extract(get_as_list, output)) self.assertIsInstance(span.get_context(), trace.SpanContext)
def link_from_headers(cls, headers, attributes=None): # type: (Dict[str, str], Attributes) -> None """ Given a dictionary, extracts the context and links the context to the current tracer. :param headers: A key value pair dictionary :type headers: dict """ ctx = extract(_get_headers_from_http_request_headers, headers) span_ctx = get_span_from_context(ctx).get_context() current_span = cls.get_current_span() current_span.links.append(Link(span_ctx, attributes))
def test_tracestate_header_with_trailing_comma(self): """Do not propagate invalid trace context. """ span = get_span_from_context( FORMAT.extract( get_as_list, { "traceparent": [ "00-12345678901234567890123456789012-1234567890123456-00" ], "tracestate": ["foo=1,"], }, )) self.assertEqual(span.get_context().trace_state["foo"], "1")
def test_tracestate_empty_header(self): """Test tracestate with an additional empty header (should be ignored) """ span = get_span_from_context( FORMAT.extract( get_as_list, { "traceparent": [ "00-12345678901234567890123456789012-1234567890123456-00" ], "tracestate": ["foo=1", ""], }, )) self.assertEqual(span.get_context().trace_state["foo"], "1")
def test_malformed_headers(self): """Test with no Datadog headers""" malformed_trace_id_key = FORMAT.TRACE_ID_KEY + "-x" malformed_parent_id_key = FORMAT.PARENT_ID_KEY + "-x" context = get_span_from_context( FORMAT.extract( get_as_list, { malformed_trace_id_key: self.serialized_trace_id, malformed_parent_id_key: self.serialized_parent_id, }, ) ).get_context() self.assertNotEqual(context.trace_id, int(self.serialized_trace_id)) self.assertNotEqual(context.span_id, int(self.serialized_parent_id)) self.assertFalse(context.is_remote)
def link_from_headers(cls, headers, attributes=None): # type: (Dict[str, str], Attributes) -> None """ Given a dictionary, extracts the context and links the context to the current tracer. :param headers: A key value pair dictionary :type headers: dict """ ctx = extract(headers) span_ctx = get_span_from_context(ctx).get_span_context() current_span = cls.get_current_span() try: current_span._links.append(OpenTelemetryLink(span_ctx, attributes)) # pylint: disable=protected-access except AttributeError: warnings.warn( """Link must be added while creating the span for Opentelemetry. It might be possible that one of the packages you are using doesn't follow the latest Opentelemtry Spec. Try updating the azure packages to the latest versions.""")
def test_format_not_supported(self): """If the traceparent does not adhere to the supported format, discard it and create a new tracecontext. RFC 4.3 If the version cannot be parsed, return an invalid trace header. """ span = get_span_from_context( FORMAT.extract( get_as_list, { "traceparent": [ "00-12345678901234567890123456789012-" "1234567890123456-00-residue" ], "tracestate": ["foo=1,bar=2,foo=3"], }, )) self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT)
def extract(self, format, carrier): """Implements the ``extract`` method from the base class.""" # TODO: Finish documentation. # pylint: disable=redefined-builtin # This implementation does not perform the extracing by itself but # uses the configured propagators in opentelemetry.propagators. # TODO: Support Format.BINARY once it is supported in # opentelemetry-python. if format not in self._supported_formats: raise opentracing.UnsupportedFormatException def get_as_list(dict_object, key): value = dict_object.get(key) return [value] if value is not None else [] propagator = propagators.get_global_httptextformat() ctx = propagator.extract(get_as_list, carrier) otel_context = get_span_from_context(ctx).get_context() return SpanContextShim(otel_context)
def get_child_parent_new_carrier(old_carrier): ctx = FORMAT.extract(get_as_list, old_carrier) parent_context = get_span_from_context(ctx).get_context() parent = trace.Span("parent", parent_context) child = trace.Span( "child", trace_api.SpanContext( parent_context.trace_id, trace.generate_span_id(), trace_flags=parent_context.trace_flags, trace_state=parent_context.trace_state, ), parent=parent, ) new_carrier = {} ctx = set_span_in_context(child) FORMAT.inject(dict.__setitem__, new_carrier, context=ctx) return child, parent, new_carrier
def inject( self, set_in_carrier: Setter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> None: span = get_span_from_context(context=context) sampled = (trace.TraceFlags.SAMPLED & span.context.trace_flags) != 0 set_in_carrier( carrier, self.TRACE_ID_KEY, format_trace_id(span.context.trace_id), ) set_in_carrier(carrier, self.SPAN_ID_KEY, format_span_id(span.context.span_id)) if span.parent is not None: set_in_carrier( carrier, self.PARENT_SPAN_ID_KEY, format_span_id(span.parent.context.span_id), ) set_in_carrier(carrier, self.SAMPLED_KEY, "1" if sampled else "0")
def test_tracestate_keys(self): """Test for valid key patterns in the tracestate """ tracestate_value = ",".join([ "1a-2f@foo=bar1", "1a-_*/2b@foo=bar2", "foo=bar3", "foo-_*/bar=bar4", ]) span = get_span_from_context( FORMAT.extract( get_as_list, { "traceparent": [ "00-12345678901234567890123456789012-1234567890123456-00" ], "tracestate": [tracestate_value], }, )) self.assertEqual(span.get_context().trace_state["1a-2f@foo"], "bar1") self.assertEqual(span.get_context().trace_state["1a-_*/2b@foo"], "bar2") self.assertEqual(span.get_context().trace_state["foo"], "bar3") self.assertEqual(span.get_context().trace_state["foo-_*/bar"], "bar4")
def get_current_span(cls): # type: () -> Span """ Get the current span from the execution context. Return None otherwise. """ return get_span_from_context()