elastic-apm 3.10.1 → 3.13.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/labeler-config.yml +3 -0
  3. data/.github/workflows/labeler.yml +16 -0
  4. data/.rubocop.yml +33 -4
  5. data/CHANGELOG.asciidoc +67 -0
  6. data/Gemfile +5 -6
  7. data/Rakefile +10 -10
  8. data/docs/api.asciidoc +2 -1
  9. data/docs/configuration.asciidoc +4 -3
  10. data/lib/elastic_apm.rb +14 -2
  11. data/lib/elastic_apm/central_config.rb +7 -2
  12. data/lib/elastic_apm/config.rb +51 -19
  13. data/lib/elastic_apm/config/log_level_map.rb +47 -0
  14. data/lib/elastic_apm/config/options.rb +2 -1
  15. data/lib/elastic_apm/config/round_float.rb +31 -0
  16. data/lib/elastic_apm/config/wildcard_pattern_list.rb +2 -0
  17. data/lib/elastic_apm/context.rb +2 -8
  18. data/lib/elastic_apm/graphql.rb +2 -0
  19. data/lib/elastic_apm/grpc.rb +3 -3
  20. data/lib/elastic_apm/instrumenter.rb +11 -4
  21. data/lib/elastic_apm/metadata.rb +3 -1
  22. data/lib/elastic_apm/metadata/cloud_info.rb +130 -0
  23. data/lib/elastic_apm/metadata/service_info.rb +2 -2
  24. data/lib/elastic_apm/metadata/system_info/container_info.rb +16 -5
  25. data/lib/elastic_apm/metrics.rb +1 -0
  26. data/lib/elastic_apm/metrics/cpu_mem_set.rb +1 -0
  27. data/lib/elastic_apm/middleware.rb +8 -3
  28. data/lib/elastic_apm/opentracing.rb +2 -1
  29. data/lib/elastic_apm/span.rb +14 -0
  30. data/lib/elastic_apm/span/context/db.rb +1 -1
  31. data/lib/elastic_apm/spies/delayed_job.rb +8 -2
  32. data/lib/elastic_apm/spies/dynamo_db.rb +8 -1
  33. data/lib/elastic_apm/spies/elasticsearch.rb +10 -2
  34. data/lib/elastic_apm/spies/faraday.rb +5 -2
  35. data/lib/elastic_apm/spies/http.rb +1 -0
  36. data/lib/elastic_apm/spies/mongo.rb +6 -2
  37. data/lib/elastic_apm/spies/net_http.rb +3 -0
  38. data/lib/elastic_apm/spies/rake.rb +4 -2
  39. data/lib/elastic_apm/spies/resque.rb +4 -2
  40. data/lib/elastic_apm/spies/sequel.rb +12 -1
  41. data/lib/elastic_apm/spies/shoryuken.rb +2 -0
  42. data/lib/elastic_apm/spies/sidekiq.rb +2 -0
  43. data/lib/elastic_apm/spies/sneakers.rb +2 -0
  44. data/lib/elastic_apm/spies/sucker_punch.rb +2 -0
  45. data/lib/elastic_apm/sql/signature.rb +4 -2
  46. data/lib/elastic_apm/sql/tokenizer.rb +2 -2
  47. data/lib/elastic_apm/stacktrace/frame.rb +1 -0
  48. data/lib/elastic_apm/stacktrace_builder.rb +2 -4
  49. data/lib/elastic_apm/trace_context.rb +1 -3
  50. data/lib/elastic_apm/trace_context/traceparent.rb +2 -6
  51. data/lib/elastic_apm/trace_context/tracestate.rb +114 -9
  52. data/lib/elastic_apm/transaction.rb +41 -7
  53. data/lib/elastic_apm/transport/connection.rb +2 -1
  54. data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +16 -16
  55. data/lib/elastic_apm/transport/filters/secrets_filter.rb +35 -12
  56. data/lib/elastic_apm/transport/serializers.rb +9 -6
  57. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +44 -4
  58. data/lib/elastic_apm/transport/serializers/span_serializer.rb +3 -3
  59. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +2 -0
  60. data/lib/elastic_apm/transport/user_agent.rb +3 -3
  61. data/lib/elastic_apm/transport/worker.rb +1 -0
  62. data/lib/elastic_apm/util.rb +2 -0
  63. data/lib/elastic_apm/util/deep_dup.rb +65 -0
  64. data/lib/elastic_apm/util/precision_validator.rb +46 -0
  65. data/lib/elastic_apm/version.rb +1 -1
  66. metadata +10 -3
