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
@@ -9,10 +9,13 @@ module ElasticAPM
9
9
  class SqlNormalizer < Normalizer
10
10
  register 'sql.active_record'
11
11
 
12
+ TYPE = 'db'
13
+ ACTION = 'sql'
14
+
12
15
  def initialize(*args)
13
16
  super
14
17
 
15
- @type = format('db.%s.sql', lookup_adapter || 'unknown').freeze
18
+ @subtype = lookup_adapter || 'unknown'
16
19
  @summarizer = SqlSummarizer.new
17
20
  end
18
21
 
@@ -22,7 +25,7 @@ module ElasticAPM
22
25
  name = summarize(payload[:sql]) || payload[:name]
23
26
  context =
24
27
  Span::Context.new(db: { statement: payload[:sql], type: 'sql' })
25
- [name, @type, context]
28
+ [name, TYPE, @subtype, ACTION, context]
26
29
  end
27
30
 
28
31
  private
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'elastic_apm/subscriber'
4
+
5
+ module ElasticAPM
6
+ # Module for explicitly starting the ElasticAPM agent and hooking into Rails.
7
+ # It is recommended to use the Railtie instead.
8
+ module Rails
9
+ extend self
10
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
11
+ # rubocop:disable Metrics/CyclomaticComplexity
12
+ # Start the ElasticAPM agent and hook into Rails.
13
+ # Note that the agent won't be started if the Rails console is being used.
14
+ #
15
+ # @param config [Config, Hash] An instance of Config or a Hash config.
16
+ # @return [true, nil] true if the agent was started, nil otherwise.
17
+ def start(config)
18
+ config = Config.new(config) unless config.is_a?(Config)
19
+ if (reason = should_skip?(config))
20
+ unless config.disable_start_message?
21
+ config.logger.info "Skipping because: #{reason}. " \
22
+ "Start manually with `ElasticAPM.start'"
23
+ end
24
+ return
25
+ end
26
+
27
+ ElasticAPM.start(config).tap do |agent|
28
+ attach_subscriber(agent)
29
+ end
30
+
31
+ if ElasticAPM.running? &&
32
+ !ElasticAPM.agent.config.disabled_spies.include?('action_dispatch')
33
+ require 'elastic_apm/spies/action_dispatch'
34
+ end
35
+ ElasticAPM.running?
36
+ rescue StandardError => e
37
+ config.logger.error format('Failed to start: %s', e.message)
38
+ config.logger.debug "Backtrace:\n" + e.backtrace.join("\n")
39
+ end
40
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
41
+ # rubocop:enable Metrics/CyclomaticComplexity
42
+
43
+ private
44
+
45
+ def should_skip?(_config)
46
+ if ::Rails.const_defined? 'Rails::Console'
47
+ return 'Rails console'
48
+ end
49
+
50
+ nil
51
+ end
52
+
53
+ def attach_subscriber(agent)
54
+ return unless agent
55
+
56
+ agent.instrumenter.subscriber = ElasticAPM::Subscriber.new(agent)
57
+ end
58
+ end
59
+ end
@@ -1,67 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'elastic_apm/subscriber'
3
+ require 'elastic_apm/rails'
4
4
 
5
5
  module ElasticAPM
6
6
  # @api private
7
- class Railtie < Rails::Railtie
7
+ class Railtie < ::Rails::Railtie
8
8
  config.elastic_apm = ActiveSupport::OrderedOptions.new
9
9
 
10
- Config::DEFAULTS.each { |option, value| config.elastic_apm[option] = value }
10
+ Config.schema.each do |key, args|
11
+ next unless args.length > 1
12
+ config.elastic_apm[key] = args.last[:default]
13
+ end
11
14
 
12
15
  initializer 'elastic_apm.initialize' do |app|
13
- config = Config.new(app.config.elastic_apm.merge(app: app)).tap do |c|
16
+ config = Config.new(app.config.elastic_apm).tap do |c|
17
+ c.app = app
18
+
14
19
  # Prepend Rails.root to log_path if present
15
20
  if c.log_path && !c.log_path.start_with?('/')
16
- c.log_path = Rails.root.join(c.log_path)
21
+ c.log_path = ::Rails.root.join(c.log_path)
17
22
  end
