def extract( self, getter: Getter[TextMapPropagatorT], carrier: TextMapPropagatorT, context: typing.Optional[Context] = None, ) -> Context: trace_header_list = getter.get(carrier, TRACE_HEADER_KEY) if not trace_header_list or len(trace_header_list) != 1: return trace.set_span_in_context(trace.INVALID_SPAN, context=context) trace_header = trace_header_list[0] if not trace_header: return trace.set_span_in_context(trace.INVALID_SPAN, context=context) try: ( trace_id, span_id, sampled, ) = AwsXRayFormat._extract_span_properties(trace_header) except AwsParseTraceHeaderError as err: _logger.debug(err.message) return trace.set_span_in_context(trace.INVALID_SPAN, context=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. Insertting INVALID span into provided context." ) return trace.set_span_in_context(trace.INVALID_SPAN, context=context) return trace.set_span_in_context(trace.DefaultSpan(span_context), context=context)
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.DefaultSpan( build_test_span_context( trace_id, span_id, is_remote, trace_flags, trace_state ) ) )
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.DefaultSpan( trace.SpanContext(self.TRACE_ID, self.SPAN_ID, is_remote=False)) ctx = trace.set_span_in_context(span) FORMAT.inject(dict.__setitem__, output, ctx) self.assertTrue("traceparent" in output) self.assertFalse("tracestate" in output)
def extract( self, getter: textmap.Getter[textmap.TextMapPropagatorT], carrier: textmap.TextMapPropagatorT, context: typing.Optional[Context] = None, ) -> Context: """Extracts SpanContext from the carrier. See `opentelemetry.trace.propagation.textmap.TextMapPropagator.extract` """ header = getter.get(carrier, self._TRACEPARENT_HEADER_NAME) if not header: return trace.set_span_in_context(trace.INVALID_SPAN, context) match = re.search(self._TRACEPARENT_HEADER_FORMAT_RE, header[0]) if not match: return trace.set_span_in_context(trace.INVALID_SPAN, 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 trace.set_span_in_context(trace.INVALID_SPAN, context) if version == "00": if match.group(5): return trace.set_span_in_context(trace.INVALID_SPAN, context) if version == "ff": return trace.set_span_in_context(trace.INVALID_SPAN, context) tracestate_headers = getter.get(carrier, self._TRACESTATE_HEADER_NAME) if tracestate_headers is None: tracestate = None else: tracestate = _parse_tracestate(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.DefaultSpan(span_context), context)
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.DefaultSpan(span_context)) self.assertEqual( output, "{}/{};o={}".format( get_hexadecimal_trace_id(self.valid_trace_id), self.valid_span_id, 1, ), )
def extract( self, get_from_carrier: Getter[HTTPTextFormatT], carrier: HTTPTextFormatT, context: typing.Optional[Context] = None, ) -> Context: trace_id_list = get_from_carrier(carrier, self.TRACE_ID_KEY) span_id_list = get_from_carrier(carrier, self.SPAN_ID_KEY) if not trace_id_list or not span_id_list: return set_span_in_context(trace.INVALID_SPAN) return set_span_in_context( trace.DefaultSpan( trace.SpanContext( trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]), )))
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.DefaultSpan( trace.SpanContext( trace_id=int(trace_id_list[0]), span_id=int(span_id_list[0]), is_remote=True, )))
def mock_opentelemetry_context(trace_id=0xDEADBEEF, span_id=0xBEEF, trace_flags=None, trace_state=None): span_context = trace.SpanContext( trace_id=trace_id, span_id=span_id, is_remote=True, trace_flags=trace_flags, trace_state=trace.TraceState( **(trace_state if trace_state is not None else { "some_key": "some_value" })), ) ctx = trace.set_span_in_context(trace.DefaultSpan(span_context)) token = context.attach(ctx) try: yield finally: context.detach(token)
def extract( self, get_from_carrier: Getter[TextMapPropagatorT], carrier: TextMapPropagatorT, context: typing.Optional[Context] = None, ) -> Context: trace_id = extract_first_element( get_from_carrier(carrier, self.TRACE_ID_KEY) ) span_id = extract_first_element( get_from_carrier(carrier, self.PARENT_ID_KEY) ) sampled = extract_first_element( get_from_carrier(carrier, self.SAMPLING_PRIORITY_KEY) ) origin = extract_first_element( get_from_carrier(carrier, self.ORIGIN_KEY) ) trace_flags = trace.TraceFlags() if sampled and int(sampled) in ( constants.AUTO_KEEP, constants.USER_KEEP, ): trace_flags |= trace.TraceFlags.SAMPLED if trace_id is None or span_id is None: return set_span_in_context(trace.INVALID_SPAN, context) 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({constants.DD_ORIGIN: origin}), ) return set_span_in_context(trace.DefaultSpan(span_context), context)
def test_default_span(self): span = trace.DefaultSpan(trace.INVALID_SPAN_CONTEXT) self.assertEqual(span.get_context(), trace.INVALID_SPAN_CONTEXT) self.assertIs(span.is_recording_events(), False)
def create_span( self, name: str, parent: trace_api.ParentSpan = trace_api.Tracer.CURRENT_SPAN, kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL, ) -> "trace_api.Span": """See `opentelemetry.trace.Tracer.create_span`. If `parent` is null the new span will be created as a root span, i.e. a span with no parent context. By default, the new span will be created as a child of the current span in this tracer's context, or as a root span if no current span exists. """ span_id = generate_span_id() if parent is Tracer.CURRENT_SPAN: parent = self.get_current_span() parent_context = parent if isinstance(parent_context, trace_api.Span): parent_context = parent.get_context() if parent_context is not None and not isinstance( parent_context, trace_api.SpanContext): raise TypeError if parent_context is None or not parent_context.is_valid(): parent = parent_context = None trace_id = generate_trace_id() trace_options = None trace_state = None else: trace_id = parent_context.trace_id trace_options = parent_context.trace_options trace_state = parent_context.trace_state context = trace_api.SpanContext(trace_id, span_id, trace_options, trace_state) # 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 decision. sampling_decision = self.sampler.should_sample( parent_context, context.trace_id, context.span_id, name, {}, # TODO: links ) if sampling_decision.sampled: return Span( name=name, context=context, parent=parent, sampler=self.sampler, attributes=sampling_decision.attributes, span_processor=self._active_span_processor, kind=kind, ) return trace_api.DefaultSpan(context=context)
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, 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.source.ids_generator.generate_trace_id() trace_flags = None trace_state = None else: trace_id = parent_span_context.trace_id trace_flags = parent_span_context.trace_flags trace_state = parent_span_context.trace_state # 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. sampling_result = self.source.sampler.should_sample( context, trace_id, name, 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.source.ids_generator.generate_span_id(), is_remote=False, trace_flags=trace_flags, trace_state=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.source.sampler, resource=self.source.resource, attributes=sampling_result.attributes.copy(), span_processor=self.source._active_span_processor, kind=kind, links=links, instrumentation_info=self.instrumentation_info, set_status_on_exception=set_status_on_exception, ) span.start(start_time=start_time, parent_context=context) else: span = trace_api.DefaultSpan(context=span_context) return span
def extract( self, getter: Getter[TextMapPropagatorT], carrier: TextMapPropagatorT, context: typing.Optional[Context] = None, ) -> Context: trace_id = format_trace_id(trace.INVALID_TRACE_ID) span_id = format_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: return trace.set_span_in_context(trace.INVALID_SPAN) 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 ( self._trace_id_regex.fullmatch(trace_id) is None or self._span_id_regex.fullmatch(span_id) is None ): ids_generator = trace.get_tracer_provider().ids_generator trace_id = ids_generator.generate_trace_id() span_id = ids_generator.generate_span_id() sampled = "0" else: 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.DefaultSpan( 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(), ) ) )
def start_span( self, name: str, parent: trace_api.ParentSpan = trace_api.Tracer.CURRENT_SPAN, kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL, attributes: Optional[types.Attributes] = None, links: Sequence[trace_api.Link] = (), start_time: Optional[int] = None, ) -> "Span": """See `opentelemetry.trace.Tracer.start_span`.""" if parent is Tracer.CURRENT_SPAN: parent = self.get_current_span() parent_context = parent if isinstance(parent_context, trace_api.Span): parent_context = parent.get_context() if parent_context is not None and not isinstance( parent_context, trace_api.SpanContext ): raise TypeError if parent_context is None or not parent_context.is_valid(): parent = parent_context = None trace_id = generate_trace_id() trace_options = None trace_state = None else: trace_id = parent_context.trace_id trace_options = parent_context.trace_options trace_state = parent_context.trace_state context = trace_api.SpanContext( trace_id, generate_span_id(), trace_options, trace_state ) # 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 decision. sampling_decision = self.sampler.should_sample( parent_context, context.trace_id, context.span_id, name, attributes, links, ) if sampling_decision.sampled: if attributes is None: span_attributes = sampling_decision.attributes else: # apply sampling decision attributes after initial attributes span_attributes = attributes.copy() span_attributes.update(sampling_decision.attributes) span = Span( name=name, context=context, parent=parent, sampler=self.sampler, attributes=span_attributes, span_processor=self._active_span_processor, kind=kind, links=links, ) span.start(start_time=start_time) else: span = trace_api.DefaultSpan(context=context) return span
def extract( self, get_from_carrier: Getter[TextMapPropagatorT], carrier: TextMapPropagatorT, context: typing.Optional[Context] = None, ) -> Context: if not carrier: raise ValueError(("Could not extract from carrier: %s", carrier)) trace_header = get_from_carrier(carrier, self.TRACE_HEADER_KEY) if not trace_header or trace_header == "": return trace.set_span_in_context(trace.INVALID_SPAN) trace_id = trace.INVALID_TRACE_ID span_id = trace.INVALID_SPAN_ID sampled = False next_kv_pair_start = 0 while next_kv_pair_start < len(trace_header): try: kv_pair_delimiter_index = trace_header.index( self.KV_PAIR_DELIMITER, next_kv_pair_start ) kv_pair_subset = trace_header[ next_kv_pair_start:kv_pair_delimiter_index ] next_kv_pair_start = kv_pair_delimiter_index + 1 except ValueError as _: kv_pair_subset = trace_header[next_kv_pair_start:] next_kv_pair_start = len(trace_header) stripped_kv_pair = kv_pair_subset.strip() try: key_and_value_delimiter_index = stripped_kv_pair.index( self.KEY_AND_VALUE_DELIMITER ) except ValueError as _: _logger.error( ( "Error parsing X-Ray trace header. Invalid key value pair: %s. Returning INVALID span context.", kv_pair_subset, ) ) return trace.set_span_in_context(trace.INVALID_SPAN) value = stripped_kv_pair[key_and_value_delimiter_index + 1 :] if stripped_kv_pair.startswith(self.TRACE_ID_KEY): if ( len(value) != self.TRACE_ID_LENGTH or not value.startswith(self.TRACE_ID_VERSION) or value[self.TRACE_ID_DELIMITER_INDEX_1] != self.TRACE_ID_DELIMITER or value[self.TRACE_ID_DELIMITER_INDEX_2] != self.TRACE_ID_DELIMITER ): _logger.error( ( "Invalid TraceId in X-Ray trace header: '%s' with value '%s'. Returning INVALID span context.", self.TRACE_HEADER_KEY, trace_header, ) ) return trace.INVALID_SPAN_CONTEXT timestamp_subset = value[ self.TRACE_ID_DELIMITER_INDEX_1 + 1 : self.TRACE_ID_DELIMITER_INDEX_2 ] unique_id_subset = value[ self.TRACE_ID_DELIMITER_INDEX_2 + 1 : self.TRACE_ID_LENGTH ] trace_id = int(timestamp_subset + unique_id_subset, 16) elif stripped_kv_pair.startswith(self.PARENT_ID_KEY): if len(value) != self.PARENT_ID_LENGTH: _logger.error( ( "Invalid ParentId in X-Ray trace header: '%s' with value '%s'. Returning INVALID span context.", self.TRACE_HEADER_KEY, trace_header, ) ) return trace.INVALID_SPAN_CONTEXT span_id = int(value, 16) elif stripped_kv_pair.startswith(self.SAMPLED_FLAG_KEY): is_sampled_flag_valid = True if len(value) != self.SAMPLED_FLAG_LENGTH: is_sampled_flag_valid = False if is_sampled_flag_valid: sampled_flag = value[0] if sampled_flag == self.IS_SAMPLED: sampled = True elif sampled_flag == self.NOT_SAMPLED: sampled = False else: is_sampled_flag_valid = False if not is_sampled_flag_valid: _logger.error( ( "Invalid Sampling flag in X-Ray trace header: '%s' with value '%s'. Returning INVALID span context.", self.TRACE_HEADER_KEY, trace_header, ) ) return trace.INVALID_SPAN_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: return context return trace.set_span_in_context( trace.DefaultSpan(span_context), context=context )
def create_default_span() -> trace_api.Span: span_context = trace_api.SpanContext(37, 73, is_remote=False) return trace_api.DefaultSpan(span_context)
def test_use_span(self): span = trace.DefaultSpan(trace.INVALID_SPAN_CONTEXT) with self.tracer.use_span(span): pass
def start_span( # pylint: disable=too-many-locals self, name: str, parent: trace_api.ParentSpan = trace_api.Tracer.CURRENT_SPAN, kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL, attributes: Optional[types.Attributes] = None, links: Sequence[trace_api.Link] = (), start_time: Optional[int] = None, set_status_on_exception: bool = True, ) -> trace_api.Span: if parent is Tracer.CURRENT_SPAN: parent = trace_api.get_current_span() parent_context = parent if isinstance(parent_context, trace_api.Span): parent_context = parent.get_context() if parent_context is not None and not isinstance( parent_context, trace_api.SpanContext): raise TypeError("parent must be a Span, SpanContext or None.") if parent_context is None or not parent_context.is_valid: parent = parent_context = None trace_id = generate_trace_id() trace_flags = None trace_state = None else: trace_id = parent_context.trace_id trace_flags = parent_context.trace_flags trace_state = parent_context.trace_state context = trace_api.SpanContext( trace_id, generate_span_id(), is_remote=False, trace_flags=trace_flags, trace_state=trace_state, ) # 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 decision. sampling_decision = self.source.sampler.should_sample( parent_context, context.trace_id, context.span_id, name, attributes, links, ) if sampling_decision.sampled: options = context.trace_flags | trace_api.TraceFlags.SAMPLED context.trace_flags = trace_api.TraceFlags(options) if attributes is None: span_attributes = sampling_decision.attributes else: # apply sampling decision attributes after initial attributes span_attributes = attributes.copy() span_attributes.update(sampling_decision.attributes) # pylint:disable=protected-access span = Span( name=name, context=context, parent=parent_context, sampler=self.source.sampler, resource=self.source.resource, attributes=span_attributes, span_processor=self.source._active_span_processor, kind=kind, links=links, instrumentation_info=self.instrumentation_info, set_status_on_exception=set_status_on_exception, ) span.start(start_time=start_time) else: span = trace_api.DefaultSpan(context=context) return span
def test_ctor(self): context = trace.SpanContext( 1, 1, trace.DEFAULT_TRACE_OPTIONS, trace.DEFAULT_TRACE_STATE ) span = trace.DefaultSpan(context) self.assertEqual(context, span.get_context())