@@ -123,6 +123,7 @@ module ElasticAPM
123
123
  end
124
124
 
125
125
  attr_accessor :trace_context
126
+
126
127
  def_delegators :trace_context, :trace_id, :id, :parent_id
127
128
 
128
129
  def self.from_header(header)
@@ -345,7 +346,7 @@ module ElasticAPM
345
346
  context = context_from_child_of(child_of) ||
346
347
  context_from_references(references) ||
347
348
  context_from_active_scope(ignore_active_scope)
348
- return context.child if context&.respond_to?(:child)
349
+ return context.child if context.respond_to?(:child)
349
350
 
350
351
  context
351
352
  end
@@ -25,6 +25,17 @@ module ElasticAPM
25
25
  extend Forwardable
26
26
  include ChildDurations::Methods
27
27
 
28
+ # @api private
29
+ class Outcome
30
+ FAILURE = "failure"
31
+ SUCCESS = "success"
32
+ UNKNOWN = "unknown"
33
+
34
+ def self.from_http_status(code)
35
+ code.to_i >= 400 ? FAILURE : SUCCESS
36
+ end
37
+ end
38
+
28
39
  DEFAULT_TYPE = 'custom'
29
40
 
30
41
  # rubocop:disable Metrics/ParameterLists
@@ -53,6 +64,7 @@ module ElasticAPM
53
64
  @transaction = transaction
54
65
  @parent = parent
55
66
  @trace_context = trace_context || parent.trace_context.child
67
+ @sample_rate = transaction.sample_rate
56
68
 
57
69
  @context = context || Span::Context.new(sync: sync)
58
70
  @stacktrace_builder = stacktrace_builder
@@ -65,6 +77,7 @@ module ElasticAPM
65
77
  :action,
66
78
  :name,
67
79
  :original_backtrace,
80
+ :outcome,
68
81
  :subtype,
69
82
  :trace_context,
70
83
  :type
@@ -73,6 +86,7 @@ module ElasticAPM
73
86
  :context,
74
87
  :duration,
75
88
  :parent,
89
+ :sample_rate,
76
90
  :self_time,
77
91
  :stacktrace,
78
92
  :timestamp,
@@ -30,7 +30,7 @@ module ElasticAPM
30
30
  rows_affected: nil
31
31
  )
32
32
  @instance = instance
33
- @statement = statement
33
+ @statement = statement&.encode('utf-8', invalid: :replace, undef: :replace)
34
34
  @type = type
35
35
  @user = user
36
36
  @rows_affected = rows_affected
@@ -41,10 +41,12 @@ module ElasticAPM
41
41
  job_name = name_from_payload(job.payload_object)
42
42
  transaction = ElasticAPM.start_transaction(job_name, TYPE)
43
43
  job.invoke_job_without_apm(*args, &block)
44
- transaction.done 'success'
44
+ transaction&.done 'success'
45
+ transaction&.outcome = Transaction::Outcome::SUCCESS
45
46
  rescue ::Exception => e
46
47
  ElasticAPM.report(e, handled: false)
47
- transaction.done 'error'
48
+ transaction&.done 'error'
49
+ transaction&.outcome = Transaction::Outcome::FAILURE
48
50
  raise
49
51
  ensure
50
52
  ElasticAPM.end_transaction
@@ -53,6 +55,10 @@ module ElasticAPM
53
55
  def self.name_from_payload(payload_object)
54
56
  if payload_object.is_a?(::Delayed::PerformableMethod)
55
57
  performable_method_name(payload_object)
58
+ elsif payload_object.instance_of?(
59
+ ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper
60
+ )
61
+ payload_object.job_data['job_class']
56
62
  else
57
63
  payload_object.class.name
58
64
  end
@@ -25,9 +25,11 @@ module ElasticAPM
25
25
  def self.without_net_http
26
26
  return yield unless defined?(NetHTTPSpy)
27
27
 
28
+ # rubocop:disable Style/ExplicitBlockArgument
28
29
  ElasticAPM::Spies::NetHTTPSpy.disable_in do
29
30
  yield
30
31
  end
32
+ # rubocop:enable Style/ExplicitBlockArgument
31
33
  end
32
34
 
33
35
  def install
@@ -37,7 +39,12 @@ module ElasticAPM
37
39
  alias :"#{operation_name}_without_apm" :"#{operation_name}"
38
40
 
39
41
  define_method(operation_name) do |params = {}, options = {}|
