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
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)
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)
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()
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)