class OpenTelemetry::Exporters::Datadog::Exporter::SpanEncoder

@api private

Constants

AUTO_KEEP
AUTO_REJECT
DD_ORIGIN
ENV_KEY
INSTRUMENTATION_SPAN_TYPES
INTERNAL_TRACE_REGEX
ORIGIN_REGEX
PROBABILITY_REGEX
RESOURCE_ENVIRONMENT_TAG
RESOURCE_SERVICE_TAG
RESOURCE_VERSION_TAG
SAMPLE_RATE_METRIC_KEY
SAMPLING_PRIORITY_KEY
TRUNCATION_HELPER
USER_KEEP
USER_REJECT
VERSION_KEY

Public Instance Methods

int64(hex_string, base) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 118
def int64(hex_string, base)
  TRUNCATION_HELPER.value_to_id(hex_string, base)
end
translate_to_datadog(otel_spans, service, env = nil, version = nil, tags = nil) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 52
def translate_to_datadog(otel_spans, service, env = nil, version = nil, tags = nil) # rubocop:disable Metrics/AbcSize
  datadog_spans = []

  default_tags = get_default_tags(tags) || {}

  otel_spans.each do |span|
    trace_id, span_id, parent_id = get_trace_ids(span)
    span_type = get_span_type(span)
    span_name = get_span_name(span)

    # this excludes service.name, which we get seperately
    span_resource_tags, resource_service_name, resource_environment_name, resource_version_name = get_resource_tags_and_service(span)

    default_tags_including_resource = default_tags.merge(span_resource_tags)
    datadog_span = ::Datadog::Span.new(nil, span_name,
                                       service: resource_service_name || service,
                                       trace_id: trace_id,
                                       parent_id: parent_id,
                                       resource: get_resource(span),
                                       span_type: span_type)
    # span_id is autogenerated so have to override
    datadog_span.span_id = span_id
    datadog_span.start_time = span.start_timestamp
    datadog_span.end_time = span.end_timestamp

    # set span.error, span tag error.msg/error.type
    if span.status && !span.status.ok?
      datadog_span.status = 1

      exception_type, exception_msg, exception_stack = get_exception_info(span)

      if exception_type && exception_msg && exception_stack
        datadog_span.set_tag('error.type', exception_type)
        datadog_span.set_tag('error.msg', exception_msg)
        datadog_span.set_tag('error.stack', exception_stack)
      end
    end

    # set default tags
    default_tags_including_resource&.keys&.each do |attribute|
      datadog_span.set_tag(attribute, default_tags_including_resource[attribute])
    end

    origin = get_origin_string(span)
    datadog_span.set_tag(DD_ORIGIN, origin) if origin && parent_id.zero?
    datadog_span.set_tag(VERSION_KEY, resource_version_name || version) if (resource_version_name || version) && parent_id.zero?
    datadog_span.set_tag(ENV_KEY, resource_environment_name || env) if resource_version_name || env

    # set tags - takes precedence over env vars
    span.attributes&.keys&.each do |attribute|
      datadog_span.set_tag(attribute, span.attributes[attribute])
    end

    sampling_rate = get_sampling_rate(span)

    if filter_internal_request?(span)
      datadog_span.set_metric(SAMPLE_RATE_METRIC_KEY, USER_REJECT)
    elsif sampling_rate
      datadog_span.set_metric(SAMPLE_RATE_METRIC_KEY, sampling_rate)
    end
    datadog_spans << datadog_span
  end

  datadog_spans
end

Private Instance Methods