40
- ElasticAPM.with_span(operation_name, 'db', subtype: 'dynamodb', action: operation_name) do
42
+ ElasticAPM.with_span(
43
+ operation_name,
44
+ 'db',
45
+ subtype: 'dynamodb',
46
+ action: operation_name
47
+ ) do
41
48
  ElasticAPM::Spies::DynamoDBSpy.without_net_http do
42
49
  original_method = method("#{operation_name}_without_apm")
43
50
  original_method.call(params, options)
@@ -27,7 +27,14 @@ module ElasticAPM
27
27
  SUBTYPE = 'elasticsearch'
28
28
 
29
29
  def self.sanitizer
30
- @sanitizer ||= ElasticAPM::Transport::Filters::HashSanitizer.new
30
+ @sanitizer ||=
31
+ begin
32
+ config = ElasticAPM.agent.config
33
+ ElasticAPM::Transport::Filters::HashSanitizer.new(
34
+ key_patterns: config.custom_key_filters +
35
+ config.sanitize_field_names
36
+ )
37
+ end
31
38
  end
32
39
 
33
40
  def install
@@ -47,7 +54,8 @@ module ElasticAPM
47
54
  if ElasticAPM.agent.config.capture_elasticsearch_queries
48
55
  unless args[1].nil? || args[1].empty?
49
56
  statement << {
50
- body: ElasticAPM::Spies::ElasticsearchSpy.sanitizer.strip_from!(args[1])
57
+ body: ElasticAPM::Spies::ElasticsearchSpy
58
+ .sanitizer.strip_from(args[1])
51
59
  }
52
60
  end
53
61
  end
@@ -28,12 +28,14 @@ module ElasticAPM
28
28
  def self.without_net_http
29
29
  return yield unless defined?(NetHTTPSpy)
30
30
 
31
+ # rubocop:disable Style/ExplicitBlockArgument
31
32
  ElasticAPM::Spies::NetHTTPSpy.disable_in do
32
33
  yield
33
34
  end
35
+ # rubocop:enable Style/ExplicitBlockArgument
34
36
  end
35
37
 
36
- # rubocop:disable Metrics/CyclomaticComplexity
38
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
37
39
  def install
38
40
  ::Faraday::Connection.class_eval do
39
41
  alias run_request_without_apm run_request
@@ -89,13 +91,14 @@ module ElasticAPM
89
91
  http.status_code = result.status.to_s
90
92
  end
91
93
 
94
+ span&.outcome = Span::Outcome.from_http_status(result.status)
92
95
  result
93
96
  end
94
97
  end
95
98
  end
96
99
  end
97
100
  end
98
- # rubocop:enable Metrics/CyclomaticComplexity
101
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
99
102
  end
100
103
 
101
104
  register 'Faraday', 'faraday', FaradaySpy.new
@@ -61,6 +61,7 @@ module ElasticAPM
61
61
  http.status_code = result.status.to_s
62
62
  end
63
63
 
64
+ span&.outcome = Span::Outcome.from_http_status(result.status)
64
65
  result
65
66
  end
66
67
  end
@@ -44,11 +44,15 @@ module ElasticAPM
44
44
  end
45
45
 
46
46
  def failed(event)
47
- pop_event(event)
47
+ span = pop_event(event)
48
+ span&.outcome = Span::Outcome::FAILURE
49
+ span
48
50
  end
49
51
 
50
52
  def succeeded(event)
51
- pop_event(event)
53
+ span = pop_event(event)
54
+ span&.outcome = Span::Outcome::SUCCESS
55
+ span
52
56
  end
53
57
 
54
58
  private
@@ -47,6 +47,7 @@ module ElasticAPM
47
47
  end
48
48
 
49
49
  # rubocop:disable Metrics/CyclomaticComplexity
50
+ # rubocop:disable Metrics/PerceivedComplexity
50
51
  def install
51
52
  Net::HTTP.class_eval do
52
53
  alias request_without_apm request
@@ -96,12 +97,14 @@ module ElasticAPM
96
97
  http.status_code = result.code
97
98
  end
98
99
 
100
+ span&.outcome = Span::Outcome.from_http_status(result.code)
99
101
  result
100
102
  end
101
103
  end
102
104
  end
103
105
  end
104
106
  # rubocop:enable Metrics/CyclomaticComplexity
107
+ # rubocop:enable Metrics/PerceivedComplexity
105
108
  end
106
109
 
107
110
  register 'Net::HTTP', 'net/http', NetHTTPSpy.new
@@ -39,9 +39,11 @@ module ElasticAPM
39
39
  begin
40
40
  result = execute_without_apm(*args)
41
41
 
