elastic-apm 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ci/.jenkins_exclude.yml +47 -0
- data/.ci/.jenkins_framework.yml +4 -0
- data/.ci/.jenkins_master_framework.yml +1 -0
- data/.ci/.jenkins_ruby.yml +1 -0
- data/.ci/downstreamTests.groovy +1 -1
- data/.gitignore +2 -1
- data/.rspec +1 -0
- data/CHANGELOG.asciidoc +24 -0
- data/Dockerfile +43 -0
- data/Gemfile +34 -15
- data/README.md +30 -1
- data/bin/dev +54 -0
- data/bin/run-tests +27 -0
- data/docker-compose.yml +32 -0
- data/docs/api.asciidoc +13 -2
- data/docs/configuration.asciidoc +30 -0
- data/docs/getting-started-rack.asciidoc +24 -0
- data/docs/release-notes.asciidoc +1 -1
- data/lib/elastic_apm.rb +12 -1
- data/lib/elastic_apm/agent.rb +15 -3
- data/lib/elastic_apm/central_config.rb +39 -19
- data/lib/elastic_apm/child_durations.rb +42 -0
- data/lib/elastic_apm/config.rb +27 -11
- data/lib/elastic_apm/context/request/socket.rb +1 -1
- data/lib/elastic_apm/context_builder.rb +1 -1
- data/lib/elastic_apm/error.rb +10 -0
- data/lib/elastic_apm/error/exception.rb +7 -0
- data/lib/elastic_apm/grape.rb +48 -0
- data/lib/elastic_apm/instrumenter.rb +77 -4
- data/lib/elastic_apm/logging.rb +0 -2
- data/lib/elastic_apm/metrics.rb +39 -26
- data/lib/elastic_apm/metrics/breakdown_set.rb +14 -0
- data/lib/elastic_apm/metrics/{cpu_mem.rb → cpu_mem_set.rb} +62 -54
- data/lib/elastic_apm/metrics/metric.rb +117 -0
- data/lib/elastic_apm/metrics/set.rb +106 -0
- data/lib/elastic_apm/metrics/span_scoped_set.rb +39 -0
- data/lib/elastic_apm/metrics/transaction_set.rb +11 -0
- data/lib/elastic_apm/metrics/vm_set.rb +44 -0
- data/lib/elastic_apm/metricset.rb +31 -4
- data/lib/elastic_apm/normalizers.rb +6 -0
- data/lib/elastic_apm/normalizers/grape.rb +5 -0
- data/lib/elastic_apm/normalizers/grape/endpoint_run.rb +47 -0
- data/lib/elastic_apm/normalizers/rails/active_record.rb +16 -5
- data/lib/elastic_apm/opentracing.rb +4 -4
- data/lib/elastic_apm/rails.rb +12 -2
- data/lib/elastic_apm/railtie.rb +1 -5
- data/lib/elastic_apm/sinatra.rb +1 -1
- data/lib/elastic_apm/span.rb +15 -10
- data/lib/elastic_apm/spies.rb +0 -1
- data/lib/elastic_apm/sql_summarizer.rb +8 -6
- data/lib/elastic_apm/subscriber.rb +4 -1
- data/lib/elastic_apm/transaction.rb +6 -6
- data/lib/elastic_apm/transport/base.rb +7 -0
- data/lib/elastic_apm/transport/connection.rb +11 -69
- data/lib/elastic_apm/transport/connection/http.rb +43 -35
- data/lib/elastic_apm/transport/connection/proxy_pipe.rb +0 -3
- data/lib/elastic_apm/transport/headers.rb +62 -0
- data/lib/elastic_apm/transport/serializers.rb +0 -2
- data/lib/elastic_apm/transport/serializers/metricset_serializer.rb +19 -6
- data/lib/elastic_apm/transport/serializers/span_serializer.rb +3 -3
- data/lib/elastic_apm/transport/user_agent.rb +31 -0
- data/lib/elastic_apm/transport/worker.rb +1 -2
- data/lib/elastic_apm/version.rb +1 -1
- metadata +20 -6
- data/lib/elastic_apm/metrics/vm.rb +0 -60
- data/lib/elastic_apm/util/prefixed_logger.rb +0 -18
| @@ -0,0 +1,117 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ElasticAPM
         | 
| 4 | 
            +
              module Metrics
         | 
| 5 | 
            +
                # @api private
         | 
| 6 | 
            +
                class Metric
         | 