18
23
  end
19
24
 
20
- if start(config)
25
+ if Rails.start(config)
21
26
  app.middleware.insert 0, Middleware
22
27
  end
23
28
  end
24
-
25
- config.after_initialize do
26
- if ElasticAPM.running? &&
27
- !ElasticAPM.agent.config.disabled_spies.include?('action_dispatch')
28
- require 'elastic_apm/spies/action_dispatch'
29
- end
30
- end
31
-
32
- private
33
-
34
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
35
- def start(config)
36
- if (reason = should_skip?(config))
37
- unless config.disable_start_message?
38
- config.alert_logger.info "Skipping because: #{reason}. " \
39
- "Start manually with `ElasticAPM.start'"
40
- end
41
- return
42
- end
43
-
44
- ElasticAPM.start(config).tap do |agent|
45
- attach_subscriber(agent)
46
- end
47
- rescue StandardError => e
48
- config.alert_logger.error format('Failed to start: %s', e.message)
49
- config.alert_logger.debug "Backtrace:\n" + e.backtrace.join("\n")
50
- end
51
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
52
-
53
- def should_skip?(_config)
54
- if Rails.const_defined? 'Rails::Console'
55
- return 'Rails console'
56
- end
57
-
58
- nil
59
- end
60
-
61
- def attach_subscriber(agent)
62
- return unless agent
63
-
64
- agent.instrumenter.subscriber = ElasticAPM::Subscriber.new(agent)
65
- end
66
29
  end
67
30
  end
@@ -14,17 +14,26 @@ module ElasticAPM
14
14
 
15
15
  DEFAULT_TYPE = 'custom'
16
16
 
17
- # rubocop:disable Metrics/ParameterLists
17
+ # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
18
18
  def initialize(
19
19
  name:,
20
20
  transaction_id:,
21
21
  trace_context:,
22
22
  type: nil,
23
+ subtype: nil,
24
+ action: nil,
23
25
  context: nil,
24
26
  stacktrace_builder: nil
25
27
  )
26
28
  @name = name
27
- @type = type || DEFAULT_TYPE
29
+
30
+ if subtype.nil? && type&.include?('.')
31
+ @type, @subtype, @action = type.split('.')
32
+ else
33
+ @type = type || DEFAULT_TYPE
34
+ @subtype = subtype
35
+ @action = action
36
+ end
28
37
 
29
38
  @transaction_id = transaction_id
30
39
  @trace_context = trace_context
@@ -32,10 +41,23 @@ module ElasticAPM
32
41
  @context = context || Span::Context.new
33
42
  @stacktrace_builder = stacktrace_builder
34
43
  end
35
- # rubocop:enable Metrics/ParameterLists
36
-
37
- attr_accessor :name, :type, :original_backtrace, :trace_context
38
- attr_reader :context, :stacktrace, :duration, :timestamp, :transaction_id
44
+ # rubocop:enable Metrics/ParameterLists, Metrics/MethodLength
45
+
46
+ attr_accessor(
47
+ :action,
48
+ :name,
49
+ :original_backtrace,
50
+ :subtype,
51
+ :trace_context,
52
+ :type
53
+ )
54
+ attr_reader(
55
+ :context,
56
+ :duration,
57
+ :stacktrace,
58
+ :timestamp,
59
+ :transaction_id
60
+ )
39
61
 
40
62
  # life cycle
41
63
 
@@ -53,6 +75,7 @@ module ElasticAPM
53
75
  stop end_time
54
76
 
55
77
  build_stacktrace! if should_build_stacktrace?
78
+ self.original_backtrace = nil # release original
56
79
 
57
80
  self
58
81
  end
@@ -82,7 +105,6 @@ module ElasticAPM
82
105
 
83
106
  def build_stacktrace!
84
107
  @stacktrace = @stacktrace_builder.build(original_backtrace, type: :span)
85
- self.original_backtrace = nil # release original
86
108
  end
87
109
 
88
110
  def should_build_stacktrace?
@@ -5,6 +5,9 @@ module ElasticAPM
5
5
  module Spies
6
6
  # @api private
7
7
  class FaradaySpy