42
- transaction.result = 'success' if transaction
42
+ transaction&.result = 'success'
43
+ transaction&.outcome = Transaction::Outcome::SUCCESS
43
44
  rescue StandardError => e
44
- transaction.result = 'error' if transaction
45
+ transaction&.result = 'error'
46
+ transaction&.outcome = Transaction::Outcome::FAILURE
45
47
  ElasticAPM.report(e)
46
48
 
47
49
  raise
@@ -36,10 +36,12 @@ module ElasticAPM
36
36
  name = @payload && @payload['class']&.to_s
37
37
  transaction = ElasticAPM.start_transaction(name, TYPE)
38
38
  perform_without_elastic_apm
39
- transaction.done 'success'
39
+ transaction&.done 'success'
40
+ transaction&.outcome = Transaction::Outcome::SUCCESS
40
41
  rescue ::Exception => e
41
42
  ElasticAPM.report(e, handled: false)
42
- transaction.done 'error' if transaction
43
+ transaction&.done 'error'
44
+ transaction&.outcome = Transaction::Outcome::FAILURE
43
45
  raise
44
46
  ensure
45
47
  ElasticAPM.end_transaction
@@ -31,6 +31,7 @@ module ElasticAPM
31
31
  @summarizer ||= Sql.summarizer
32
32
  end
33
33
 
34
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
34
35
  def install
35
36
  require 'sequel/database/logging'
36
37
 
@@ -61,7 +62,12 @@ module ElasticAPM
61
62
  action: ACTION,
62
63
  context: context
63
64
  )
64
- yield.tap do |result|
65
+ log_connection_yield_without_apm(
66
+ sql,
67
+ connection,
68
+ args,
69
+ &block
70
+ ).tap do |result|
65
71
  if name =~ /^(UPDATE|DELETE)/
66
72
  if connection.respond_to?(:changes)
67
73
  span.context.db.rows_affected = connection.changes
@@ -70,11 +76,16 @@ module ElasticAPM
70
76
  end
71
77
  end
72
78
  end
79
+ rescue
80
+ span&.outcome = Span::Outcome::FAILURE
81
+ raise
73
82
  ensure
83
+ span&.outcome ||= Span::Outcome::SUCCESS
74
84
  ElasticAPM.end_span
75
85
  end
76
86
  end
77
87
  end
88
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
78
89
  end
79
90
 
80
91
  register 'Sequel', 'sequel', SequelSpy.new
@@ -37,9 +37,11 @@ module ElasticAPM
37
37
  yield
38
38
 
39
39
  transaction&.done :success
40
+ transaction&.outcome = Transaction::Outcome::SUCCESS
40
41
  rescue ::Exception => e
41
42
  ElasticAPM.report(e, handled: false)
42
43
  transaction&.done :error
44
+ transaction&.outcome = Transaction::Outcome::FAILURE
43
45
  raise
44
46
  ensure
45
47
  ElasticAPM.end_transaction
@@ -35,9 +35,11 @@ module ElasticAPM
35
35
  yield
36
36
 
37
37
  transaction&.done :success
38
+ transaction&.outcome = Transaction::Outcome::SUCCESS
38
39
  rescue ::Exception => e
39
40
  ElasticAPM.report(e, handled: false)
40
41
  transaction&.done :error
42
+ transaction&.outcome = Transaction::Outcome::FAILURE
41
43
  raise
42
44
  ensure
43
45
  ElasticAPM.end_transaction
@@ -57,11 +57,13 @@ module ElasticAPM
57
57
 
58
58
  res = @app.call(deserialized_msg, delivery_info, metadata, handler)
59
59
  transaction&.done(:success)
60
+ transaction&.outcome = Transaction::Outcome::SUCCESS
60
61
 
61
62
  res
62
63
  rescue ::Exception => e
63
64
  ElasticAPM.report(e, handled: false)
64
65
  transaction&.done(:error)
66
+ transaction&.outcome = Transaction::Outcome::FAILURE
65
67
  raise
66
68
  ensure
67
69
  ElasticAPM.end_transaction
@@ -35,12 +35,14 @@ module ElasticAPM
35
35
  transaction = ElasticAPM.start_transaction(name, TYPE)
36
36
  __run_perform_without_elastic_apm(*args)
37
37
  transaction.done 'success'
38
+ transaction&.outcome = Transaction::Outcome::SUCCESS
38
39
  rescue ::Exception => e
39
40
  # Note that SuckerPunch by default doesn't raise the errors from
40
41
  # the user-defined JobClass#perform method as it uses an error
41
42
  # handler, accessed via `SuckerPunch.exception_handler`.
42
43
  ElasticAPM.report(e, handled: false)
