elastic-apm 2.8.1 → 2.11.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/.jenkins_codecov.yml +5 -0
  3. data/.ci/.jenkins_exclude.yml +63 -0
  4. data/.ci/.jenkins_framework.yml +9 -0
  5. data/.ci/.jenkins_master_framework.yml +3 -0
  6. data/.ci/.jenkins_ruby.yml +11 -0
  7. data/.ci/Jenkinsfile +268 -0
  8. data/.ci/bin/check_paths_for_matches.py +80 -0
  9. data/.ci/downstreamTests.groovy +188 -0
  10. data/.ci/jobs/apm-agent-ruby-downstream.yml +37 -0
  11. data/.ci/jobs/apm-agent-ruby-linting-mbp.yml +38 -0
  12. data/.ci/jobs/apm-agent-ruby-mbp.yml +41 -0
  13. data/.ci/jobs/apm-agent-ruby.yml +4 -0
  14. data/.ci/jobs/defaults.yml +24 -0
  15. data/.ci/linting.groovy +32 -0
  16. data/.ci/prepare-git-context.sh +23 -0
  17. data/.pre-commit-config.yaml +22 -0
  18. data/.rspec +0 -1
  19. data/.rubocop.yml +3 -3
  20. data/CHANGELOG.md +59 -2
  21. data/docs/api.asciidoc +24 -7
  22. data/docs/configuration.asciidoc +43 -4
  23. data/docs/index.asciidoc +2 -0
  24. data/docs/log-correlation.asciidoc +96 -0
  25. data/docs/metrics.asciidoc +77 -6
  26. data/lib/elastic_apm.rb +37 -5
  27. data/lib/elastic_apm/agent.rb +29 -4
  28. data/lib/elastic_apm/central_config.rb +141 -0
  29. data/lib/elastic_apm/central_config/cache_control.rb +34 -0
  30. data/lib/elastic_apm/config.rb +165 -340
  31. data/lib/elastic_apm/config/bytes.rb +25 -0
  32. data/lib/elastic_apm/config/duration.rb +6 -8
  33. data/lib/elastic_apm/config/options.rb +134 -0
  34. data/lib/elastic_apm/config/regexp_list.rb +13 -0
  35. data/lib/elastic_apm/context_builder.rb +2 -0
  36. data/lib/elastic_apm/error/exception.rb +3 -1
  37. data/lib/elastic_apm/error_builder.rb +6 -3
  38. data/lib/elastic_apm/instrumenter.rb +6 -0
  39. data/lib/elastic_apm/metadata.rb +2 -1
  40. data/lib/elastic_apm/metrics.rb +2 -1
  41. data/lib/elastic_apm/metrics/vm.rb +60 -0
  42. data/lib/elastic_apm/normalizers/action_controller.rb +5 -2
  43. data/lib/elastic_apm/normalizers/action_mailer.rb +5 -2
  44. data/lib/elastic_apm/normalizers/action_view.rb +14 -9
  45. data/lib/elastic_apm/normalizers/active_record.rb +5 -2
  46. data/lib/elastic_apm/rails.rb +59 -0
  47. data/lib/elastic_apm/railtie.rb +11 -48
  48. data/lib/elastic_apm/span.rb +29 -7
  49. data/lib/elastic_apm/spies/faraday.rb +9 -2
  50. data/lib/elastic_apm/spies/http.rb +9 -2
  51. data/lib/elastic_apm/spies/mongo.rb +18 -3
  52. data/lib/elastic_apm/spies/net_http.rb +8 -2
  53. data/lib/elastic_apm/stacktrace/frame.rb +3 -1
  54. data/lib/elastic_apm/stacktrace_builder.rb +2 -2
  55. data/lib/elastic_apm/subscriber.rb +11 -3
  56. data/lib/elastic_apm/transport/connection.rb +17 -3
  57. data/lib/elastic_apm/transport/connection/proxy_pipe.rb +8 -1
  58. data/lib/elastic_apm/transport/filters/secrets_filter.rb +3 -1
  59. data/lib/elastic_apm/transport/serializers/error_serializer.rb +12 -2
  60. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +6 -1
  61. data/lib/elastic_apm/transport/serializers/span_serializer.rb +11 -3
  62. data/lib/elastic_apm/version.rb +1 -1
  63. metadata +26 -4
  64. data/Jenkinsfile +0 -280
  65. data/lib/elastic_apm/config/size.rb +0 -28
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Config
5
+ # @api private
6
+ class Bytes
7
+ MULTIPLIERS = {
8
+ 'kb' => 1024,
9
+ 'mb' => 1024 * 1_000,
10
+ 'gb' => 1024 * 100_000
11
+ }.freeze
12
+ REGEX = /^(\d+)(b|kb|mb|gb)?$/i.freeze
13
+
14
+ def initialize(default_unit: 'kb')
15
+ @default_unit = default_unit
16
+ end
17
+
18
+ def call(value)
19
+ _, amount, unit = REGEX.match(String(value)).to_a
20
+ unit ||= @default_unit
21
+ MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
22
+ end
23
+ end
24
+ end
25
+ end
@@ -7,18 +7,16 @@ module ElasticAPM
7
7
  MULTIPLIERS = { 'ms' => 0.001, 'm' => 60 }.freeze
