elastic-apm 3.10.0 → 3.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.asciidoc +55 -0
  3. data/Gemfile +0 -3
  4. data/docs/api.asciidoc +2 -1
  5. data/docs/configuration.asciidoc +1 -0
  6. data/lib/elastic_apm.rb +14 -2
  7. data/lib/elastic_apm/config.rb +24 -6
  8. data/lib/elastic_apm/config/options.rb +2 -1
  9. data/lib/elastic_apm/config/round_float.rb +31 -0
  10. data/lib/elastic_apm/config/wildcard_pattern_list.rb +2 -0
  11. data/lib/elastic_apm/instrumenter.rb +10 -3
  12. data/lib/elastic_apm/metadata.rb +3 -1
  13. data/lib/elastic_apm/metadata/cloud_info.rb +128 -0
  14. data/lib/elastic_apm/middleware.rb +8 -3
  15. data/lib/elastic_apm/span.rb +16 -1
  16. data/lib/elastic_apm/spies/delayed_job.rb +6 -2
  17. data/lib/elastic_apm/spies/elasticsearch.rb +8 -2
  18. data/lib/elastic_apm/spies/faraday.rb +1 -0
  19. data/lib/elastic_apm/spies/http.rb +1 -0
  20. data/lib/elastic_apm/spies/mongo.rb +6 -2
  21. data/lib/elastic_apm/spies/net_http.rb +1 -0
  22. data/lib/elastic_apm/spies/rake.rb +4 -2
  23. data/lib/elastic_apm/spies/resque.rb +4 -2
  24. data/lib/elastic_apm/spies/sequel.rb +10 -1
  25. data/lib/elastic_apm/spies/shoryuken.rb +2 -0
  26. data/lib/elastic_apm/spies/sidekiq.rb +2 -0
  27. data/lib/elastic_apm/spies/sneakers.rb +2 -0
  28. data/lib/elastic_apm/spies/sucker_punch.rb +2 -0
  29. data/lib/elastic_apm/trace_context.rb +1 -1
  30. data/lib/elastic_apm/trace_context/traceparent.rb +2 -4
  31. data/lib/elastic_apm/trace_context/tracestate.rb +112 -9
  32. data/lib/elastic_apm/transaction.rb +39 -6
  33. data/lib/elastic_apm/transport/connection.rb +1 -0
  34. data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +14 -15
  35. data/lib/elastic_apm/transport/filters/secrets_filter.rb +10 -6
  36. data/lib/elastic_apm/transport/serializers.rb +8 -6
  37. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +43 -3
  38. data/lib/elastic_apm/transport/serializers/span_serializer.rb +3 -1
  39. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +2 -0
  40. data/lib/elastic_apm/transport/user_agent.rb +3 -3
  41. data/lib/elastic_apm/transport/worker.rb +1 -0
  42. data/lib/elastic_apm/util.rb +2 -0
  43. data/lib/elastic_apm/util/deep_dup.rb +66 -0
  44. data/lib/elastic_apm/util/precision_validator.rb +46 -0
  45. data/lib/elastic_apm/version.rb +1 -1
  46. metadata +7 -3
@@ -41,9 +41,14 @@ module ElasticAPM
41
41
  ElasticAPM.report(e, context: context, handled: false)
42
42
  raise
43
43
  ensure
44
- if resp && transaction
45
- status, headers, _body = resp
46
- transaction.add_response(status, headers: headers.dup)
44
+ if transaction
45
+ if resp
46
+ status, headers, _body = resp
47
+ transaction.add_response(status, headers: headers.dup)
48
+ transaction&.outcome = Transaction::Outcome.from_http_status(status)
49
+ else
50
+ transaction&.outcome = Transaction::Outcome::FAILURE
51
+ end
47
52
  end
48
53
 
49
54
  ElasticAPM.end_transaction http_result(status)
@@ -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
@@ -38,7 +49,8 @@ module ElasticAPM
38
49
  action: nil,
39
50
  context: nil,
40
51
  stacktrace_builder: nil,
41
- sync: nil
52
+ sync: nil,
53
+ sample_rate: nil
42
54
  )
43
55
  @name = name