43
44
  transaction.done 'error'
45
+ transaction&.outcome = Transaction::Outcome::FAILURE
44
46
  raise
45
47
  ensure
46
48
  ElasticAPM.end_transaction
@@ -36,8 +36,8 @@ module ElasticAPM
36
36
  end
37
37
 
38
38
  def initialize(sql)
39
- @sql = sql
40
- @tokenizer = Tokenizer.new(sql)
39
+ @sql = sql.encode('utf-8', invalid: :replace, undef: :replace)
40
+ @tokenizer = Tokenizer.new(@sql)
41
41
  end
42
42
 
43
43
  def parse
@@ -158,9 +158,11 @@ module ElasticAPM
158
158
  def scan_dotted_identifier
159
159
  table = @tokenizer.text
160
160
 
161
+ # rubocop:disable Style/WhileUntilModifier
161
162
  while scan_token(PERIOD) && scan_token(IDENT)
162
163
  table += ".#{@tokenizer.text}"
163
164
  end
165
+ # rubocop:enable Style/WhileUntilModifier
164
166
 
165
167
  table
166
168
  end
@@ -130,7 +130,7 @@ module ElasticAPM
130
130
  end
131
131
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
132
132
 
133
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
133
+ # rubocop:disable Metrics/CyclomaticComplexity
134
134
  def scan_dollar_sign
135
135
  while (peek = peek_char)
136
136
  case peek
@@ -165,7 +165,7 @@ module ElasticAPM
165
165
 
166
166
  OTHER
167
167
  end
168
- # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
168
+ # rubocop:enable Metrics/CyclomaticComplexity
169
169
 
170
170
  def scan_quoted_indentifier(delimiter)
171
171
  while (char = next_char)
@@ -38,6 +38,7 @@ module ElasticAPM
38
38
  :module,
39
39
  :colno
40
40
  )
41
+
41
42
  def build_context(context_line_count)
42
43
  return unless abs_path && context_line_count > 0
43
44
 
@@ -23,7 +23,7 @@ require 'elastic_apm/util/lru_cache'
23
23
  module ElasticAPM
24
24
  # @api private
25
25
  class StacktraceBuilder
26
- JAVA_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
26
+ JAVA_FORMAT = /^(.+)\.([^.]+)\(([^:]+):(\d+)\)$/.freeze
27
27
  RUBY_FORMAT = /^(.+?):(\d+)(?::in `(.+?)')?$/.freeze
28
28
 
29
29
  RUBY_VERS_REGEX = %r{ruby(/gems)?[-/](\d+\.)+\d}.freeze
@@ -86,14 +86,13 @@ module ElasticAPM
86
86
  [file, number, method, module_name]
87
87
  end
88
88
 
89
- # rubocop:disable Metrics/CyclomaticComplexity
90
89
  def library_frame?(config, abs_path)
91
90
  return false unless abs_path
92
91
 
93
92
  return true if abs_path.start_with?(GEMS_PATH)
94
93
 
95
94
  if abs_path.start_with?(config.__root_path)
96
- return true if abs_path.start_with?(config.__root_path + '/vendor')
95
+ return true if abs_path.start_with?("#{config.__root_path}/vendor")
97
96
  return false
98
97
  end
99
98
 
@@ -102,7 +101,6 @@ module ElasticAPM
102
101
 
103
102
  false
104
103
  end
105
- # rubocop:enable Metrics/CyclomaticComplexity
106
104
 
107
105
  def strip_load_path(path)
108
106
  return nil if path.nil?
@@ -33,7 +33,7 @@ module ElasticAPM
33
33
  **legacy_traceparent_attrs
34
34
  )
35
35
  @traceparent = traceparent || Traceparent.new(**legacy_traceparent_attrs)
36
- @tracestate = tracestate
36
+ @tracestate = tracestate || Tracestate.new
37
37
  end
38
38
 
39
39
  attr_accessor :traceparent, :tracestate
@@ -42,7 +42,6 @@ module ElasticAPM
42
42
  :version, :trace_id, :id, :parent_id, :ensure_parent_id, :recorded?
43
43
 
44
44
  class << self
45
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
46
45
  def parse(legacy_header = nil, env: nil, metadata: nil)
47
46
  unless legacy_header || env || metadata
48
47
  raise ArgumentError, 'TraceContext expects env:, metadata: ' \
@@ -57,7 +56,6 @@ module ElasticAPM
57
56
  trace_context_from_metadata(metadata)
58
57
  end
59
58
  end
60
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
61
59
 
62
60
  private
63
61