8
8
  REGEX = /^(-)?(\d+)(m|ms|s)?$/i.freeze
9
9
 
10
- def initialize(seconds)
11
- @seconds = seconds
10
+ def initialize(default_unit: 's')
11
+ @default_unit = default_unit
12
12
  end
13
13
 
14
- attr_accessor :seconds
15
-
16
- def self.parse(str, default_unit:)
17
- _, negative, amount, unit = REGEX.match(str).to_a
18
- unit ||= default_unit
14
+ def call(str)
15
+ _, negative, amount, unit = REGEX.match(String(str)).to_a
16
+ unit ||= @default_unit
19
17
  seconds = MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
20
18
  seconds = 0 - seconds if negative
21
- new(seconds)
19
+ seconds
22
20
  end
23
21
  end
24
22
  end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Config
5
+ # @api private
6
+ module Options
7
+ # @api private
8
+ class Option
9
+ def initialize(
10
+ key,
11
+ value: nil,
12
+ type: :string,
13
+ default: nil,
14
+ converter: nil
15
+ )
16
+ @key = key
17
+ @type = type
18
+ @default = default
19
+ @converter = converter
20
+
21
+ set(value || default)
22
+ end
23
+
24
+ attr_reader :key, :value, :default, :type
25
+
26
+ def set(value)
27
+ @value = normalize(value)
28
+ end
29
+
30
+ def env_key
31
+ "ELASTIC_APM_#{key.upcase}"
32
+ end
33
+
34
+ private
35
+
36
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
37
+ def normalize(val)
38
+ return unless val
39
+
40
+ if @converter
41
+ return @converter.call(val)
42
+ end
43
+
44
+ case type
45
+ when :string then val.to_s
46
+ when :int then val.to_i
47
+ when :float then val.to_f
48
+ when :bool then normalize_bool(val)
49
+ when :list then normalize_list(val)
50
+ when :dict then normalize_dict(val)
51
+ else
52
+ # raise "Unknown options type '#{type.inspect}'"
53
+ val
54
+ end
55
+ end
56
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
57
+
58
+ def normalize_bool(val)
59
+ return val unless val.is_a?(String)
60
+ !%w[0 false].include?(val.strip.downcase)
61
+ end
62
+
63
+ def normalize_list(val)
64
+ return Array(val) unless val.is_a?(String)
65
+ val.split(/[ ,]/)
66
+ end
67
+
68
+ def normalize_dict(val)
69
+ return val unless val.is_a?(String)
70
+ Hash[val.split(/[&,]/).map { |kv| kv.split('=') }]
71
+ end
72
+ end
73
+
74
+ # @api private
75
+ module ClassMethods
76
+ def schema
77
+ @schema ||= {}
78
+ end
79
+
80
+ def option(*args)
81
+ key = args.shift
82
+ schema[key] = *args
83
+ end
84
+ end
85
+
86
+ # @api private
87
+ module InstanceMethods
88
+ def load_schema
89
+ Hash[self.class.schema.map do |key, args|
90
+ [key, Option.new(key, *args)]
91
+ end]
92
+ end
93
+
94
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
95
+ def method_missing(name, *value)
96
+ name_str = name.to_s
97
+
98
+ if name_str.end_with?('=')
99
+ key = name_str[0...-1].to_sym
100
+ set(key, value.first)
101
+
102
+ elsif name_str.end_with?('?')
103
+ key = name_str[0...-1].to_sym
104
+ options.key?(key) ? options[key].value : super
105
+
106
+ elsif options.key?(name)
107
+ options.fetch(name).value
108
+
109
+ else
110
+ super
111
+ end
112
+ end
113
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
114
+
115
+ def [](key)
116
+ options[key]
117
+ end
118
+ alias :get :[]
119
+
120
+ def set(key, value)
121
+ options.fetch(key.to_sym).set(value)
122
+ rescue KeyError
123
+ warn format("Unknown option '%s'", key)
124
+ nil
125
+ end
126
+ end
127
+
128
+ def self.extended(kls)
129
+ kls.instance_eval { extend ClassMethods }
130
+ kls.class_eval { include InstanceMethods }
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ class Config
5
+ # @api private
6
+ class RegexpList
7
+ def call(value)
8
+ value = value.is_a?(String) ? value.split(',') : Array(value)
9
+ value.map(&Regexp.method(:new))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -38,6 +38,8 @@ module ElasticAPM
38
38
  request.headers = headers if config.capture_headers?
