elastic-apm 3.7.0 → 3.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/Jenkinsfile +139 -96
  3. data/.ci/packer_cache.sh +12 -10
  4. data/.rspec +0 -1
  5. data/CHANGELOG.asciidoc +80 -0
  6. data/Gemfile +4 -3
  7. data/bin/run-tests +4 -1
  8. data/docker-compose.yml +2 -0
  9. data/docs/configuration.asciidoc +38 -0
  10. data/docs/debugging.asciidoc +14 -0
  11. data/docs/supported-technologies.asciidoc +2 -1
  12. data/lib/elastic_apm/config.rb +36 -13
  13. data/lib/elastic_apm/config/options.rb +2 -1
  14. data/lib/elastic_apm/config/round_float.rb +31 -0
  15. data/lib/elastic_apm/config/wildcard_pattern_list.rb +13 -1
  16. data/lib/elastic_apm/context_builder.rb +1 -1
  17. data/lib/elastic_apm/grpc.rb +2 -2
  18. data/lib/elastic_apm/instrumenter.rb +10 -3
  19. data/lib/elastic_apm/metadata.rb +3 -1
  20. data/lib/elastic_apm/metadata/cloud_info.rb +128 -0
  21. data/lib/elastic_apm/metadata/service_info.rb +5 -2
  22. data/lib/elastic_apm/metadata/system_info.rb +5 -3
  23. data/lib/elastic_apm/metadata/system_info/container_info.rb +28 -4
  24. data/lib/elastic_apm/middleware.rb +8 -2
  25. data/lib/elastic_apm/opentracing.rb +47 -23
  26. data/lib/elastic_apm/span.rb +7 -3
  27. data/lib/elastic_apm/spies.rb +16 -14
  28. data/lib/elastic_apm/spies/delayed_job.rb +4 -2
  29. data/lib/elastic_apm/spies/dynamo_db.rb +58 -0
  30. data/lib/elastic_apm/spies/elasticsearch.rb +26 -2
  31. data/lib/elastic_apm/spies/mongo.rb +1 -1
  32. data/lib/elastic_apm/spies/net_http.rb +6 -2
  33. data/lib/elastic_apm/spies/sequel.rb +1 -1
  34. data/lib/elastic_apm/trace_context.rb +1 -1
  35. data/lib/elastic_apm/trace_context/traceparent.rb +2 -4
  36. data/lib/elastic_apm/trace_context/tracestate.rb +112 -9
  37. data/lib/elastic_apm/transaction.rb +26 -5
  38. data/lib/elastic_apm/transport/connection.rb +1 -0
  39. data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +70 -0
  40. data/lib/elastic_apm/transport/filters/secrets_filter.rb +14 -56
  41. data/lib/elastic_apm/transport/serializers.rb +8 -6
  42. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +56 -23
  43. data/lib/elastic_apm/transport/serializers/span_serializer.rb +2 -1
  44. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +1 -0
  45. data/lib/elastic_apm/transport/user_agent.rb +3 -3
  46. data/lib/elastic_apm/transport/worker.rb +5 -0
  47. data/lib/elastic_apm/util.rb +2 -0
  48. data/lib/elastic_apm/util/precision_validator.rb +46 -0
  49. data/lib/elastic_apm/version.rb +1 -1
  50. metadata +12 -8
  51. data/.ci/downstreamTests.groovy +0 -192
@@ -30,14 +30,17 @@ module ElasticAPM
30
30
 
31
31
  attr_reader :name, :version
32
32
  end
33
+
33
34
  class Agent < Versioned; end
34
35
  class Framework < Versioned; end
35
36
  class Language < Versioned; end
36
37
  class Runtime < Versioned; end
38
+
37
39
  def initialize(config)
38
40
  @config = config
39
41
 
40
42
  @name = @config.service_name
43
+ @node_name = @config.service_node_name
41
44
  @environment = @config.environment
42
45
  @agent = Agent.new(name: 'ruby', version: VERSION)