| 7 | 
            +
                  def initialize(
         | 
| 8 | 
            +
                    key,
         | 
| 9 | 
            +
                    initial_value: nil,
         | 
| 10 | 
            +
                    tags: nil,
         | 
| 11 | 
            +
                    reset_on_collect: false
         | 
| 12 | 
            +
                  )
         | 
| 13 | 
            +
                    @key = key
         | 
| 14 | 
            +
                    @initial_value = initial_value
         | 
| 15 | 
            +
                    @value = initial_value
         | 
| 16 | 
            +
                    @tags = tags
         | 
| 17 | 
            +
                    @reset_on_collect = reset_on_collect
         | 
| 18 | 
            +
                    @mutex = Mutex.new
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  attr_reader :key, :initial_value, :tags, :value
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def value=(value)
         | 
| 24 | 
            +
                    @mutex.synchronize { @value = value }
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def reset!
         | 
| 28 | 
            +
                    self.value = initial_value
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def tags?
         | 
| 32 | 
            +
                    !!tags&.any?
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def reset_on_collect?
         | 
| 36 | 
            +
                    @reset_on_collect
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def collect
         | 
| 40 | 
            +
                    collected = value
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    self.value = initial_value if reset_on_collect?
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    return nil if reset_on_collect? && collected == 0
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    collected
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                # @api private
         | 
| 51 | 
            +
                class NoopMetric
         | 
| 52 | 
            +
                  def value; end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def value=(_); end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def collect; end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  def reset!; end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def tags?; end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  def reset_on_collect?; end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def inc!; end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  def dec!; end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def update(_, delta: nil); end
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                # @api private
         | 
| 72 | 
            +
                class Counter < Metric
         | 
| 73 | 
            +
                  def initialize(key, initial_value: 0, **args)
         | 
| 74 | 
            +
                    super(key, initial_value: initial_value, **args)
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  def inc!
         | 
| 78 | 
            +
                    @value += 1
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  def dec!
         | 
| 82 | 
            +
                    @value -= 1
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                # @api private
         | 
| 87 | 
            +
                class Gauge < Metric
         | 
| 88 | 
            +
                  def initialize(key, **args)
         | 
| 89 | 
            +
                    super(key, initial_value: 0, **args)
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                # @api private
         | 
| 94 | 
            +
                class Timer < Metric
         | 
| 95 | 
            +
                  def initialize(key, **args)
         | 
| 96 | 
            +
                    super(key, initial_value: 0, **args)
         | 
| 97 | 
            +
                    @count = 0
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  attr_accessor :count
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  def update(duration, delta: 0)
         | 
| 103 | 
            +
                    @mutex.synchronize do
         | 
| 104 | 
            +
                      @value += duration
         | 
| 105 | 
            +
                      @count += delta
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  def reset!
         | 
| 110 | 
            +
                    @mutex.synchronize do
         | 
| 111 | 
            +
                      @value = 0
         | 
| 112 | 
            +
                      @count = 0
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
              end
         | 
| 117 | 
            +
            end
         | 
| @@ -0,0 +1,106 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ElasticAPM
         | 
| 4 | 
            +
              # @api private
         | 
| 5 | 
            +
              module Metrics
         | 
| 6 | 
            +
                NOOP = NoopMetric.new
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                # @api private
         | 
| 9 | 
            +
                class Set
         | 
| 10 | 
            +
                  include Logging
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  DISTINCT_LABEL_LIMIT = 1000
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def initialize(config)
         | 
| 15 | 
            +
                    @config = config
         | 
| 16 | 
            +
                    @metrics = {}
         | 
| 17 | 
            +
                    @disabled = false
         | 
| 18 | 
            +
                    @lock = Mutex.new
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  attr_reader :metrics
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def disable!
         | 
| 24 | 
            +
                    @disabled = true
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def disabled?
         | 
| 28 | 
            +
                    @disabled
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def counter(key, tags: nil, **args)
         | 
| 32 | 
            +
                    metric(Counter, key, tags: tags, **args)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def gauge(key, tags: nil, **args)
         | 
| 36 | 
            +
                    metric(Gauge, key, tags: tags, **args)
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def timer(key, tags: nil, **args)
         | 
| 40 | 
            +
                    metric(Timer, key, tags: tags, **args)
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
         | 
| 44 | 
            +
                  def metric(kls, key, tags: nil, **args)
         | 
| 45 | 
            +
                    key = key_with_tags(key, tags)
         | 
| 46 | 
            +
                    return metrics[key] if metrics[key]
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    @lock.synchronize do
         | 
