def test_to_json(self):
        context = trace_api.SpanContext(
            trace_id=0x000000000000000000000000DEADBEEF,
            span_id=0x00000000DEADBEF0,
            is_remote=False,
            trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED),
        )
        span = trace._Span("span-name", context)
        span.resource = Resource({})

        self.assertEqual(
            span.to_json(),
            """{
    "name": "span-name",
    "context": {
        "trace_id": "0x000000000000000000000000deadbeef",
        "span_id": "0x00000000deadbef0",
        "trace_state": "{}"
    },
    "kind": "SpanKind.INTERNAL",
    "parent_id": null,
    "start_time": null,
    "end_time": null,
    "attributes": {},
    "events": [],
    "links": [],
    "resource": {}
}""",
        )
        self.assertEqual(
            span.to_json(indent=None),
            '{"name": "span-name", "context": {"trace_id": "0x000000000000000000000000deadbeef", "span_id": "0x00000000deadbef0", "trace_state": "{}"}, "kind": "SpanKind.INTERNAL", "parent_id": null, "start_time": null, "end_time": null, "attributes": {}, "events": [], "links": [], "resource": {}}',
        )
    def test_service_name_fallback(self):
        context = trace_api.SpanContext(
            trace_id=0x000000000000000000000000DEADBEEF,
            span_id=0x34BF92DEEFC58C92,
            is_remote=False,
            trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED),
        )
        trace_api.get_tracer_provider().sampler = sampling.TraceIdRatioBased(
            0.5)

        resource_with_default_name = Resource(
            attributes={
                "key_resource": "some_resource",
                "service.name": "unknown_service",
            })

        span = trace._Span(
            name="sampled",
            context=context,
            parent=None,
            resource=resource_with_default_name,
        )
        span.start()
        span.end()

        # pylint: disable=protected-access
        exporter = datadog.DatadogSpanExporter(service="fallback_service_name")
        datadog_spans = [
            span.to_dict() for span in exporter._translate_to_datadog([span])
        ]

        self.assertEqual(len(datadog_spans), 1)

        span = datadog_spans[0]
        self.assertEqual(span["service"], "fallback_service_name")
    def test_sampling_rate(self):
        context = trace_api.SpanContext(
            trace_id=0x000000000000000000000000DEADBEEF,
            span_id=0x34BF92DEEFC58C92,
            is_remote=False,
            trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED),
        )
        sampler = sampling.TraceIdRatioBased(0.5)

        span = trace.Span(name="sampled",
                          context=context,
                          parent=None,
                          sampler=sampler)
        span.start()
        span.end()

        # pylint: disable=protected-access
        exporter = datadog.DatadogSpanExporter()
        datadog_spans = [
            span.to_dict() for span in exporter._translate_to_datadog([span])
        ]

        self.assertEqual(len(datadog_spans), 1)

        actual = [
            span["metrics"].get(datadog.constants.SAMPLE_RATE_METRIC_KEY)
            if "metrics" in span else None for span in datadog_spans
        ]
        expected = [0.5]
        self.assertListEqual(actual, expected)
Example #4
0
    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.DefaultSpan(
            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 test_start_span_explicit(self):
        tracer = new_tracer()

        other_parent = trace._Span(
            "name",
            trace_api.SpanContext(
                trace_id=0x000000000000000000000000DEADBEEF,
                span_id=0x00000000DEADBEF0,
                is_remote=False,
                trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED),
            ),
        )

        other_parent_context = trace_api.set_span_in_context(other_parent)

        self.assertEqual(trace_api.get_current_span(), trace_api.INVALID_SPAN)

        root = tracer.start_span("root")
        self.assertIsNotNone(root.start_time)
        self.assertIsNone(root.end_time)

        # Test with the implicit root span
        with tracer.use_span(root, True):
            self.assertIs(trace_api.get_current_span(), root)

            with tracer.start_span("stepchild", other_parent_context) as child:
                # The child's parent should be the one passed in,
                # not the current span.
                self.assertNotEqual(child.parent, root)
                self.assertIs(child.parent, other_parent.get_span_context())

                self.assertIsNotNone(child.start_time)
                self.assertIsNone(child.end_time)

                # The child should inherit its context from the explicit
                # parent, not the current span.
                child_context = child.get_span_context()
                self.assertEqual(
                    other_parent.get_span_context().trace_id,
                    child_context.trace_id,
                )
                self.assertNotEqual(
                    other_parent.get_span_context().span_id,
                    child_context.span_id,
                )
                self.assertEqual(
                    other_parent.get_span_context().trace_state,
                    child_context.trace_state,
                )
                self.assertEqual(
                    other_parent.get_span_context().trace_flags,
                    child_context.trace_flags,
                )

                # Verify start_span() did not set the current span.
                self.assertIs(trace_api.get_current_span(), root)

            # Verify ending the child did not set the current span.
            self.assertIs(trace_api.get_current_span(), root)
            self.assertIsNotNone(child.end_time)
    def test_start_as_current_span_explicit(self):
        tracer = new_tracer()

        other_parent = trace_api.SpanContext(
            trace_id=0x000000000000000000000000DEADBEEF,
            span_id=0x00000000DEADBEF0,
            is_remote=False,
            trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED),
        )

        self.assertEqual(trace_api.get_current_span(), trace_api.INVALID_SPAN)

        # Test with the implicit root span
        with tracer.start_as_current_span("root") as root:
            self.assertIs(trace_api.get_current_span(), root)

            self.assertIsNotNone(root.start_time)
            self.assertIsNone(root.end_time)

            with tracer.start_as_current_span(
                "stepchild", other_parent
            ) as child:
                # The child should become the current span as usual, but its
                # parent should be the one passed in, not the
                # previously-current span.
                self.assertIs(trace_api.get_current_span(), child)
                self.assertNotEqual(child.parent, root)
                self.assertIs(child.parent, other_parent)

            # After exiting the child's scope the last span on the stack should
            # become current, not the child's parent.
            self.assertNotEqual(trace_api.get_current_span(), other_parent)
            self.assertIs(trace_api.get_current_span(), root)
            self.assertIsNotNone(child.end_time)