43
46
  @framework = Framework.new(
@@ -49,8 +52,8 @@ module ElasticAPM
49
52
  @version = @config.service_version || Util.git_sha
50
53
  end
51
54
 
52
- attr_reader :name, :environment, :agent, :framework, :language, :runtime,
53
- :version
55
+ attr_reader :name, :node_name, :environment, :agent, :framework, :language,
56
+ :runtime, :version
54
57
 
55
58
  private
56
59
 
@@ -24,7 +24,7 @@ module ElasticAPM
24
24
  def initialize(config)
25
25
  @config = config
26
26
 
27
- @hostname = @config.hostname || `hostname`.chomp
27
+ @hostname = @config.hostname || self.class.system_hostname
28
28
  @architecture = gem_platform.cpu
29
29
  @platform = gem_platform.os
30
30
 
@@ -35,11 +35,13 @@ module ElasticAPM
35
35
 
36
36
  attr_reader :hostname, :architecture, :platform, :container, :kubernetes
37
37
 
38
- private
39
-
40
38
  def gem_platform
41
39
  @gem_platform ||= Gem::Platform.local
42
40
  end
41
+
42
+ def self.system_hostname
43
+ @system_hostname ||= `hostname`.chomp
44
+ end
43
45
  end
44
46
  end
45
47
  end
@@ -81,8 +81,14 @@ module ElasticAPM
81
81
  ENV.fetch('KUBERNETES_POD_UID', kubernetes_pod_uid)
82
82
  end
83
83
 
84
- CONTAINER_ID_REGEX = /^[0-9A-Fa-f]{64}$/.freeze
85
- KUBEPODS_REGEX = %r{(?:^/kubepods/[^/]+/pod([^/]+)$)|(?:^/kubepods\.slice/kubepods-[^/]+\.slice/kubepods-[^/]+-pod([^/]+)\.slice$)}.freeze # rubocop:disable Metrics/LineLength
84
+ CONTAINER_ID_REGEXES = [
85
+ %r{^[[:xdigit:]]{64}$},
86
+ %r{^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4,}$}
87
+ ]
88
+ KUBEPODS_REGEXES = [
89
+ %r{(?:^/kubepods[^\s]*/pod([^/]+)$)},
90
+ %r{(?:^/kubepods\.slice/kubepods-[^/]+\.slice/kubepods-[^/]+-pod([^/]+)\.slice$)}
91
+ ]
86
92
  SYSTEMD_SCOPE_SUFFIX = '.scope'
87
93
 
88
94
  # rubocop:disable Metrics/PerceivedComplexity
@@ -118,18 +124,36 @@ module ElasticAPM
118
124
  end
119
125
  end
120
126
 
121
- if (kubepods_match = KUBEPODS_REGEX.match(directory))
127
+ if (kubepods_match = match_kubepods(directory))
122
128
  pod_id = kubepods_match[1] || kubepods_match[2]
123
129
 
124
130
  self.container_id = container_id
125
131
  self.kubernetes_pod_uid = pod_id
126
- elsif CONTAINER_ID_REGEX.match(container_id)
132
+ elsif match_container(container_id)
127
133
  self.container_id = container_id
128
134
  end
129
135
  end
130
136
  end
131
137
  # rubocop:enable Metrics/PerceivedComplexity
132
138
  # rubocop:enable Metrics/CyclomaticComplexity
139
+
140
+ def match_kubepods(directory)
141
+ KUBEPODS_REGEXES.each do |r|
142
+ next unless (match = r.match(directory))
143
+ return match
144
+ end
145
+
146
+ nil
147
+ end
148
+
149
+ def match_container(container_id)
150
+ CONTAINER_ID_REGEXES.each do |r|
151
+ next unless (match = r.match(container_id))
152
+ return match
153
+ end
154
+
155
+ nil
156
+ end
133
157
  end
134
158
  end
135
159
  end
@@ -59,9 +59,15 @@ module ElasticAPM
59
59
  end