8
+ TYPE = 'ext'
9
+ SUBTYPE = 'faraday'
10
+
8
11
  def self.without_net_http
9
12
  return yield unless defined?(NetHTTPSpy)
10
13
 
@@ -37,9 +40,13 @@ module ElasticAPM
37
40
  end
38
41
 
39
42
  name = "#{method.upcase} #{host}"
40
- type = "ext.faraday.#{method}"
41
43
 
42
- ElasticAPM.with_span name, type do |span|
44
+ ElasticAPM.with_span(
45
+ name,
46
+ TYPE,
47
+ subtype: SUBTYPE,
48
+ action: method.to_s
49
+ ) do |span|
43
50
  ElasticAPM::Spies::FaradaySpy.without_net_http do
44
51
  trace_context = span&.trace_context || transaction.trace_context
45
52
 
@@ -5,6 +5,9 @@ module ElasticAPM
5
5
  module Spies
6
6
  # @api private
7
7
  class HTTPSpy
8
+ TYPE = 'ext'
9
+ SUBTYPE = 'http_rb'
10
+
8
11
  # rubocop:disable Metrics/MethodLength
9
12
  def install
10
13
  ::HTTP::Client.class_eval do
@@ -19,9 +22,13 @@ module ElasticAPM
19
22
  host = req.uri.host
20
23
 
21
24
  name = "#{method} #{host}"
22
- type = "ext.http_rb.#{method}"
23
25
 
24
- ElasticAPM.with_span name, type do |span|
26
+ ElasticAPM.with_span(
27
+ name,
28
+ TYPE,
29
+ subtype: SUBTYPE,
30
+ action: method
31
+ ) do |span|
25
32
  trace_context = span&.trace_context || transaction.trace_context
26
33
  req['Elastic-Apm-Traceparent'] = trace_context.to_header
27
34
  perform_without_apm(req, options)
@@ -14,7 +14,9 @@ module ElasticAPM
14
14
 
15
15
  # @api private
16
16
  class Subscriber
17
- TYPE = 'db.mongodb.query'
17
+ TYPE = 'db'
18
+ SUBTYPE = 'mongodb'
19
+ ACTION = 'query'
18
20
 
19
21
  def initialize
20
22
  @events = {}
@@ -34,18 +36,31 @@ module ElasticAPM
34
36
 
35
37
  private
36
38
 
39
+ # rubocop:disable Metrics/MethodLength
37
40
  def push_event(event)
38
41
  return unless ElasticAPM.current_transaction
42
+ # Some MongoDB commands are not on collections but rather are db
43
+ # admin commands. For these commands, the value at the `command_name`
44
+ # key is the integer 1.
45
+ unless event.command[event.command_name] == 1
46
+ collection = event.command[event.command_name]
47
+ end
48
+ name = [event.database_name,
49
+ collection,
50
+ event.command_name].compact.join('.')
39
51
 
40
52
  span =
41
53
  ElasticAPM.start_span(
42
- event.command_name.to_s,
54
+ name,
43
55
  TYPE,
56
+ subtype: SUBTYPE,
57
+ action: ACTION,
44
58
  context: build_context(event)
45
59
  )
46
60
 
47
61
  @events[event.operation_id] = span
48
62
  end
63
+ # rubocop:enable Metrics/MethodLength
49
64
 
50
65
  def pop_event(event)
51
66
  return unless (curr = ElasticAPM.current_span)
