Exemplo n.º 1
0
    def inject(self, span_context, format: object, carrier: object):
        """Injects ``span_context`` into ``carrier``.

        See base class for more details.

        Args:
            span_context: The ``opentracing.SpanContext`` to inject.
            format: a Python object instance that represents a given
                carrier format. `format` may be of any type, and `format`
                equality is defined by Python ``==`` operator.
            carrier: the format-specific carrier object to inject into
        """

        # pylint: disable=redefined-builtin
        # This implementation does not perform the injecting 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 UnsupportedFormatException

        propagator = propagators.get_global_textmap()

        ctx = set_span_in_context(DefaultSpan(span_context.unwrap()))
        propagator.inject(type(carrier).__setitem__, carrier, context=ctx)
    def extract(
        self,
        getter: Getter[TextMapPropagatorT],
        carrier: TextMapPropagatorT,
        context: Optional[Context] = None,
    ) -> Context:

        traceid = _extract_first_element(
            getter.get(carrier, OT_TRACE_ID_HEADER))

        spanid = _extract_first_element(getter.get(carrier, OT_SPAN_ID_HEADER))

        sampled = _extract_first_element(getter.get(carrier,
                                                    OT_SAMPLED_HEADER))

        if sampled == "true":
            traceflags = TraceFlags.SAMPLED
        else:
            traceflags = TraceFlags.DEFAULT

        if (traceid != INVALID_TRACE_ID
                and _valid_extract_traceid.fullmatch(traceid) is not None
                and spanid != INVALID_SPAN_ID
                and _valid_extract_spanid.fullmatch(spanid) is not None):
            context = set_span_in_context(
                DefaultSpan(
                    SpanContext(
                        trace_id=int(traceid, 16),
                        span_id=int(spanid, 16),
                        is_remote=True,
                        trace_flags=traceflags,
                    )),
                context,
            )

            baggage = get_all(context) or {}

            for key in getter.keys(carrier):

                if not key.startswith(OT_BAGGAGE_PREFIX):
                    continue

                baggage[key[len(OT_BAGGAGE_PREFIX):]] = _extract_first_element(
                    getter.get(carrier, key))

            for key, value in baggage.items():
                context = set_baggage(key, value, context)

        return context
Exemplo n.º 3
0
def main():
    otel_init()

    brokers = 'timemachine:9094'
    topic = 'sotest'
    partitions = [0, 1, 2]

    # create a consumer
    consumer = KafkaConsumer(bootstrap_servers=brokers)
    toppars = []
    for partition in partitions:
        toppars.append(TopicPartition(topic, partition))
    consumer.assign(toppars)

    ids_generator = trace.get_tracer_provider().ids_generator
    # poll loop
    for msg in consumer:
        # print
        print('[{}] msg:{}'.format(datetime.now(), msg))
        tid = None
        sid = None
        for header in msg.headers:
            k, v = header
            print('k:{}, v_hex:{}'.format(k, v.hex()))
            if k == 'traceparent':
                # traceparent is a byte array like this
                # b'00-0f900a88ec248149f39125cecd714909-d3f0ef64d9020f01-01'
                tid = int(v[3:35], 16)
                sid = int(v[36:52], 16)
                print('tid:{}, sid:{}'.format(tid, sid))

        parent_span_context = SpanContext(trace_id=tid,
                                          span_id=sid,
                                          trace_flags=1,
                                          trace_state=0,
                                          is_remote=True)
        span = DefaultSpan(context=parent_span_context)
        context = {'current-span': span}
        with tracer.start_as_current_span(
                "py_kc", context=context,
                kind=trace_api.SpanKind.CONSUMER) as s:
            print('context:{}'.format(context))
            print('span:{}'.format(s))

            # dummy sleep
            time.sleep(0.1)
        print('-' * 30)
Exemplo n.º 4
0
    def inject(self, span_context, format, carrier):
        """Implements the ``inject`` method from the base class."""

        # TODO: Finish documentation.
        # pylint: disable=redefined-builtin
        # This implementation does not perform the injecting 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

        propagator = propagators.get_global_httptextformat()

        ctx = set_span_in_context(DefaultSpan(span_context.unwrap()))
        propagator.inject(type(carrier).__setitem__, carrier, context=ctx)