60
60
 
61
61
  def path_ignored?(env)
62
- config.ignore_url_patterns.any? do |r|
63
- env['PATH_INFO'].match r
62
+ return true if config.ignore_url_patterns.any? do |r|
63
+ r.match(env['PATH_INFO'])
64
64
  end
65
+
66
+ return true if config.transaction_ignore_urls.any? do |r|
67
+ r.match(env['PATH_INFO'])
68
+ end
69
+
70
+ false
65
71
  end
66
72
 
67
73
  def start_transaction(env)
@@ -39,7 +39,7 @@ module ElasticAPM
39
39
  @span_context
40
40
  end
41
41
 
42
- def set_label(key, val)
42
+ def set_tag(key, val)
43
43
  if elastic_span.is_a?(Transaction)
44
44
  case key.to_s
45
45
  when 'type'
@@ -54,6 +54,8 @@ module ElasticAPM
54
54
  else
55
55
  elastic_span.context.labels[key] = val
56
56
  end
57
+
58
+ self
57
59
  end
58
60
 
59
61
  def set_baggage_item(_key, _value)
@@ -78,18 +80,12 @@ module ElasticAPM
78
80
  ElasticAPM.report_message message
79
81
  end
80
82
  end
81
-
82
83
  # rubocop:enable Lint/UnusedMethodArgument
83
- def finish(clock_end: Util.monotonic_micros, end_time: nil)
84
- return unless (agent = ElasticAPM.agent)
85
84
 
86
- if end_time
87
- warn '[ElasticAPM] DEPRECATED: Setting a custom end time as a ' \
88
- '`Time` is deprecated. Use `clock_end:` and monotonic time instead.'
89
- clock_end = end_time
90
- end
85
+ def finish(end_time: Time.now)
86
+ return unless (agent = ElasticAPM.agent)
91
87
 
92
- elastic_span.done clock_end: clock_end
88
+ elastic_span.done clock_end: Util.micros(end_time)
93
89
 
94
90
  case elastic_span
95
91
  when ElasticAPM::Transaction
@@ -114,6 +110,8 @@ module ElasticAPM
114
110
 
115
111
  # @api private
116
112
  class SpanContext
113
+ extend Forwardable
114
+
117
115
  def initialize(trace_context:, baggage: nil)
118
116
  if baggage
119
117
  ElasticAPM.agent.config.logger.warn(
@@ -125,10 +123,27 @@ module ElasticAPM
125
123
  end
126
124
 
127
125
  attr_accessor :trace_context
126
+ def_delegators :trace_context, :trace_id, :id, :parent_id
127
+
128
+ def self.from_header(header)
129
+ return unless header
130
+
131
+ trace_context = ElasticAPM::TraceContext.parse(header)
132
+ return unless trace_context
133
+
134
+ trace_context.traceparent.id = trace_context.parent_id
135
+ trace_context.traceparent.parent_id = nil
136
+
137
+ from_trace_context(trace_context)
138
+ end
128
139
 
129
140
  def self.from_trace_context(trace_context)
130
141
  new(trace_context: trace_context)
131
142
  end
143
+
144
+ def child
145
+ self.class.from_trace_context(trace_context.child)
146
+ end
132
147
  end
133
148
 
134
149
  # @api private
@@ -210,7 +225,7 @@ module ElasticAPM
210
225
  child_of: nil,
211
226
  references: nil,
212
227
  start_time: Time.now,
213
- labels: {},
228
+ tags: {},
214
229
  ignore_active_scope: false,
215
230
  finish_on_close: true,
216
231
  **
@@ -220,14 +235,14 @@ module ElasticAPM
220
235
  child_of: child_of,
221
236
  references: references,
222
237
  start_time: start_time,
223
- labels: labels,
238
+ tags: tags,
224
239
  ignore_active_scope: ignore_active_scope
225
240
  )
226
241
  scope = scope_manager.activate(span, finish_on_close: finish_on_close)