39
39
  request.env = env if config.capture_env?
40
40
 
41
+ request.cookies = req.cookies
42
+
41
43
  context
42
44
  end
43
45
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
@@ -11,6 +11,7 @@ module ElasticAPM
11
11
  "#{exception.class}: #{exception.message}"
12
12
  @type = exception.class.to_s
13
13
  @module = format_module exception
14
+ @cause = exception.cause && Exception.new(exception.cause)
14
15
 
15
16
  attrs.each do |key, val|
16
17
  send(:"#{key}=", val)
@@ -24,7 +25,8 @@ module ElasticAPM
24
25
  :message,
25
26
  :module,
26
27
  :stacktrace,
27
- :type
28
+ :type,
29
+ :cause
28
30
  )
29
31
 
30
32
  private
@@ -52,12 +52,15 @@ module ElasticAPM
52
52
  error.culprit = stacktrace.frames.first.function
53
53
  end
54
54
 
55
- # rubocop:disable Metrics/AbcSize
55
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
56
56
  def add_current_transaction_fields(error, transaction)
57
57
  return unless transaction
58
58
 
59
59
  error.transaction_id = transaction.id
60
- error.transaction = { sampled: transaction.sampled? }
60
+ error.transaction = {
61
+ sampled: transaction.sampled?,
62
+ type: transaction.type
63
+ }
61
64
  error.trace_id = transaction.trace_id
62
65
  error.parent_id = ElasticAPM.current_span&.id || transaction.id
63
66
 
@@ -66,6 +69,6 @@ module ElasticAPM
66
69
  Util.reverse_merge!(error.context.tags, transaction.context.tags)
67
70
  Util.reverse_merge!(error.context.custom, transaction.context.custom)
68
71
  end
69
- # rubocop:enable Metrics/AbcSize
72
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
70
73
  end
71
74
  end
@@ -134,9 +134,12 @@ module ElasticAPM
134
134
 
135
135
  # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
136
136
  # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
