elastic-apm 3.7.0 → 3.8.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.
@@ -42,6 +42,7 @@ module ElasticAPM
42
42
  option :breakdown_metrics, type: :bool, default: true
43
43
  option :capture_body, type: :string, default: 'off'
44
44
  option :capture_headers, type: :bool, default: true
45
+ option :capture_elasticsearch_queries, type: :bool, default: false
45
46
  option :capture_env, type: :bool, default: true
46
47
  option :central_config, type: :bool, default: true
47
48
  option :current_user_email_method, type: :string, default: 'email'
@@ -78,6 +79,7 @@ module ElasticAPM
78
79
  option :sanitize_field_names, type: :list, default: [], converter: WildcardPatternList.new
79
80
  option :server_ca_cert, type: :string
80
81
  option :service_name, type: :string
82
+ option :service_node_name, type: :string
81
83
  option :service_version, type: :string
82
84
  option :source_lines_error_app_frames, type: :int, default: 5
83
85
  option :source_lines_error_library_frames, type: :int, default: 0
@@ -158,13 +160,12 @@ module ElasticAPM
158
160
  end
159
161
 
160
162
  def replace_options(new_options)
161
- return unless new_options
163
+ return if new_options.nil? || new_options.empty?
162
164
  options_copy = @options.dup
163
165
  new_options.each do |key, value|
164
166
  options_copy.fetch(key.to_sym).set(value)
165
167
  end
166
168
  @options = options_copy
167
- set_log_level(logger)
168
169
  end
169
170
 
170
171
  def app=(app)
@@ -286,15 +287,10 @@ module ElasticAPM
286
287
 
287
288
  def build_logger
288
289
  Logger.new(log_path == '-' ? STDOUT : log_path).tap do |logger|
289
- set_log_level(logger)
290
+ logger.level = log_level
290
291
  end
291
292
  end
292
293
 
293
- def set_log_level(logger)
294
- return unless logger
295
- logger.level = log_level
296
- end
297
-
298
294
  def app_type?(app)
299
295
  if defined?(::Rails::Application) && app.is_a?(::Rails::Application)
300
296
  return :rails
@@ -71,7 +71,7 @@ module ElasticAPM
71
71
  transaction.done 'success'
72
72
  rescue ::Exception => e
73
73
  ElasticAPM.report(e, handled: false)
74
- transaction.done 'error'
74
+ transaction.done 'error' if transaction
75
75
  raise
76
76
  ensure
77
77
  ElasticAPM.end_transaction
@@ -91,7 +91,7 @@ module ElasticAPM
91
91
  def trace_context(call)
92
92
  TraceContext.parse(metadata: call.metadata)
93
93
  rescue TraceContext::InvalidTraceparentHeader
94
- warn "Couldn't parse invalid trace context header: #{header.inspect}"
94
+ warn "Couldn't parse invalid trace context header: #{call.metadata}"
95
95
  nil
96
96
  end
97
97
  end
@@ -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
 
@@ -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
@@ -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
@@ -26,16 +26,30 @@ module ElasticAPM
26
26
  TYPE = 'db'
27
27
  SUBTYPE = 'elasticsearch'
28
28
 
29
+ def self.sanitizer
30
+ @sanitizer ||= ElasticAPM::Transport::Filters::HashSanitizer.new
31
+ end
32
+
29
33
  def install
30
34
  ::Elasticsearch::Transport::Client.class_eval do
31
35
  alias perform_request_without_apm perform_request
32
36
 
33
37
  def perform_request(method, path, *args, &block)
34
38
  name = format(NAME_FORMAT, method, path)
35
- statement = args[0].is_a?(String) ? args[0] : args[0].to_json
39
+ statement = []
40
+
41
+ statement << { params: args&.[](0) }
42
+
43
+ if ElasticAPM.agent.config.capture_elasticsearch_queries
44
+ unless args[1].nil? || args[1].empty?
45
+ statement << {
46
+ body: ElasticAPM::Spies::ElasticsearchSpy.sanitizer.strip_from!(args[1])
47
+ }
48
+ end
49
+ end
36
50
 
37
51
  context = Span::Context.new(
38
- db: { statement: statement },
52
+ db: { statement: statement.reduce({}, :merge).to_json },
39
53
  destination: {
40
54
  name: SUBTYPE,
41
55
  resource: SUBTYPE,
@@ -85,8 +85,8 @@ module ElasticAPM
85
85
  end
86
86
 
87
87
  def pop_event(event)
88
- return unless (curr = ElasticAPM.current_span)
89
88
  span = @events.delete(event.operation_id)
89
+ return unless (curr = ElasticAPM.current_span)
90
90
 
91
91
  curr == span && ElasticAPM.end_span
92
92
  end
@@ -64,8 +64,12 @@ module ElasticAPM
64
64
  method = req.method.to_s.upcase
65
65
  path, query = req.path.split('?')
66
66
 
67
- cls = use_ssl? ? URI::HTTPS : URI::HTTP
68
- uri = cls.build([nil, host, port, path, query, nil])
67
+ url = use_ssl? ? +'https://' : +'http://'
68
+ url << host
69
+ url << ":#{port}" if port
70
+ url << path
71
+ url << "?#{query}" if query
72
+ uri = URI(url)
69
73
 
70
74
  destination =
71
75
  ElasticAPM::Span::Context::Destination.from_uri(uri)
@@ -28,7 +28,7 @@ module ElasticAPM
28
28
  ACTION = 'query'
29
29
 
30
30
  def self.summarizer
31
- @summarizer = Sql.summarizer
31
+ @summarizer ||= Sql.summarizer
32
32
  end
33
33
 
34
34
  def install
@@ -0,0 +1,77 @@
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
+ module Transport
22
+ module Filters
23
+ class HashSanitizer
24
+ FILTERED = '[FILTERED]'
25
+
26
+ KEY_FILTERS = [
27
+ /passw(or)?d/i,
28
+ /auth/i,
29
+ /^pw$/,
30
+ /secret/i,
31
+ /token/i,
32
+ /api[-._]?key/i,
33
+ /session[-._]?id/i,
34
+ /(set[-_])?cookie/i
35
+ ].freeze
36
+
37
+ VALUE_FILTERS = [
38
+ # (probably) credit card number
39
+ /^\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}$/
40
+ ].freeze
41
+
42
+ attr_accessor :key_filters
43
+
44
+ def initialize
45
+ @key_filters = KEY_FILTERS
46
+ end
47
+
48
+ def strip_from!(obj, key_filters = KEY_FILTERS)
49
+ return unless obj&.is_a?(Hash)
50
+
51
+ obj.each do |k, v|
52
+ if filter_key?(k)
53
+ next obj[k] = FILTERED
54
+ end
55
+
56
+ case v
57
+ when Hash
58
+ strip_from!(v)
59
+ when String
60
+ if filter_value?(v)
61
+ obj[k] = FILTERED
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def filter_key?(key)
68
+ @key_filters.any? { |regex| regex.match(key) }
69
+ end
70
+
71
+ def filter_value?(value)
72
+ VALUE_FILTERS.any? { |regex| regex.match(value) }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end