227
242
 
228
243
  if block_given?
229
244
  begin
230
- yield scope
245
+ return yield scope
231
246
  ensure
232
247
  scope.close
233
248
  end
@@ -243,7 +258,7 @@ module ElasticAPM
243
258
  child_of: nil,
244
259
  references: nil,
245
260
  start_time: Time.now,
246
- labels: {},
261
+ tags: {},
247
262
  ignore_active_scope: false,
248
263
  **
249
264
  )
@@ -280,7 +295,7 @@ module ElasticAPM
280
295
  span_context ||=
281
296
  SpanContext.from_trace_context(elastic_span.trace_context)
282
297
 
283
- labels.each do |key, value|
298
+ tags.each do |key, value|
284
299
  elastic_span.context.labels[key] = value
285
300
  end
286
301
 
@@ -293,7 +308,7 @@ module ElasticAPM
293
308
 
294
309
  def inject(span_context, format, carrier)
295
310
  case format
296
- when ::OpenTracing::FORMAT_RACK
311
+ when ::OpenTracing::FORMAT_RACK, ::OpenTracing::FORMAT_TEXT_MAP
297
312
  carrier['elastic-apm-traceparent'] =
298
313
  span_context.traceparent.to_header
299
314
  else
@@ -304,10 +319,16 @@ module ElasticAPM
304
319
  def extract(format, carrier)
305
320
  case format
306
321
  when ::OpenTracing::FORMAT_RACK
307
- ElasticAPM::TraceContext
308
- .parse(carrier['HTTP_ELASTIC_APM_TRACEPARENT'])
322
+ SpanContext.from_header(
323
+ carrier['HTTP_ELASTIC_APM_TRACEPARENT']
324
+ )
325
+ when ::OpenTracing::FORMAT_TEXT_MAP
326
+ SpanContext.from_header(
327
+ carrier['elastic-apm-traceparent']
328
+ )
309
329
  else
310
- warn 'Only extraction from HTTP headers via Rack is available'
330
+ warn 'Only extraction from HTTP headers via Rack or in ' \
331
+ 'text map format are available'
311
332
  nil
312
333
  end
313
334
  rescue ElasticAPM::TraceContext::InvalidTraceparentHeader
@@ -321,9 +342,12 @@ module ElasticAPM
321
342
  references:,
322
343
  ignore_active_scope:
323
344
  )
324
- context_from_child_of(child_of) ||
325
- context_from_references(references) ||
326
- context_from_active_scope(ignore_active_scope)
345
+ context = context_from_child_of(child_of) ||
346
+ context_from_references(references) ||
347
+ context_from_active_scope(ignore_active_scope)
348
+ return context.child if context&.respond_to?(:child)
349
+
350
+ context
327
351
  end
328
352
 
329
353
  def context_from_child_of(child_of)
@@ -344,7 +368,7 @@ module ElasticAPM
344
368
  def context_from_active_scope(ignore_active_scope)
345
369
  if ignore_active_scope
346
370
  ElasticAPM.agent&.config&.logger&.warn(
347
- 'ignore_active_scope might lead to unexpeced results'
371
+ 'ignore_active_scope might lead to unexpected results'
348
372
  )
349
373
  return
350
374
  end
@@ -38,7 +38,8 @@ module ElasticAPM
38
38
  action: nil,
39
39
  context: nil,
40
40
  stacktrace_builder: nil,
41
- sync: nil
41
+ sync: nil,
42
+ sample_rate: nil
42
43
  )
43
44
  @name = name
44
45
 
@@ -53,6 +54,7 @@ module ElasticAPM
53
54
  @transaction = transaction
54
55
  @parent = parent
55
56
  @trace_context = trace_context || parent.trace_context.child
57
+ @sample_rate = transaction.sample_rate
56
58
 
57
59
  @context = context || Span::Context.new(sync: sync)
58
60
  @stacktrace_builder = stacktrace_builder