@@ -58,7 +73,7 @@ module ElasticAPM
58
73
  Span::Context.new(
59
74
  db: {
60
75
  instance: event.database_name,
61
- statement: nil,
76
+ statement: event.command.to_s,
62
77
  type: 'mongodb',
63
78
  user: nil
64
79
  }
@@ -6,6 +6,8 @@ module ElasticAPM
6
6
  # @api private
7
7
  class NetHTTPSpy
8
8
  KEY = :__elastic_apm_net_http_disabled
9
+ TYPE = 'ext'
10
+ SUBTYPE = 'net_http'
9
11
 
10
12
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
11
13
  class << self
@@ -46,9 +48,13 @@ module ElasticAPM
46
48
  host ||= address
47
49
 
48
50
  name = "#{method} #{host}"
49
- type = "ext.net_http.#{method}"
50
51
 
51
- ElasticAPM.with_span name, type do |span|
52
+ ElasticAPM.with_span(
53
+ name,
54
+ TYPE,
55
+ subtype: SUBTYPE,
56
+ action: method.to_s
57
+ ) do |span|
52
58
  trace_context = span&.trace_context || transaction.trace_context
53
59
  req['Elastic-Apm-Traceparent'] = trace_context.to_header
54
60
  request_without_apm(req, body, &block)
@@ -32,6 +32,8 @@ module ElasticAPM
32
32
  to = lineno + padding - 1
33
33
  file_lines = read_lines(abs_path, from..to)
34
34
 
35
+ return unless file_lines
36
+
35
37
  self.context_line = file_lines[padding]
36
38
  self.pre_context = file_lines.first(padding)
37
39
  self.post_context = file_lines.last(padding)
@@ -43,7 +45,7 @@ module ElasticAPM
43
45
  def read_lines(path, range)
44
46
  File.readlines(path)[range]
45
47
  rescue Errno::ENOENT
46
- []
48
+ nil
47
49
  end
48
50
  end
49
51
  end
@@ -67,8 +67,8 @@ module ElasticAPM
67
67
  def library_frame?(config, abs_path)
68
68
  return false unless abs_path
69
69
 
70
- if abs_path.start_with?(config.root_path)
71
- return true if abs_path.start_with?(config.root_path + '/vendor')
70
+ if abs_path.start_with?(config.__root_path)
71
+ return true if abs_path.start_with?(config.__root_path + '/vendor')
72
72
  return false
73
73
  end
74
74
 
@@ -29,8 +29,8 @@ module ElasticAPM
29
29
 
30
30
  Notification = Struct.new(:id, :span)
31
31
 
32
+ # rubocop:disable Metrics/MethodLength
32
33
  def start(name, id, payload)
33
- # debug "AS::Notification#start:#{name}:#{id}"
34
34
  return unless (transaction = @agent.current_transaction)
35
35
 
36
36
  normalized = @normalizers.normalize(transaction, name, payload)
@@ -39,12 +39,20 @@ module ElasticAPM
39
39
  if normalized == :skip
40
40
  nil
41
41
  else
42
- name, type, context = normalized
43
- @agent.start_span(name, type, context: context)
42
+ name, type, subtype, action, context = normalized
43
+
44
+ @agent.start_span(
45
+ name,
46
+ type,
47
+ subtype: subtype,
48
+ action: action,
49
+ context: context
50
+ )
44
51
  end
45
52
 
46
53
  transaction.notifications << Notification.new(id, span)
47
54
  end
55
+ # rubocop:enable Metrics/MethodLength
48
56
 
49
57
  def finish(_name, id, _payload)
50
58
  # debug "AS::Notification#finish:#{name}:#{id}"
@@ -7,6 +7,7 @@ require 'elastic_apm/transport/connection/http'
7
7
 
8
8
  module ElasticAPM
9
9
  module Transport
10
+ # rubocop:disable Metrics/ClassLength
10
11
  # @api private
11
12
  class Connection
12
13
  include Logging
@@ -130,13 +131,26 @@ module ElasticAPM
130
131
  ].join(' ')
131
132
  end
132
133
 
133
- def build_ssl_context
134
- return unless @config.use_ssl? && @config.server_ca_cert
134
+ def build_ssl_context # rubocop:disable Metrics/MethodLength
135
+ return unless @config.use_ssl?
135
136
 
136
137
  OpenSSL::SSL::SSLContext.new.tap do |context|
137
- context.ca_file = @config.server_ca_cert
138
+ if @config.server_ca_cert
139
+ context.ca_file = @config.server_ca_cert
140
+ else
141
+ context.cert_store =
142
+ OpenSSL::X509::Store.new.tap(&:set_default_paths)
143
+ end
144
+
145
+ context.verify_mode =
146
+ if @config.verify_server_cert
147
+ OpenSSL::SSL::VERIFY_PEER
148
+ else
149
+ OpenSSL::SSL::VERIFY_NONE
150
+ end
138
151
  end
139
152
  end
140
153
  end
154
+ # rubocop:enable Metrics/ClassLength
141
155
  end
142
156
  end