137
+ # rubocop:disable Metrics/ParameterLists
137
138
  def start_span(
138
139
  name,
139
140
  type = nil,
141
+ subtype: nil,
142
+ action: nil,
140
143
  backtrace: nil,
141
144
  context: nil,
142
145
  trace_context: nil
@@ -155,6 +158,8 @@ module ElasticAPM
155
158
 
156
159
  span = Span.new(
157
160
  name: name,
161
+ subtype: subtype,
162
+ action: action,
158
163
  transaction_id: transaction.id,
159
164
  trace_context: trace_context || parent.trace_context.child,
160
165
  type: type,
@@ -170,6 +175,7 @@ module ElasticAPM
170
175
 
171
176
  span.start
172
177
  end
178
+ # rubocop:enable Metrics/ParameterLists
173
179
  # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
174
180
  # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
175
181
 
@@ -7,9 +7,10 @@ module ElasticAPM
7
7
  @service = ServiceInfo.new(config)
8
8
  @process = ProcessInfo.new(config)
9
9
  @system = SystemInfo.new(config)
10
+ @labels = config.global_labels
10
11
  end
11
12
 
12
- attr_reader :service, :process, :system
13
+ attr_reader :service, :process, :system, :labels
13
14
  end
14
15
  end
15
16
 
@@ -24,7 +24,7 @@ module ElasticAPM
24
24
  def initialize(config, tags: nil, &block)
25
25
  @config = config
26
26
  @tags = tags
27
- @samplers = [CpuMem].map do |kls|
27
+ @samplers = [CpuMem, VM].map do |kls|
28
28
  debug "Adding metrics collector '#{kls}'"
29
29
  kls.new(config)
30
30
  end
@@ -95,3 +95,4 @@ module ElasticAPM
95
95
  end
96
96
 
97
97
  require 'elastic_apm/metrics/cpu_mem'
98
+ require 'elastic_apm/metrics/vm'
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ElasticAPM
4
+ module Metrics
5
+ # @api private
6
+ class VM
7
+ include Logging
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ @total_time = 0
12
+ @disabled = false
13
+ end
14
+
15
+ attr_reader :config
16
+ attr_writer :disabled
17
+
18
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
19
+ # rubocop:disable Metrics/CyclomaticComplexity
20
+ def collect
21
+ return if disabled?
22
+
23
+ stat = GC.stat
24
+ thread_count = Thread.list.count
25
+
26
+ sample = {
27
+ 'ruby.gc.count': stat[:count],
28
+ 'ruby.threads': thread_count
29
+ }
30
+
31
+ (live_slots = stat[:heap_live_slots]) &&
32
+ sample[:'ruby.heap.slots.live'] = live_slots
33
+ (heap_slots = stat[:heap_free_slots]) &&
34
+ sample[:'ruby.heap.slots.free'] = heap_slots
35
+ (allocated = stat[:total_allocated_objects]) &&
36
+ sample[:'ruby.heap.allocations.total'] = allocated
37
+
38
+ return sample unless GC::Profiler.enabled?
39
+
40
+ @total_time += GC::Profiler.total_time
41
+ GC::Profiler.clear
42
+ sample[:'ruby.gc.time'] = @total_time
43
+
44
+ sample
45
+ rescue TypeError => e
46
+ error 'VM metrics encountered error: %s', e
47
+ debug('Backtrace:') { e.backtrace.join("\n") }
48
+
49
+ @disabled = true
50
+ nil
51
+ end
52
+ # rubocop:enable Metrics/CyclomaticComplexity
53
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
54
+
55
+ def disabled?
56
+ @disabled
57
+ end
58
+ end
59
+ end
60
+ end
@@ -6,11 +6,14 @@ module ElasticAPM
6
6
  # @api private
7
7
  class ProcessActionNormalizer < Normalizer
8
8
  register 'process_action.action_controller'
9
- TYPE = 'app.controller.action'
9
+
10
+ TYPE = 'app'
11
+ SUBTYPE = 'controller'
12
+ ACTION = 'action'
10
13
 
11
14
  def normalize(transaction, _name, payload)
12
15
  transaction.name = endpoint(payload)
13
- [transaction.name, TYPE, nil]
16
+ [transaction.name, TYPE, SUBTYPE, ACTION, nil]
14
17
  end
15
18
 
16
19
  private
@@ -6,10 +6,13 @@ module ElasticAPM
6
6
  # @api private
7
7
  class ProcessActionNormalizer < Normalizer
8
8
  register 'process.action_mailer'
9
- TYPE = 'app.mailer.action'
9
+
10
+ TYPE = 'app'
11
+ SUBTYPE = 'mailer'
12
+ ACTION = 'action'
10
13
 
11
14
  def normalize(_transaction, _name, payload)
12
- [endpoint(payload), TYPE, nil]
15
+ [endpoint(payload), TYPE, SUBTYPE, ACTION, nil]
13
16
  end
14
17
 
15
18
  private
@@ -7,8 +7,8 @@ module ElasticAPM
7
7
  class RenderNormalizer < Normalizer
8
8
  private
9
9
 
10
- def normalize_render(payload, type)
11
- [path_for(payload[:identifier]), type, nil]
10
+ def normalize_render(payload, type, subtype, action)
11
+ [path_for(payload[:identifier]), type, subtype, action, nil]
12
12
  end
13
13
 
14
14
  def path_for(path)
@@ -19,7 +19,7 @@ module ElasticAPM
19
19
  end
20
20
 
21
21
  def view_path(path)
22
- root = @config.view_paths.find { |vp| path.start_with?(vp) }
22
+ root = @config.__view_paths.find { |vp| path.start_with?(vp) }
23
23
  return unless root
24
24
 
25
25
  strip_root(root, path)
@@ -41,30 +41,35 @@ module ElasticAPM
41
41
  # @api private
42
42
  class RenderTemplateNormalizer < RenderNormalizer
43
43
  register 'render_template.action_view'
44
- TYPE = 'template.view'
44
+ TYPE = 'template'
45
+ SUBTYPE = 'view'
45
46
 
46
47
  def normalize(_transaction, _name, payload)
47
- normalize_render(payload, TYPE)
48
+ normalize_render(payload, TYPE, SUBTYPE, nil)
48
49
  end
49
50
  end
50
51
 
51
52
  # @api private
52
53
  class RenderPartialNormalizer < RenderNormalizer
53
54
  register 'render_partial.action_view'
54
- TYPE = 'template.view.partial'
55
+ TYPE = 'template'
56
+ SUBTYPE = 'view'
57
+ ACTION = 'partial'
55
58
 
56
59
  def normalize(_transaction, _name, payload)
57
- normalize_render(payload, TYPE)
60
+ normalize_render(payload, TYPE, SUBTYPE, ACTION)
58
61
  end
59
62
  end
60
63
 
61
64
  # @api private
62
65
  class RenderCollectionNormalizer < RenderNormalizer
63
66
  register 'render_collection.action_view'
64
- TYPE = 'template.view.collection'
67
+ TYPE = 'template'
68
+ SUBTYPE = 'view'
69
+ ACTION = 'collection'
65
70
 
66
71
  def normalize(_transaction, _name, payload)
67
- normalize_render(payload, TYPE)
72
+ normalize_render(payload, TYPE, SUBTYPE, ACTION)
68
73
  end
69
74
  end
70
75
  end