@@ -73,6 +75,7 @@ module ElasticAPM
73
75
  :context,
74
76
  :duration,
75
77
  :parent,
78
+ :sample_rate,
76
79
  :self_time,
77
80
  :stacktrace,
78
81
  :timestamp,
@@ -97,11 +100,12 @@ module ElasticAPM
97
100
 
98
101
  def done(clock_end: Util.monotonic_micros)
99
102
  stop clock_end
103
+ self
104
+ end
100
105
 
106
+ def prepare_for_serialization!
101
107
  build_stacktrace! if should_build_stacktrace?
102
108
  self.original_backtrace = nil # release original
103
-
104
- self
105
109
  end
106
110
 
107
111
  def stopped?
@@ -80,23 +80,25 @@ module ElasticAPM
80
80
  end
81
81
  end
82
82
 
83
- # @api private
84
- module Kernel
85
- private
83
+ unless ENV['ELASTIC_APM_SKIP_REQUIRE_PATCH'] == '1'
84
+ # @api private
85
+ module Kernel
86
+ private
86
87
 
87
- alias require_without_apm require
88
+ alias require_without_apm require
88
89
 
89
- def require(path)
90
- res = require_without_apm(path)
90
+ def require(path)
91
+ res = require_without_apm(path)
91
92
 
92
- begin
93
- ElasticAPM::Spies.hook_into(path)
94
- rescue ::Exception => e
95
- puts "Failed hooking into '#{path}'. Please report this at " \
96
- 'github.com/elastic/apm-agent-ruby'
97
- puts e.backtrace.join("\n")
98
- end
93
+ begin
94
+ ElasticAPM::Spies.hook_into(path)
95
+ rescue ::Exception => e
96
+ puts "Failed hooking into '#{path}'. Please report this at " \
97
+ 'github.com/elastic/apm-agent-ruby'
98
+ puts e.backtrace.join("\n")
99
+ end
99
100
 
100
- res
101
+ res
102
+ end
101
103
  end
102
104
  end
@@ -41,10 +41,10 @@ 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
45
  rescue ::Exception => e
46
46
  ElasticAPM.report(e, handled: false)
47
- transaction.done 'error'
47
+ transaction&.done 'error'
48
48
  raise
49
49
  ensure
50
50
  ElasticAPM.end_transaction
@@ -53,6 +53,8 @@ module ElasticAPM
53
53
  def self.name_from_payload(payload_object)
54
54
  if payload_object.is_a?(::Delayed::PerformableMethod)
55
55
  performable_method_name(payload_object)
56
+ elsif payload_object.class.name == 'ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper'
57
+ payload_object.job_data['job_class']
56
58
  else
57
59
  payload_object.class.name
58
60
  end
@@ -0,0 +1,58 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ # frozen_string_literal: true
19
+
20
+ module ElasticAPM
21
+ # @api private
22
+ module Spies
23
+ # @api private
24
+ class DynamoDBSpy
25
+ def self.without_net_http
26
+ return yield unless defined?(NetHTTPSpy)
27
+
28
+ ElasticAPM::Spies::NetHTTPSpy.disable_in do
29
+ yield
30
+ end
31
+ end
32
+
33
+ def install
34
+ ::Aws::DynamoDB::Client.class_eval do
35
+ # Alias all available operations
36
+ api.operation_names.each do |operation_name|
37
+ alias :"#{operation_name}_without_apm" :"#{operation_name}"
38
+
39
+ define_method(operation_name) do |params = {}, options = {}|
40
+ ElasticAPM.with_span(operation_name, 'db', subtype: 'dynamodb', action: operation_name) do
41
+ ElasticAPM::Spies::DynamoDBSpy.without_net_http do
42
+ original_method = method("#{operation_name}_without_apm")
43
+ original_method.call(params, options)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ register(
53
+ 'Aws::DynamoDB::Client',
54
+ 'aws-sdk-dynamodb',
55
+ DynamoDBSpy.new
56
+ )
57
+ end
58
+ end