Exemplo n.º 5
0
class BatchExportSpanProcessor(SpanProcessor):
    """Batch span processor implementation.

    BatchExportSpanProcessor is an implementation of `SpanProcessor` that
    batches ended spans and pushes them to the configured `SpanExporter`.
    """

    _FLUSH_TOKEN_SPAN = DefaultSpan(context=None)

    def __init__(
        self,
        span_exporter: SpanExporter,
        max_queue_size: int = 2048,
        schedule_delay_millis: float = 5000,
        max_export_batch_size: int = 512,
    ):
        if max_queue_size <= 0:
            raise ValueError("max_queue_size must be a positive integer.")

        if schedule_delay_millis <= 0:
            raise ValueError("schedule_delay_millis must be positive.")

        if max_export_batch_size <= 0:
            raise ValueError(
                "max_export_batch_size must be a positive integer."
            )

        if max_export_batch_size > max_queue_size:
            raise ValueError(
                "max_export_batch_size must be less than and equal to max_export_batch_size."
            )

        self.span_exporter = span_exporter
        self.queue = collections.deque(
            [], max_queue_size
        )  # type: typing.Deque[Span]
        self.worker_thread = threading.Thread(target=self.worker, daemon=True)
        self.condition = threading.Condition(threading.Lock())
        self.flush_condition = threading.Condition(threading.Lock())
        # flag to indicate that there is a flush operation on progress
        self._flushing = False
        self.schedule_delay_millis = schedule_delay_millis
        self.max_export_batch_size = max_export_batch_size
        self.max_queue_size = max_queue_size
        self.done = False
        # flag that indicates that spans are being dropped
        self._spans_dropped = False
        # precallocated list to send spans to exporter
        self.spans_list = [
            None
        ] * self.max_export_batch_size  # type: typing.List[typing.Optional[Span]]
        self.worker_thread.start()

    def on_start(self, span: Span) -> None:
        pass

    def on_end(self, span: Span) -> None:
        if self.done:
            logger.warning("Already shutdown, dropping span.")
            return
        if len(self.queue) == self.max_queue_size:
            if not self._spans_dropped:
                logger.warning("Queue is full, likely spans will be dropped.")
                self._spans_dropped = True

        self.queue.appendleft(span)

        if len(self.queue) >= self.max_queue_size // 2:
            with self.condition:
                self.condition.notify()

    def worker(self):
        timeout = self.schedule_delay_millis / 1e3
        while not self.done:
            if (
                len(self.queue) < self.max_export_batch_size
                and not self._flushing
            ):
                with self.condition:
                    self.condition.wait(timeout)
                    if not self.queue:
                        # spurious notification, let's wait again
                        continue
                    if self.done:
                        # missing spans will be sent when calling flush
                        break

            # substract the duration of this export call to the next timeout
            start = time_ns()
            self.export()
            end = time_ns()
            duration = (end - start) / 1e9
            timeout = self.schedule_delay_millis / 1e3 - duration

        # be sure that all spans are sent
        self._drain_queue()

    def export(self) -> None:
        """Exports at most max_export_batch_size spans."""
        idx = 0
        notify_flush = False
        # currently only a single thread acts as consumer, so queue.pop() will
        # not raise an exception
        while idx < self.max_export_batch_size and self.queue:
            span = self.queue.pop()
            if span is self._FLUSH_TOKEN_SPAN:
                notify_flush = True
            else:
                self.spans_list[idx] = span
                idx += 1
        token = attach(set_value("suppress_instrumentation", True))
        try:
            # Ignore type b/c the Optional[None]+slicing is too "clever"
            # for mypy
            self.span_exporter.export(self.spans_list[:idx])  # type: ignore
        # pylint: disable=broad-except
        except Exception:
            logger.exception("Exception while exporting Span batch.")
        detach(token)

        if notify_flush:
            with self.flush_condition:
                self.flush_condition.notify()

        # clean up list
        for index in range(idx):
            self.spans_list[index] = None

    def _drain_queue(self):
        """"Export all elements until queue is empty.

        Can only be called from the worker thread context because it invokes
        `export` that is not thread safe.
        """
        while self.queue:
            self.export()

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        if self.done:
            logger.warning("Already shutdown, ignoring call to force_flush().")
            return True

        self._flushing = True
        self.queue.appendleft(self._FLUSH_TOKEN_SPAN)

        # wake up worker thread
        with self.condition:
            self.condition.notify_all()

        # wait for token to be processed
        with self.flush_condition:
            ret = self.flush_condition.wait(timeout_millis / 1e3)

        self._flushing = False

        if not ret:
            logger.warning("Timeout was exceeded in force_flush().")
        return ret

    def shutdown(self) -> None:
        # signal the worker thread to finish and then wait for it
        self.done = True
        with self.condition:
            self.condition.notify_all()
        self.worker_thread.join()
        self.span_exporter.shutdown()
Exemplo n.º 6
0
    def start_span(
        self,
        operation_name: str = None,
        child_of: Union[SpanShim, SpanContextShim] = None,
        references: list = None,
        tags: Attributes = None,
        start_time: float = None,
        ignore_active_span: bool = False,
    ) -> SpanShim:
        """Implements the ``start_span()`` method from the base class.

        Starts a span. In terms of functionality, this method behaves exactly
        like the same method on a "regular" OpenTracing tracer. See
        :meth:`opentracing.Tracer.start_span` for more details.

        Args:
            operation_name: Name of the operation represented by the new span
                from the perspective of the current service.
            child_of: A :class:`SpanShim` or :class:`SpanContextShim`
                representing the parent in a "child of" reference. If
                specified, the *references* parameter must be omitted.
            references: A list of :class:`opentracing.Reference` objects that
                identify one or more parents of type :class:`SpanContextShim`.
            tags: A dictionary of tags.
            start_time: An explicit start time expressed as the number of
                seconds since the epoch as returned by :func:`time.time()`.
            ignore_active_span: Ignore the currently-active span in the
                OpenTelemetry tracer and make the created span the root span of
                a new trace.

        Returns:
            An already-started :class:`SpanShim` instance.
        """

        # Use active span as parent when no explicit parent is specified.
        if not ignore_active_span and not child_of:
            child_of = self.active_span

        # Use the specified parent or the active span if possible. Otherwise,
        # use a `None` parent, which triggers the creation of a new trace.
        parent = child_of.unwrap() if child_of else None
        if isinstance(parent, OtelSpanContext):
            parent = DefaultSpan(parent)

        parent_span_context = set_span_in_context(parent)

        links = []
        if references:
            for ref in references:
                links.append(Link(ref.referenced_context.unwrap()))

        # The OpenTracing API expects time values to be `float` values which
        # represent the number of seconds since the epoch. OpenTelemetry
        # represents time values as nanoseconds since the epoch.
        start_time_ns = start_time
        if start_time_ns is not None:
            start_time_ns = util.time_seconds_to_ns(start_time)

        span = self._otel_tracer.start_span(
            operation_name,
            context=parent_span_context,
            links=links,
            attributes=tags,
            start_time=start_time_ns,
        )

        context = SpanContextShim(span.get_span_context())
        return SpanShim(self, context, span)