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