Files
2020-04-08 10:39:44 -07:00

168 lines
5.3 KiB
Python

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