| 49 | 
            +
                      return metrics[key] if metrics[key]
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      metrics[key] =
         | 
| 52 | 
            +
                        if metrics.length < DISTINCT_LABEL_LIMIT
         | 
| 53 | 
            +
                          kls.new(key, tags: tags, **args)
         | 
| 54 | 
            +
                        else
         | 
| 55 | 
            +
                          unless @label_limit_logged
         | 
| 56 | 
            +
                            warn(
         | 
| 57 | 
            +
                              'The limit of %d metricsets has been reached, no new ' \
         | 
| 58 | 
            +
                               'metricsets will be created.', DISTINCT_LABEL_LIMIT
         | 
| 59 | 
            +
                            )
         | 
| 60 | 
            +
                            @label_limit_logged = true
         | 
| 61 | 
            +
                          end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                          NOOP
         | 
| 64 | 
            +
                        end
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
                  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  # rubocop:disable Metrics/MethodLength
         | 
| 70 | 
            +
                  def collect
         | 
| 71 | 
            +
                    return if disabled?
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    @lock.synchronize do
         | 
| 74 | 
            +
                      metrics.each_with_object({}) do |(key, metric), sets|
         | 
| 75 | 
            +
                        next unless (value = metric.collect)
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                        # metrics have a key of name and flat array of key-value pairs
         | 
| 78 | 
            +
                        #   eg [name, key, value, key, value]
         | 
| 79 | 
            +
                        # they can be sent in the same metricset but not if they have
         | 
| 80 | 
            +
                        # differing tags. So, we split the resulting sets by tags first.
         | 
| 81 | 
            +
                        name, *tags = key
         | 
| 82 | 
            +
                        sets[tags] ||= Metricset.new
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                        # then we set the `samples` value for the metricset
         | 
| 85 | 
            +
                        set = sets[tags]
         | 
| 86 | 
            +
                        set.samples[name] = value
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                        # and finally we copy the tags from the Metric to the Metricset
         | 
| 89 | 
            +
                        set.merge_tags! metric.tags
         | 
| 90 | 
            +
                      end.values
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                  # rubocop:enable Metrics/MethodLength
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  private
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  def key_with_tags(key, tags)
         | 
| 98 | 
            +
                    return key unless tags
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    tuple = tags.keys.zip(tags.values)
         | 
| 101 | 
            +
                    tuple.flatten!
         | 
| 102 | 
            +
                    tuple.unshift(key)
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ElasticAPM
         | 
| 4 | 
            +
              module Metrics
         | 
| 5 | 
            +
                # @api private
         | 
| 6 | 
            +
                class SpanScopedSet < Set
         | 
| 7 | 
            +
                  def collect
         | 
| 8 | 
            +
                    super.tap do |sets|
         | 
| 9 | 
            +
                      return unless sets
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                      sets.each do |set|
         | 
| 12 | 
            +
                        move_transaction(set)
         | 
| 13 | 
            +
                        move_span(set)
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  private
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def move_transaction(set)
         | 
| 21 | 
            +
                    name = set.tags&.delete(:'transaction.name')
         | 
| 22 | 
            +
                    type = set.tags&.delete(:'transaction.type')
         | 
| 23 | 
            +
                    return unless name || type
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    set.transaction = { name: name, type: type }
         | 
| 26 | 
            +
                    set.tags = nil if set.tags.empty?
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def move_span(set)
         | 
| 30 | 
            +
                    type = set.tags&.delete(:'span.type')
         | 
| 31 | 
            +
                    subtype = set.tags&.delete(:'span.subtype')
         | 
| 32 | 
            +
                    return unless type
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    set.span = { type: type, subtype: subtype }
         | 
| 35 | 
            +
                    set.tags = nil if set.tags.empty?
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ElasticAPM
         | 
| 4 | 
            +
              module Metrics
         | 
| 5 | 
            +
                # @api private
         | 
| 6 | 
            +
                class VMSet < Set
         | 
| 7 | 
            +
                  include Logging
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def collect
         | 
| 10 | 
            +
                    read!
         | 
| 11 | 
            +
                    super
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
         | 
| 15 | 
            +
                  def read!
         | 
| 16 | 
            +
                    return if disabled?
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    stat = GC.stat
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    gauge(:'ruby.gc.count').value = stat[:count]
         | 
| 21 | 
            +
                    gauge(:'ruby.threads').value = Thread.list.count
         | 