Example #7
0
    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)
Example #8
0
    def extract(
        self,
        get_from_carrier: Getter[HTTPTextFormatT],
        carrier: HTTPTextFormatT,
        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(
            get_from_carrier(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 set_span_in_context(trace.INVALID_SPAN)
        else:
            trace_id = (_extract_first_element(
                get_from_carrier(carrier, self.TRACE_ID_KEY)) or trace_id)
            span_id = (_extract_first_element(
                get_from_carrier(carrier, self.SPAN_ID_KEY)) or span_id)
            sampled = (_extract_first_element(
                get_from_carrier(carrier, self.SAMPLED_KEY)) or sampled)
            flags = (_extract_first_element(
                get_from_carrier(carrier, self.FLAGS_KEY)) or flags)

        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 set_span_in_context(
            trace.DefaultSpan(
                trace.SpanContext(
                    # trace an span ids are encoded in hex, so must be converted
                    trace_id=int(trace_id, 16),
                    span_id=int(span_id, 16),
                    is_remote=True,
                    trace_flags=trace.TraceFlags(options),
                    trace_state=trace.TraceState(),
                )))
    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 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
        )
Example #11
0
def _create_start_and_end_span(name, span_processor):
    span = trace._Span(
        name,
        trace_api.SpanContext(
            0xDEADBEEF,
            0xDEADBEEF,
            is_remote=False,
            trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED),
        ),
        span_processor=span_processor,
    )
    span.start()
    span.end()
    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,
        getter: Getter[TextMapPropagatorT],
        carrier: TextMapPropagatorT,
        context: typing.Optional[Context] = None,
    ) -> 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 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 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_attributes_to_json(self):
     context = trace_api.SpanContext(
         trace_id=0x000000000000000000000000DEADBEEF,
         span_id=0x00000000DEADBEF0,
         is_remote=False,
         trace_flags=trace_api.TraceFlags(trace_api.TraceFlags.SAMPLED),
     )
     span = trace._Span("span-name", context, resource=Resource({}))
     span.set_attribute("key", "value")
     span.add_event("event", {"key2": "value2"}, 123)
     date_str = ns_to_iso_str(123)
     self.assertEqual(
         span.to_json(indent=None),
         '{"name": "span-name", "context": {"trace_id": "0x000000000000000000000000deadbeef", "span_id": "0x00000000deadbef0", "trace_state": "[]"}, "kind": "SpanKind.INTERNAL", "parent_id": null, "start_time": null, "end_time": null, "status": {"status_code": "UNSET"}, "attributes": {"key": "value"}, "events": [{"name": "event", "timestamp": "'
         + date_str
         + '", "attributes": {"key2": "value2"}}], "links": [], "resource": {}}',
     )
Example #16
0
    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 extract(
        cls, get_from_carrier: httptextformat.Getter[_T], carrier: _T
    ) -> trace.SpanContext:
        """Extracts a valid SpanContext from the carrier.
        """
        header = get_from_carrier(carrier, cls._TRACEPARENT_HEADER_NAME)

        if not header:
            return trace.INVALID_SPAN_CONTEXT

        match = re.search(cls._TRACEPARENT_HEADER_FORMAT_RE, header[0])
        if not match:
            return 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.INVALID_SPAN_CONTEXT

        if version == "00":
            if match.group(5):
                return trace.INVALID_SPAN_CONTEXT
        if version == "ff":
            return trace.INVALID_SPAN_CONTEXT

        tracestate_headers = get_from_carrier(
            carrier, cls._TRACESTATE_HEADER_NAME
        )
        tracestate = _parse_tracestate(tracestate_headers)

        span_context = trace.SpanContext(
            trace_id=int(trace_id, 16),
            span_id=int(span_id, 16),
            trace_flags=trace.TraceFlags(trace_flags),
            trace_state=tracestate,
        )

        return span_context
Example #18
0
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
import unittest

from opentelemetry import trace
from opentelemetry.sdk.trace import sampling

TO_DEFAULT = trace.TraceFlags(trace.TraceFlags.DEFAULT)
TO_SAMPLED = trace.TraceFlags(trace.TraceFlags.SAMPLED)


class TestDecision(unittest.TestCase):
    def test_is_recording(self):
        self.assertTrue(
            sampling.Decision.is_recording(sampling.Decision.RECORD_ONLY))
        self.assertTrue(
            sampling.Decision.is_recording(
                sampling.Decision.RECORD_AND_SAMPLE))
        self.assertFalse(sampling.Decision.is_recording(
            sampling.Decision.DROP))

    def test_is_sampled(self):
        self.assertFalse(
    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 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
Example #21
0
    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
        )