lib/datadog/tracing/correlation.rb



require_relative 'utils'
require_relative 'metadata/ext'
require_relative '../core/logging/ext'

module Datadog
  module Tracing
    # Contains behavior for managing correlations with tracing
    # e.g. Retrieve a correlation to the current trace for logging, etc.
    module Correlation
      # Represents current trace state with key identifiers
      # @public_api
      class Identifier
        LOG_ATTR_ENV = 'dd.env'.freeze
        LOG_ATTR_SERVICE = 'dd.service'.freeze
        LOG_ATTR_SPAN_ID = 'dd.span_id'.freeze
        LOG_ATTR_TRACE_ID = 'dd.trace_id'.freeze
        LOG_ATTR_VERSION = 'dd.version'.freeze
        LOG_ATTR_SOURCE = 'ddsource'.freeze

        attr_reader \
          :env,
          :service,
          :span_id,
          :span_name,
          :span_resource,
          :span_service,
          :span_type,
          :trace_name,
          :trace_resource,
          :trace_service,
          :version

        # @!visibility private
        def initialize(
          env: nil,
          service: nil,
          span_id: nil,
          span_name: nil,
          span_resource: nil,
          span_service: nil,
          span_type: nil,
          trace_id: nil,
          trace_name: nil,
          trace_resource: nil,
          trace_service: nil,
          version: nil
        )
          # Dup and freeze strings so they aren't modified by reference.
          @env = Core::Utils::SafeDup.frozen_dup(env || Datadog.configuration.env)
          @service = Core::Utils::SafeDup.frozen_dup(service || Datadog.configuration.service)
          @span_id = span_id || 0
          @span_name = Core::Utils::SafeDup.frozen_dup(span_name)
          @span_resource = Core::Utils::SafeDup.frozen_dup(span_resource)
          @span_service = Core::Utils::SafeDup.frozen_dup(span_service)
          @span_type = Core::Utils::SafeDup.frozen_dup(span_type)
          @trace_id = trace_id || 0
          @trace_name = Core::Utils::SafeDup.frozen_dup(trace_name)
          @trace_resource = Core::Utils::SafeDup.frozen_dup(trace_resource)
          @trace_service = Core::Utils::SafeDup.frozen_dup(trace_service)
          @version = Core::Utils::SafeDup.frozen_dup(version || Datadog.configuration.version)
        end

        def to_h
          @to_h ||= {
            # Adds IDs as tags to log output
            dd: {
              # To preserve precision during JSON serialization, use strings for large numbers
              env: env.to_s,
              service: service.to_s,
              version: version.to_s,
              trace_id: trace_id.to_s,
              span_id: span_id.to_s
            },
            ddsource: Core::Logging::Ext::DD_SOURCE
          }
        end

        # This method (#to_log_format) implements an algorithm by prefixing keys for nested values
        # but the algorithm makes the constants implicit. Hence, we use it for validation during test.
        def to_log_format
          @log_format ||= begin
            attributes = []
            attributes << "#{LOG_ATTR_ENV}=#{env}" unless env.nil?
            attributes << "#{LOG_ATTR_SERVICE}=#{service}"
            attributes << "#{LOG_ATTR_VERSION}=#{version}" unless version.nil?
            attributes << "#{LOG_ATTR_TRACE_ID}=#{trace_id}"
            attributes << "#{LOG_ATTR_SPAN_ID}=#{span_id}"
            attributes << "#{LOG_ATTR_SOURCE}=#{Core::Logging::Ext::DD_SOURCE}"
            attributes.join(' ')
          end
        end

        # DEV-2.0: This public method was returning an Integer, but with 128 bit trace id it would return a String.
        def trace_id
          if Datadog.configuration.tracing.trace_id_128_bit_logging_enabled &&
              !Tracing::Utils::TraceId.to_high_order(@trace_id).zero?
            Kernel.format('%032x', @trace_id)
          else
            Tracing::Utils::TraceId.to_low_order(@trace_id)
          end
        end
      end

      module_function

      # Produces a CorrelationIdentifier from the TraceDigest provided
      #
      # DEV: can we memoize this object, give it can be common to
      # use a correlation multiple times, specially in the context of logging?
      # @!visibility private
      def identifier_from_digest(digest)
        return Identifier.new unless digest

        Identifier.new(
          span_id: digest.span_id,
          span_name: digest.span_name,
          span_resource: digest.span_resource,
          span_service: digest.span_service,
          span_type: digest.span_type,
          trace_id: digest.trace_id,
          trace_name: digest.trace_name,
          trace_resource: digest.trace_resource,
          trace_service: digest.trace_service
        )
      end
    end
  end
end