filter_internal_request?(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 278
def filter_internal_request?(span)
  span.attributes['http.route'].match(INTERNAL_TRACE_REGEX) if span.attributes&.key?('http.route')
end
get_default_tags(tags) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 238
def get_default_tags(tags)
  # Parse a string of tags typically provided via environment variables.
  # The expected string is of the form: "key1:value1,key2:value2"

  return {} if tags.nil?

  tag_map = tags.split(',').map { |kv| kv.split(':') }.to_h

  if tag_map.keys&.index('') || tag_map.values&.index('') || tag_map.values&.any? { |v| v.ends_with?(':') }
    OpenTelemetry.logger.debug("malformed tag in default tags: #{tags}")
    {}
  else
    tag_map
  end
end
get_exception_info(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 145
def get_exception_info(span)
  # Parse span exception type, msg, and stack from span events
  error_event = span&.events&.find { |ev| ev.name == 'error' }

  return unless error_event

  err_type = error_event.attributes['error.type']
  err_msg = error_event.attributes['error.msg']
  err_stack = error_event.attributes['error.stack']

  [err_type, err_msg, err_stack]
rescue StandardError => e
  OpenTelemetry.logger.debug("error on exception info from span events: #{span.events} , #{e.message}")
end
get_origin_string(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 219
def get_origin_string(span)
  tracestate = begin
                 span.tracestate
               rescue NoMethodError
                 nil
               end

  return if tracestate.nil? || tracestate.index(DD_ORIGIN).nil?

  # Depending on the edge cases in tracestate values this might be
  # less efficient than mapping string => array => hash.
  origin_value = tracestate.match(ORIGIN_REGEX)
  return if origin_value.nil?

  origin_value[1]
rescue StandardError => e
  OpenTelemetry.logger.debug("error getting origin from trace state, #{e.message}")
end
get_rate_from_description(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 254
def get_rate_from_description(span)
  # format to parse of sampler description is
  # "ProbabilitySampler{1.000000}" or
  # "AlwaysOnSampler" / "AlwaysOffSampler"
  # TODO: remove begin/rescue block if PR #282 is accepted+released
  sampler = begin
              span.sampler
            rescue NoMethodError
              nil
            end

  return nil unless sampler&.is_a?(ProbabilitySampler)

  rate = sampler.description&.match(PROBABILITY_REGEX)

  return nil unless rate

  rate[0].to_f(4)
rescue StandardError => e
  # rescue just in case the format changes dramatically in the future
  OpenTelemetry.logger.warn("error while extracting sampling rate #{e.message} , #{e.backtrace}")
  nil
end
get_resource(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 160
def get_resource(span)
  # Get resource name for http related spans
  # TODO: how to handle resource naming for broader span types, ie db/cache/queue etc

  if span.attributes&.key?('http.method')
    route = span.attributes['http.route'] || span.attributes['http.target']

    return span.attributes['http.method'] + ' ' + route if route

    return span.attributes['http.method']
  end

  span.name
rescue StandardError => e
  OpenTelemetry.logger.debug("error encoding trace_ids #{e.message}")
  span.name
end
get_resource_tags_and_service(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 191
def get_resource_tags_and_service(span)
  resource_tags = {}
  service_name = nil
  environment_name = nil
  version_name = nil
  # this is open to change in new versions so being extra defensive here
  return resource_tags unless (resource_attributes = begin
                                                       span.resource.attribute_enumerator.to_h
                                                     rescue StandardError
                                                       nil
                                                     end)

  # grab service name seperately since it has significance
  resource_attributes.each do |rattribute_key, rattribute_value|
    if rattribute_key == RESOURCE_SERVICE_TAG
      service_name = rattribute_value
    elsif rattribute_key == RESOURCE_ENVIRONMENT_TAG
      environment_name = rattribute_value
    elsif rattribute_key == RESOURCE_VERSION_TAG
      version_name = rattribute_value
    else
      resource_tags[rattribute_key] = rattribute_value
    end
  end

  [resource_tags, service_name, environment_name, version_name]
end
get_sampling_rate(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 178
def get_sampling_rate(span)
  get_rate_from_description(span) || 1 if span.trace_flags&.sampled?
end
get_span_name(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 182
def get_span_name(span)
  # Get span name by using instrumentation and kind while backing off to
  instrumentation_name = span.instrumentation_library&.name
  kind = span.kind
  instrumentation_name && kind ? "#{instrumentation_name.to_s.gsub(':', '_')}.#{kind}" : span.name
rescue NoMethodError
  span.name
end
get_span_type(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 135
def get_span_type(span)
  # Get Datadog span type
  return unless span.instrumentation_library

  instrumentation_name = span.instrumentation_library.name
  INSTRUMENTATION_SPAN_TYPES[instrumentation_name]
rescue NoMethodError
  span.name
end
get_trace_ids(span) click to toggle source
# File lib/opentelemetry/exporters/datadog/exporter/span_encoder.rb, line 124
def get_trace_ids(span)
  trace_id = int64(span.trace_id.unpack1('H*'), 16)
  span_id = int64(span.span_id.unpack1('H*'), 16)
  parent_id = span.parent_span_id ? int64(span.parent_span_id.unpack1('H*'), 16) || 0 : 0

  [trace_id, span_id, parent_id]
rescue StandardError => e
  OpenTelemetry.logger.debug("error encoding trace_ids #{e.message}")
  [0, 0, 0]
end