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.
- checksums.yaml +4 -4
- data/.ci/Jenkinsfile +139 -96
- data/.ci/packer_cache.sh +12 -10
- data/.rspec +0 -1
- data/CHANGELOG.asciidoc +16 -0
- data/Gemfile +2 -1
- data/bin/run-tests +4 -1
- data/docker-compose.yml +2 -0
- data/docs/configuration.asciidoc +37 -0
- data/lib/elastic_apm/config.rb +4 -8
- data/lib/elastic_apm/grpc.rb +2 -2
- data/lib/elastic_apm/metadata/service_info.rb +5 -2
- data/lib/elastic_apm/opentracing.rb +47 -23
- data/lib/elastic_apm/spies.rb +16 -14
- data/lib/elastic_apm/spies/elasticsearch.rb +16 -2
- data/lib/elastic_apm/spies/mongo.rb +1 -1
- data/lib/elastic_apm/spies/net_http.rb +6 -2
- data/lib/elastic_apm/spies/sequel.rb +1 -1
- data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +77 -0
- data/lib/elastic_apm/transport/filters/secrets_filter.rb +12 -56
- data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +27 -20
- data/lib/elastic_apm/version.rb +1 -1
- metadata +3 -3
- data/.ci/downstreamTests.groovy +0 -192
data/lib/elastic_apm/config.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
data/lib/elastic_apm/grpc.rb
CHANGED
@@ -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: #{
|
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,
|
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
|
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
|
-
|
87
|
-
|
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:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
308
|
-
|
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
|
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
|
-
|
326
|
-
|
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
|
371
|
+
'ignore_active_scope might lead to unexpected results'
|
348
372
|
)
|
349
373
|
return
|
350
374
|
end
|
data/lib/elastic_apm/spies.rb
CHANGED
@@ -80,23 +80,25 @@ module ElasticAPM
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
unless ENV['ELASTIC_APM_SKIP_REQUIRE_PATCH'] == '1'
|
84
|
+
# @api private
|
85
|
+
module Kernel
|
86
|
+
private
|
86
87
|
|
87
|
-
|
88
|
+
alias require_without_apm require
|
88
89
|
|
89
|
-
|
90
|
-
|
90
|
+
def require(path)
|
91
|
+
res = require_without_apm(path)
|
91
92
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
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 =
|
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,
|
@@ -64,8 +64,12 @@ module ElasticAPM
|
|
64
64
|
method = req.method.to_s.upcase
|
65
65
|
path, query = req.path.split('?')
|
66
66
|
|
67
|
-
|
68
|
-
|
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)
|
@@ -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
|