| 22 | 
            +
                    gauge(:'ruby.heap.slots.live').value = stat[:heap_live_slots]
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    gauge(:'ruby.heap.slots.free').value = stat[:heap_free_slots]
         | 
| 25 | 
            +
                    gauge(:'ruby.heap.allocations.total').value =
         | 
| 26 | 
            +
                      stat[:total_allocated_objects]
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    return unless GC::Profiler.enabled?
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    @total_time ||= 0
         | 
| 31 | 
            +
                    @total_time += GC::Profiler.total_time
         | 
| 32 | 
            +
                    GC::Profiler.clear
         | 
| 33 | 
            +
                    gauge(:'ruby.gc.time').value = @total_time
         | 
| 34 | 
            +
                  rescue TypeError => e
         | 
| 35 | 
            +
                    error 'VM metrics encountered error: %s', e
         | 
| 36 | 
            +
                    debug('Backtrace:') { e.backtrace.join("\n") }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    disable!
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| @@ -3,17 +3,44 @@ | |
| 3 3 | 
             
            module ElasticAPM
         | 
| 4 4 | 
             
              # @api private
         | 
| 5 5 | 
             
              class Metricset
         | 
| 6 | 
            -
                def initialize( | 
| 6 | 
            +
                def initialize(
         | 
| 7 | 
            +
                  timestamp: Util.micros,
         | 
| 8 | 
            +
                  tags: nil,
         | 
| 9 | 
            +
                  transaction: nil,
         | 
| 10 | 
            +
                  span: nil,
         | 
| 11 | 
            +
                  **samples
         | 
| 12 | 
            +
                )
         | 
| 7 13 | 
             
                  @timestamp = timestamp
         | 
| 8 | 
            -
                  @ | 
| 14 | 
            +
                  @tags = tags
         | 
| 15 | 
            +
                  @transaction = transaction
         | 
| 16 | 
            +
                  @span = span
         | 
| 9 17 | 
             
                  @samples = samples
         | 
| 10 18 | 
             
                end
         | 
| 11 19 |  | 
| 12 | 
            -
                attr_accessor :timestamp
         | 
| 13 | 
            -
                attr_reader :samples | 
| 20 | 
            +
                attr_accessor :timestamp, :transaction, :span, :tags
         | 
| 21 | 
            +
                attr_reader :samples
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def merge_tags!(tags)
         | 
| 24 | 
            +
                  return unless tags
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  @tags ||= {}
         | 
| 27 | 
            +
                  @tags.merge! tags
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def tags?
         | 
| 31 | 
            +
                  tags&.any?
         | 
| 32 | 
            +
                end
         | 
| 14 33 |  | 
| 15 34 | 
             
                def empty?
         | 
| 16 35 | 
             
                  samples.empty?
         | 
| 17 36 | 
             
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def inspect
         | 
| 39 | 
            +
                  "<ElasticAPM::Metricset timestamp:#{timestamp}" \
         | 
| 40 | 
            +
                    " transaction:#{transaction.inspect}" \
         | 
| 41 | 
            +
                    " span:#{span.inspect}" \
         | 
| 42 | 
            +
                    " tags:#{tags.inspect}" \
         | 
| 43 | 
            +
                    " samples:#{samples.inspect}>"
         | 
| 44 | 
            +
                end
         | 
| 18 45 | 
             
              end
         | 
| 19 46 | 
             
            end
         | 
| @@ -12,6 +12,8 @@ module ElasticAPM # :nodoc: | |
| 12 12 | 
             
                  def self.register(name)
         | 
| 13 13 | 
             
                    Normalizers.register(name, self)
         | 
| 14 14 | 
             
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def backtrace(payload); end
         | 
| 15 17 | 
             
                end
         | 
| 16 18 |  | 
| 17 19 | 
             
                def self.register(name, klass)
         | 
| @@ -54,6 +56,10 @@ module ElasticAPM # :nodoc: | |
| 54 56 | 
             
                  def normalize(transaction, name, payload)
         | 
| 55 57 | 
             
                    self.for(name).normalize(transaction, name, payload)
         | 
| 56 58 | 
             
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def backtrace(name, payload)
         | 
| 61 | 
            +
                    self.for(name).backtrace(payload)
         | 
| 62 | 
            +
                  end
         | 
| 57 63 | 
             
                end
         | 
| 58 64 | 
             
              end
         | 
| 59 65 | 
             
            end
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ElasticAPM
         | 
| 4 | 
            +
              module Normalizers
         | 
| 5 | 
            +
                module Grape
         | 
| 6 | 
            +
                  # @api private
         | 
| 7 | 
            +
                  class EndpointRun < Normalizer
         | 
| 8 | 
            +
                    register 'endpoint_run.grape'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    TYPE = 'app'
         | 
| 11 | 
            +
                    SUBTYPE = 'resource'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    FRAMEWORK_NAME = 'Grape'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def normalize(transaction, _name, payload)
         | 
| 16 | 
            +
                      transaction.name = endpoint(payload[:env])
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                      if transaction_from_host_app?(transaction)
         | 
| 19 | 
            +
                        transaction.context.set_service(
         | 
| 20 | 
            +
                          framework_name: FRAMEWORK_NAME,
         | 
| 21 | 
            +
                          framework_version: ::Grape::VERSION
         | 
| 22 | 
            +
                        )
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      [transaction.name, TYPE, SUBTYPE, nil, nil]
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def backtrace(payload)
         | 
| 29 | 
            +
                      source_location = payload[:endpoint].source.source_location
         | 
| 30 | 
            +
                      ["#{source_location[0]}:#{source_location[1]}"]
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    private
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def transaction_from_host_app?(transaction)
         | 
| 36 | 
            +
                      transaction.config.framework_name != FRAMEWORK_NAME
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def endpoint(env)
         | 
| 40 | 
            +
                      route_name = env['api.endpoint']&.routes&.first&.pattern&.origin ||
         | 
| 41 | 
            +
                        env['REQUEST_PATH']
         | 
| 42 | 
            +
                      [env['REQUEST_METHOD'], route_name].join(' ')
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -11,31 +11,42 @@ module ElasticAPM | |
| 11 11 |  | 
| 12 12 | 
             
                    TYPE = 'db'
         | 
| 13 13 | 
             
                    ACTION = 'sql'
         | 
| 14 | 
            +
                    SKIP_NAMES = %w[SCHEMA CACHE].freeze
         | 
| 15 | 
            +
                    UNKNOWN = 'unknown'
         | 
| 14 16 |  | 
| 15 17 | 
             
                    def initialize(*args)
         | 
| 16 18 | 
             
                      super
         | 
| 17 19 |  | 
| 18 | 
            -
                      @subtype = lookup_adapter || 'unknown'
         | 
| 19 20 | 
             
                      @summarizer = SqlSummarizer.new
         | 
| 21 | 
            +
                      @adapters = {}
         | 
| 20 22 | 
             
                    end
         | 
| 21 23 |  | 
| 22 24 | 
             
                    def normalize(_transaction, _name, payload)
         | 
| 23 | 
            -
                      return :skip if  | 
| 25 | 
            +
                      return :skip if SKIP_NAMES.include?(payload[:name])
         | 
| 24 26 |  | 
| 25 27 | 
             
                      name = summarize(payload[:sql]) || payload[:name]
         | 
| 26 28 | 
             
                      context =
         | 
| 27 29 | 
             
                        Span::Context.new(db: { statement: payload[:sql], type: 'sql' })
         | 
| 28 | 
            -
                      [name, TYPE,  | 
| 30 | 
            +
                      [name, TYPE, subtype(payload), ACTION, context]
         | 
| 29 31 | 
             
                    end
         | 
| 30 32 |  | 
| 31 33 | 
             
                    private
         | 
| 32 34 |  | 
| 35 | 
            +
                    def subtype(payload)
         | 
| 36 | 
            +
                      cached_adapter_name(
         | 
| 37 | 
            +
                        payload[:connection] ? payload[:connection].adapter_name :
         | 
| 38 | 
            +
                          ::ActiveRecord::Base.connection_config[:adapter]
         | 
| 39 | 
            +
                      )
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 33 42 | 
             
                    def summarize(sql)
         | 
| 34 43 | 
             
                      @summarizer.summarize(sql)
         | 
| 35 44 | 
             
                    end
         | 
| 36 45 |  | 
| 37 | 
            -
                    def  | 
| 38 | 
            -
                       | 
| 46 | 
            +
                    def cached_adapter_name(adapter_name)
         | 
| 47 | 
            +
                      return UNKNOWN if adapter_name.nil? || adapter_name.empty?
         | 
| 48 | 
            +
                      @adapters[adapter_name] ||
         | 
| 49 | 
            +
                        (@adapters[adapter_name] = adapter_name.downcase)
         | 
| 39 50 | 
             
                    rescue StandardError
         | 
| 40 51 | 
             
                      nil
         | 
| 41 52 | 
             
                    end
         |