import threading from opentracing import Span as OpenTracingSpan from opentracing.ext import tags as OTTags from ddtrace.span import Span as DatadogSpan from ddtrace.ext import errors from .tags import Tags from .span_context import SpanContext class Span(OpenTracingSpan): """Datadog implementation of :class:`opentracing.Span`""" def __init__(self, tracer, context, operation_name): if context is not None: context = SpanContext(ddcontext=context._dd_context, baggage=context.baggage) else: context = SpanContext() super(Span, self).__init__(tracer, context) self.finished = False self._lock = threading.Lock() # use a datadog span self._dd_span = DatadogSpan(tracer._dd_tracer, operation_name, context=context._dd_context) def finish(self, finish_time=None): """Finish the span. This calls finish on the ddspan. :param finish_time: specify a custom finish time with a unix timestamp per time.time() :type timestamp: float """ if self.finished: return # finish the datadog span self._dd_span.finish(finish_time) self.finished = True def set_baggage_item(self, key, value): """Sets a baggage item in the span context of this span. Baggage is used to propagate state between spans. :param key: baggage item key :type key: str :param value: baggage item value :type value: a type that can be compat.stringify()'d :rtype: Span :return: itself for chaining calls """ new_ctx = self.context.with_baggage_item(key, value) with self._lock: self._context = new_ctx return self def get_baggage_item(self, key): """Gets a baggage item from the span context of this span. :param key: baggage item key :type key: str :rtype: str :return: the baggage value for the given key or ``None``. """ return self.context.get_baggage_item(key) def set_operation_name(self, operation_name): """Set the operation name.""" self._dd_span.name = operation_name def log_kv(self, key_values, timestamp=None): """Add a log record to this span. Passes on relevant opentracing key values onto the datadog span. :param key_values: a dict of string keys and values of any type :type key_values: dict :param timestamp: a unix timestamp per time.time() :type timestamp: float :return: the span itself, for call chaining :rtype: Span """ # match opentracing defined keys to datadog functionality # opentracing/specification/blob/1be630515dafd4d2a468d083300900f89f28e24d/semantic_conventions.md#log-fields-table for key, val in key_values.items(): if key == 'event' and val == 'error': # TODO: not sure if it's actually necessary to set the error manually self._dd_span.error = 1 self.set_tag('error', 1) elif key == 'error' or key == 'error.object': self.set_tag(errors.ERROR_TYPE, val) elif key == 'message': self.set_tag(errors.ERROR_MSG, val) elif key == 'stack': self.set_tag(errors.ERROR_STACK, val) else: pass return self def set_tag(self, key, value): """Set a tag on the span. This sets the tag on the underlying datadog span. """ if key == Tags.SPAN_TYPE: self._dd_span.span_type = value elif key == Tags.SERVICE_NAME: self._dd_span.service = value elif key == Tags.RESOURCE_NAME or key == OTTags.DATABASE_STATEMENT: self._dd_span.resource = value elif key == OTTags.PEER_HOSTNAME: self._dd_span.set_tag(Tags.TARGET_HOST, value) elif key == OTTags.PEER_PORT: self._dd_span.set_tag(Tags.TARGET_PORT, value) elif key == Tags.SAMPLING_PRIORITY: self._dd_span.context.sampling_priority = value else: self._dd_span.set_tag(key, value) def _get_tag(self, key): """Gets a tag from the span. This method retrieves the tag from the underlying datadog span. """ return self._dd_span.get_tag(key) def _get_metric(self, key): """Gets a metric from the span. This method retrieves the metric from the underlying datadog span. """ return self._dd_span.get_metric(key) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type: self._dd_span.set_exc_info(exc_type, exc_val, exc_tb) # note: self.finish() AND _dd_span.__exit__ will call _span.finish() but # it is idempotent self._dd_span.__exit__(exc_type, exc_val, exc_tb) self.finish() def _associate_dd_span(self, ddspan): """Associates a DD span with this span.""" # get the datadog span context self._dd_span = ddspan self.context._dd_context = ddspan.context @property def _dd_context(self): return self._dd_span.context