44
56
 
@@ -53,6 +65,7 @@ module ElasticAPM
53
65
  @transaction = transaction
54
66
  @parent = parent
55
67
  @trace_context = trace_context || parent.trace_context.child
68
+ @sample_rate = transaction.sample_rate
56
69
 
57
70
  @context = context || Span::Context.new(sync: sync)
58
71
  @stacktrace_builder = stacktrace_builder
@@ -65,6 +78,7 @@ module ElasticAPM
65
78
  :action,
66
79
  :name,
67
80
  :original_backtrace,
81
+ :outcome,
68
82
  :subtype,
69
83
  :trace_context,
70
84
  :type
@@ -73,6 +87,7 @@ module ElasticAPM
73
87
  :context,
74
88
  :duration,
75
89
  :parent,
90
+ :sample_rate,
76
91
  :self_time,
77
92
  :stacktrace,
78
93
  :timestamp,
@@ -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,8 @@ 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.class.name == 'ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper'
59
+ payload_object.job_data['job_class']
56
60
  else
57
61
  payload_object.class.name
58
62
  end
@@ -27,7 +27,13 @@ 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 + config.sanitize_field_names
35
+ )
36
+ end
31
37
  end
32
38
 
33
39
  def install
@@ -47,7 +53,7 @@ module ElasticAPM
47
53
  if ElasticAPM.agent.config.capture_elasticsearch_queries
48
54
  unless args[1].nil? || args[1].empty?
49
55
  statement << {
50
- body: ElasticAPM::Spies::ElasticsearchSpy.sanitizer.strip_from!(args[1])
56
+ body: ElasticAPM::Spies::ElasticsearchSpy.sanitizer.strip_from(args[1])
51
57
  }
52
58
  end
53
59
  end
@@ -89,6 +89,7 @@ module ElasticAPM
89
89
  http.status_code = result.status.to_s
90
90
  end
91
91
 
92
+ span&.outcome = Span::Outcome.from_http_status(result.status)
92
93
  result
93
94
  end
94
95
  end
@@ -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
@@ -96,6 +96,7 @@ module ElasticAPM
96
96
  http.status_code = result.code
97
97
  end
98
98
 
99
+ span&.outcome = Span::Outcome.from_http_status(result.code)
99
100
  result
100
101
  end
101
102
  end
@@ -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
@@ -61,7 +61,12 @@ module ElasticAPM
61
61
  action: ACTION,
62
62
  context: context
63
63
  )
64
- yield.tap do |result|
64
+ log_connection_yield_without_apm(
65
+ sql,
66
+ connection,
67
+ args,
68
+ &block
69
+ ).tap do |result|
65
70
  if name =~ /^(UPDATE|DELETE)/
66
71
  if connection.respond_to?(:changes)
67
72
  span.context.db.rows_affected = connection.changes
@@ -70,7 +75,11 @@ module ElasticAPM
70
75
  end
71
76
  end
72
77
  end
78
+ rescue
79
+ span&.outcome = Span::Outcome::FAILURE
80
+ raise
73
81
  ensure
82
+ span&.outcome ||= Span::Outcome::SUCCESS
74
83
  ElasticAPM.end_span
75
84
  end
76
85
  end
@@ -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
@@ -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
@@ -33,8 +33,7 @@ module ElasticAPM
33
33
  trace_id: nil,
34
34
  span_id: nil,
35
35
  id: nil,
36
- recorded: true,
37
- tracestate: nil
36
+ recorded: true
38
37
  )
39
38
  @version = version
40
39
  @trace_id = trace_id || hex(TRACE_ID_LENGTH)
@@ -42,11 +41,10 @@ module ElasticAPM
42
41
  @parent_id = span_id
43
42
  @id = id || hex(ID_LENGTH)
44
43
  @recorded = recorded
45
- @tracestate = tracestate
46
44
  end
47
45
  # rubocop:enable Metrics/ParameterLists
48
46
 
49
- attr_accessor :version, :id, :trace_id, :parent_id, :recorded, :tracestate
47
+ attr_accessor :version, :id, :trace_id, :parent_id, :recorded
50
48
 
