def extract( self, carrier: textmap.CarrierT, context: typing.Optional[Context] = None, getter: textmap.Getter = textmap.default_getter, ) -> Context: if context is None: context = Context() header = self._get_header_value(getter, carrier) if not header: return context match = re.fullmatch(_TRACE_CONTEXT_HEADER_RE, header) if match is None: return context trace_id = match.group("trace_id") span_id = match.group("span_id") trace_options = match.group("trace_flags") if trace_id == "0" * 32 or int(span_id) == 0: return context span_context = SpanContext( trace_id=int(trace_id, 16), span_id=int(span_id), is_remote=True, trace_flags=TraceFlags(trace_options), ) return trace.set_span_in_context(trace.NonRecordingSpan(span_context), context)
def extract( self, getter: Getter[TextMapPropagatorT], carrier: TextMapPropagatorT, context: typing.Optional[Context] = None, ) -> Context: if context is None: context = get_current() header = getter.get(carrier, self.TRACE_ID_KEY) if not header: return trace.set_span_in_context(trace.INVALID_SPAN, context) fields = _extract_first_element(header).split(":") context = self._extract_baggage(getter, carrier, context) if len(fields) != 4: return trace.set_span_in_context(trace.INVALID_SPAN, context) trace_id, span_id, _parent_id, flags = fields if (trace_id == trace.INVALID_TRACE_ID or span_id == trace.INVALID_SPAN_ID): return trace.set_span_in_context(trace.INVALID_SPAN, context) span = trace.NonRecordingSpan( trace.SpanContext( trace_id=int(trace_id, 16), span_id=int(span_id, 16), is_remote=True, trace_flags=trace.TraceFlags( int(flags, 16) & trace.TraceFlags.SAMPLED), )) return trace.set_span_in_context(span, context)
def extract( self, carrier: CarrierT, context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: if context is None: context = Context() header = getter.get(carrier, self.TRACE_ID_KEY) if not header: return context context = self._extract_baggage(getter, carrier, context) trace_id, span_id, flags = _parse_trace_id_header(header) if (trace_id == trace.INVALID_TRACE_ID or span_id == trace.INVALID_SPAN_ID): return context span = trace.NonRecordingSpan( trace.SpanContext( trace_id=trace_id, span_id=span_id, is_remote=True, trace_flags=trace.TraceFlags(flags & trace.TraceFlags.SAMPLED), )) return trace.set_span_in_context(span, context)
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 = { "baggage": ["key1=val1,key2=val2"], "traceparent": [traceparent_value], "tracestate": [tracestate_value], } ctx = extract(carrier_getter, headers) baggage_entries = baggage.get_all(context=ctx) expected = {"key1": "val1", "key2": "val2"} self.assertEqual(baggage_entries, expected) span_context = get_current_span(context=ctx).get_span_context() self.assertEqual(span_context.trace_id, self.TRACE_ID) self.assertEqual(span_context.span_id, self.SPAN_ID) span = trace.NonRecordingSpan(span_context) ctx = baggage.set_baggage("key3", "val3") ctx = baggage.set_baggage("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["baggage"]) self.assertIn("key4=val4", output["baggage"]) self.assertIn("foo=1", output["tracestate"]) self.assertIn("bar=2", output["tracestate"]) self.assertIn("baz=3", output["tracestate"])
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 = trace.get_current_span( FORMAT.extract( { "traceparent": [traceparent_value], "tracestate": [tracestate_value], }, ) ).get_span_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.NonRecordingSpan(span_context) ctx = trace.set_span_in_context(span) FORMAT.inject(output, context=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_use_span_exception(self): class TestUseSpanException(Exception): pass default_span = trace_api.NonRecordingSpan( trace_api.INVALID_SPAN_CONTEXT) tracer = new_tracer() with self.assertRaises(TestUseSpanException): with tracer.use_span(default_span): raise TestUseSpanException()
def test_ctor(self): context = trace.SpanContext( 1, 1, is_remote=False, trace_flags=trace.DEFAULT_TRACE_OPTIONS, trace_state=trace.DEFAULT_TRACE_STATE, ) span = trace.NonRecordingSpan(context) self.assertEqual(context, span.get_span_context())
def _create_parent_span(trace_flags: trace.TraceFlags, is_remote=False, trace_state=None) -> trace.NonRecordingSpan: return trace.NonRecordingSpan( trace.SpanContext( 0xDEADBEEF, 0xDEADBEF0, is_remote=is_remote, trace_flags=trace_flags, trace_state=trace_state, ))
def build_test_current_context( trace_id=int(TRACE_ID_BASE16, 16), span_id=int(SPAN_ID_BASE16, 16), is_remote=True, trace_flags=DEFAULT_TRACE_OPTIONS, trace_state=DEFAULT_TRACE_STATE, ): return set_span_in_context( trace_api.NonRecordingSpan( build_test_span_context(trace_id, span_id, is_remote, trace_flags, trace_state)))
def test_get_current_span(self): """_DefaultTracer's start_span will also be retrievable via get_current_span """ self.assertEqual(trace.get_current_span(), trace.INVALID_SPAN) span = trace.NonRecordingSpan(trace.INVALID_SPAN_CONTEXT) ctx = trace.set_span_in_context(span) token = context.attach(ctx) try: self.assertIs(trace.get_current_span(), span) finally: context.detach(token) self.assertEqual(trace.get_current_span(), trace.INVALID_SPAN)
def extract( self, carrier: textmap.CarrierT, context: typing.Optional[Context] = None, getter: textmap.Getter = textmap.default_getter, ) -> Context: """Extracts SpanContext from the carrier. See `opentelemetry.propagators.textmap.TextMapPropagator.extract` """ if context is None: context = Context() header = getter.get(carrier, self._TRACEPARENT_HEADER_NAME) if not header: return context match = re.search(self._TRACEPARENT_HEADER_FORMAT_RE, header[0]) if not match: return context version = match.group(1) trace_id = match.group(2) span_id = match.group(3) trace_flags = match.group(4) if trace_id == "0" * 32 or span_id == "0" * 16: return context if version == "00": if match.group(5): return context if version == "ff": return context tracestate_headers = getter.get(carrier, self._TRACESTATE_HEADER_NAME) if tracestate_headers is None: tracestate = None else: tracestate = TraceState.from_header(tracestate_headers) span_context = trace.SpanContext( trace_id=int(trace_id, 16), span_id=int(span_id, 16), is_remote=True, trace_flags=trace.TraceFlags(trace_flags), trace_state=tracestate, ) return trace.set_span_in_context(trace.NonRecordingSpan(span_context), context)
def extract( self, carrier: CarrierT, context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: if context is None: context = Context() trace_header_list = getter.get(carrier, TRACE_HEADER_KEY) if not trace_header_list or len(trace_header_list) != 1: return context trace_header = trace_header_list[0] if not trace_header: return context try: ( trace_id, span_id, sampled, ) = AwsXRayPropagator._extract_span_properties(trace_header) except AwsParseTraceHeaderError as err: _logger.debug(err.message) return context options = 0 if sampled: options |= trace.TraceFlags.SAMPLED span_context = trace.SpanContext( trace_id=trace_id, span_id=span_id, is_remote=True, trace_flags=trace.TraceFlags(options), trace_state=trace.TraceState(), ) if not span_context.is_valid: _logger.debug( "Invalid Span Extracted. Inserting INVALID span into provided context." ) return context return trace.set_span_in_context( trace.NonRecordingSpan(span_context), context=context )
def test_no_send_empty_tracestate(self): """If the tracestate is empty, do not set the header. RFC 3.3.1.1 Empty and whitespace-only list members are allowed. Vendors MUST accept empty tracestate headers but SHOULD avoid sending them. """ output = {} # type:typing.Dict[str, str] span = trace.NonRecordingSpan( trace.SpanContext(self.TRACE_ID, self.SPAN_ID, is_remote=False)) ctx = trace.set_span_in_context(span) FORMAT.inject(output, context=ctx) self.assertTrue("traceparent" in output) self.assertFalse("tracestate" in output)
def test_inject_with_valid_context(self): span_context = SpanContext( trace_id=self.valid_trace_id, span_id=self.valid_span_id, is_remote=True, trace_flags=TraceFlags(1), ) output = self._inject(trace.NonRecordingSpan(span_context)) self.assertEqual( output, "{}/{};o={}".format( format_trace_id(self.valid_trace_id), self.valid_span_id, 1, ), )
def extract( self, getter: Getter[TextMapPropagatorT], carrier: TextMapPropagatorT, context: typing.Optional[Context] = None, ) -> Context: trace_id_list = getter.get(carrier, self.TRACE_ID_KEY) span_id_list = getter.get(carrier, self.SPAN_ID_KEY) if not trace_id_list or not span_id_list: return trace.set_span_in_context(trace.INVALID_SPAN) return trace.set_span_in_context( trace.NonRecordingSpan( trace.SpanContext( trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]), is_remote=True, )))
def extract( self, carrier: CarrierT, context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: if context is None: context = Context() trace_id = extract_first_element(getter.get(carrier, self.TRACE_ID_KEY)) span_id = extract_first_element(getter.get(carrier, self.PARENT_ID_KEY)) sampled = extract_first_element( getter.get(carrier, self.SAMPLING_PRIORITY_KEY)) origin = extract_first_element(getter.get(carrier, self.ORIGIN_KEY)) trace_flags = trace.TraceFlags() if sampled and int(sampled) in ( constants.AUTO_KEEP, constants.USER_KEEP, ): trace_flags = trace.TraceFlags(trace.TraceFlags.SAMPLED) if trace_id is None or span_id is None: return context trace_state = [] if origin is not None: trace_state.append((constants.DD_ORIGIN, origin)) span_context = trace.SpanContext( trace_id=int(trace_id), span_id=int(span_id), is_remote=True, trace_flags=trace_flags, trace_state=trace.TraceState(trace_state), ) return set_span_in_context(trace.NonRecordingSpan(span_context), context)
def test_inject_not_sampled(self): span = trace.NonRecordingSpan( trace.SpanContext( trace_id=1, span_id=2, is_remote=False, trace_flags=trace.TraceFlags(0), trace_state=trace.DEFAULT_TRACE_STATE, ), ) ctx = trace.set_span_in_context(span) prop = _ServerTimingResponsePropagator() carrier = {} prop.inject(carrier, ctx) self.assertEqual(carrier["Access-Control-Expose-Headers"], "Server-Timing") self.assertEqual( carrier["Server-Timing"], 'traceparent;desc="00-00000000000000000000000000000001-0000000000000002-00"', )
def test_inject(self): span = trace.NonRecordingSpan( trace.SpanContext( trace_id=1, span_id=2, is_remote=False, trace_flags=trace.DEFAULT_TRACE_OPTIONS, trace_state=trace.DEFAULT_TRACE_STATE, ), ) ctx = trace.set_span_in_context(span) prop = TraceResponsePropagator() carrier = {} prop.inject(carrier, ctx) self.assertEqual(carrier["Access-Control-Expose-Headers"], "traceresponse") self.assertEqual( carrier["traceresponse"], "00-00000000000000000000000000000001-0000000000000002-00", )
def extract( self, carrier: CarrierT, context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: if context is None: context = Context() trace_id_list = getter.get(carrier, self.TRACE_ID_KEY) span_id_list = getter.get(carrier, self.SPAN_ID_KEY) if not trace_id_list or not span_id_list: return context return trace.set_span_in_context( trace.NonRecordingSpan( trace.SpanContext( trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]), is_remote=True, )), context, )
def test_use_span(self): span = trace.NonRecordingSpan(trace.INVALID_SPAN_CONTEXT) with self.tracer.use_span(span): pass
def start_span( # pylint: disable=too-many-locals self, name: str, context: Optional[context_api.Context] = None, kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL, attributes: types.Attributes = None, links: Sequence[trace_api.Link] = (), start_time: Optional[int] = None, record_exception: bool = True, set_status_on_exception: bool = True, ) -> trace_api.Span: parent_span_context = trace_api.get_current_span( context ).get_span_context() if parent_span_context is not None and not isinstance( parent_span_context, trace_api.SpanContext ): raise TypeError( "parent_span_context must be a SpanContext or None." ) # is_valid determines root span if parent_span_context is None or not parent_span_context.is_valid: parent_span_context = None trace_id = self.id_generator.generate_trace_id() else: trace_id = parent_span_context.trace_id # The sampler decides whether to create a real or no-op span at the # time of span creation. No-op spans do not record events, and are not # exported. # The sampler may also add attributes to the newly-created span, e.g. # to include information about the sampling result. # The sampler may also modify the parent span context's tracestate sampling_result = self.sampler.should_sample( context, trace_id, name, kind, attributes, links ) trace_flags = ( trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED) if sampling_result.decision.is_sampled() else trace_api.TraceFlags(trace_api.TraceFlags.DEFAULT) ) span_context = trace_api.SpanContext( trace_id, self.id_generator.generate_span_id(), is_remote=False, trace_flags=trace_flags, trace_state=sampling_result.trace_state, ) # Only record if is_recording() is true if sampling_result.decision.is_recording(): # pylint:disable=protected-access span = _Span( name=name, context=span_context, parent=parent_span_context, sampler=self.sampler, resource=self.resource, attributes=sampling_result.attributes.copy(), span_processor=self.span_processor, kind=kind, links=links, instrumentation_info=self.instrumentation_info, record_exception=record_exception, set_status_on_exception=set_status_on_exception, limits=self._span_limits, ) span.start(start_time=start_time, parent_context=context) else: span = trace_api.NonRecordingSpan(context=span_context) return span
def extract( self, carrier: CarrierT, context: typing.Optional[Context] = None, getter: Getter = default_getter, ) -> Context: if context is None: context = Context() trace_id = trace.INVALID_TRACE_ID span_id = trace.INVALID_SPAN_ID sampled = "0" flags = None single_header = _extract_first_element( getter.get(carrier, self.SINGLE_HEADER_KEY)) if single_header: # The b3 spec calls for the sampling state to be # "deferred", which is unspecified. This concept does not # translate to SpanContext, so we set it as recorded. sampled = "1" fields = single_header.split("-", 4) if len(fields) == 1: sampled = fields[0] elif len(fields) == 2: trace_id, span_id = fields elif len(fields) == 3: trace_id, span_id, sampled = fields elif len(fields) == 4: trace_id, span_id, sampled, _ = fields else: trace_id = (_extract_first_element( getter.get(carrier, self.TRACE_ID_KEY)) or trace_id) span_id = (_extract_first_element( getter.get(carrier, self.SPAN_ID_KEY)) or span_id) sampled = (_extract_first_element( getter.get(carrier, self.SAMPLED_KEY)) or sampled) flags = (_extract_first_element(getter.get( carrier, self.FLAGS_KEY)) or flags) if (trace_id == trace.INVALID_TRACE_ID or span_id == trace.INVALID_SPAN_ID or self._trace_id_regex.fullmatch(trace_id) is None or self._span_id_regex.fullmatch(span_id) is None): return context trace_id = int(trace_id, 16) span_id = int(span_id, 16) options = 0 # The b3 spec provides no defined behavior for both sample and # flag values set. Since the setting of at least one implies # the desire for some form of sampling, propagate if either # header is set to allow. if sampled in self._SAMPLE_PROPAGATE_VALUES or flags == "1": options |= trace.TraceFlags.SAMPLED return trace.set_span_in_context( trace.NonRecordingSpan( trace.SpanContext( # trace an span ids are encoded in hex, so must be converted trace_id=trace_id, span_id=span_id, is_remote=True, trace_flags=trace.TraceFlags(options), trace_state=trace.TraceState(), )), context, )
def create_default_span() -> trace_api.Span: span_context = trace_api.SpanContext(37, 73, is_remote=False) return trace_api.NonRecordingSpan(span_context)
def test_use_span(self): self.assertEqual(trace.get_current_span(), trace.INVALID_SPAN) span = trace.NonRecordingSpan(trace.INVALID_SPAN_CONTEXT) with trace.use_span(span): self.assertIs(trace.get_current_span(), span) self.assertEqual(trace.get_current_span(), trace.INVALID_SPAN)
def test_default_span(self): span = trace.NonRecordingSpan(trace.INVALID_SPAN_CONTEXT) self.assertEqual(span.get_span_context(), trace.INVALID_SPAN_CONTEXT) self.assertIs(span.is_recording(), False)