elastic-apm 3.7.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
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