lib/elastic_apm/span.rb



# frozen_string_literal: true

require 'securerandom'
require 'forwardable'

require 'elastic_apm/span/context'

module ElasticAPM
  # @api private
  class Span
    extend Forwardable

    def_delegators :@trace_context, :trace_id, :parent_id, :id

    DEFAULT_TYPE = 'custom'

    # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
    def initialize(
      name:,
      transaction_id:,
      trace_context:,
      type: nil,
      subtype: nil,
      action: nil,
      context: nil,
      stacktrace_builder: nil
    )
      @name = name

      if subtype.nil? && type&.include?('.')
        @type, @subtype, @action = type.split('.')
      else
        @type = type || DEFAULT_TYPE
        @subtype = subtype
        @action = action
      end

      @transaction_id = transaction_id
      @trace_context = trace_context

      @context = context || Span::Context.new
      @stacktrace_builder = stacktrace_builder
    end
    # rubocop:enable Metrics/ParameterLists, Metrics/MethodLength

    attr_accessor(
      :action,
      :name,
      :original_backtrace,
      :subtype,
      :trace_context,
      :type
    )
    attr_reader(
      :context,
      :duration,
      :stacktrace,
      :timestamp,
      :transaction_id
    )

    # life cycle

    def start(timestamp = Util.micros)
      @timestamp = timestamp

      self
    end

    def stop(end_timestamp = Util.micros)
      @duration ||= (end_timestamp - timestamp)
    end

    def done(end_time: Util.micros)
      stop end_time

      build_stacktrace! if should_build_stacktrace?
      self.original_backtrace = nil # release original

      self
    end

    def stopped?
      !!duration
    end

    def started?
      !!timestamp
    end

    def running?
      started? && !stopped?
    end

    # relations

    def inspect
      "<ElasticAPM::Span id:#{id}" \
        " name:#{name.inspect}" \
        " type:#{type.inspect}" \
        '>'
    end

    private

    def build_stacktrace!
      @stacktrace = @stacktrace_builder.build(original_backtrace, type: :span)
    end

    def should_build_stacktrace?
      @stacktrace_builder && original_backtrace && long_enough_for_stacktrace?
    end

    def long_enough_for_stacktrace?
      min_duration =
        @stacktrace_builder.config.span_frames_min_duration_us

      return true if min_duration < 0
      return false if min_duration == 0

      duration >= min_duration
    end
  end
end