51
49
  alias :recorded? :recorded
52
50
 
@@ -17,26 +17,129 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
+ require 'elastic_apm/util/precision_validator'
21
+
20
22
  module ElasticAPM
21
23
  class TraceContext
22
24
  # @api private
23
25
  class Tracestate
24
- def initialize(values = [])
25
- @values = values
26
+ # @api private
27
+ class Entry
28
+ def initialize(key, value)
29
+ @key = key
30
+ @value = value
31
+ end
32
+
33
+ attr_reader :key, :value
34
+
35
+ def to_s
36
+ "#{key}=#{value}"
37
+ end
26
38
  end
27
39
 
28
- attr_accessor :values
40
+ class EsEntry
41
+ ASSIGN = ':'
42
+ SPLIT = ';'
43
+
44
+ SHORT_TO_LONG = { 's' => 'sample_rate' }
45
+ LONG_TO_SHORT = { 'sample_rate' => 's' }
46
+
47
+ def initialize(values = nil)
48
+ parse(values)
49
+ end
50
+
51
+ attr_reader :sample_rate
52
+
53
+ def key
54
+ 'es'
55
+ end
56
+
57
+ def value
58
+ LONG_TO_SHORT.map do |l, s|
59
+ "#{s}#{ASSIGN}#{send(l)}"
60
+ end.join(SPLIT)
61
+ end
62
+
63
+ def empty?
64
+ !sample_rate
65
+ end
66
+
67
+ def sample_rate=(val)
68
+ @sample_rate = Util::PrecisionValidator.validate(
69
+ val, precision: 4, minimum: 0.0001
70
+ )
71
+ end
72
+
73
+ def to_s
74
+ return nil if empty?
75
+
76
+ "es=#{value}"
77
+ end
78
+
79
+ private
80
+
81
+ def parse(values)
82
+ return unless values
83
+
84
+ values.split(SPLIT).map do |kv|
85
+ k, v = kv.split(ASSIGN)
86
+ next unless SHORT_TO_LONG.keys.include?(k)
87
+ send("#{SHORT_TO_LONG[k]}=", v)
88
+ end
89
+ end
90
+ end
91
+
92
+ extend Forwardable
93
+
94
+ def initialize(entries: {}, sample_rate: nil)
95
+ @entries = entries
96
+
97
+ self.sample_rate = sample_rate if sample_rate
98
+ end
99
+
100
+ attr_accessor :entries
101
+
102
+ def_delegators :es_entry, :sample_rate, :sample_rate=
29
103
 
30
104
  def self.parse(header)
31
- # HTTP allows multiple headers with the same name, eg. multiple
32
- # Set-Cookie headers per response.
33
- # Rack handles this by joining the headers under the same key, separated
34
- # by newlines, see https://www.rubydoc.info/github/rack/rack/file/SPEC
35
- new(String(header).split("\n"))
105
+ entries =
106
+ split_by_nl_and_comma(header)
107
+ .each_with_object({}) do |entry, hsh|
108
+ k, v = entry.split('=')
109
+
110
+ hsh[k] =
111
+ case k
112
+ when 'es' then EsEntry.new(v)
113
+ else Entry.new(k, v)
114
+ end
115
+ end
116
+
117
+ new(entries: entries)
36
118
  end
37
119
 
38
120
  def to_header
39
- values.join(',')
121
+ return "" unless entries.any?
122
+
123
+ entries.values.map(&:to_s).join(',')
124
+ end
125
+
126
+ private
127
+
128
+ def es_entry
129
+ # lazy generate this so we only add it if necessary
130
+ entries['es'] ||= EsEntry.new
131
+ end
132
+
133
+ class << self
134
+ private
135
+
136
+ def split_by_nl_and_comma(str)
137
+ # HTTP allows multiple headers with the same name, eg. multiple
138
+ # Set-Cookie headers per response.
139
+ # Rack handles this by joining the headers under the same key, separated
140
+ # by newlines, see https://www.rubydoc.info/github/rack/rack/file/SPEC
141
+ String(str).split("\n").map { |s| s.split(',') }.flatten
142
+ end
40
143
  end
41
144
  end
42
145
  end