skylight 5.0.1 → 5.1.1
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/CHANGELOG.md +395 -364
- data/CLA.md +1 -1
- data/LICENSE.md +7 -17
- data/README.md +1 -1
- data/ext/extconf.rb +42 -54
- data/ext/libskylight.yml +9 -6
- data/lib/skylight.rb +20 -30
- data/lib/skylight/api.rb +22 -18
- data/lib/skylight/cli.rb +47 -46
- data/lib/skylight/cli/doctor.rb +50 -50
- data/lib/skylight/cli/helpers.rb +19 -19
- data/lib/skylight/cli/merger.rb +141 -139
- data/lib/skylight/config.rb +265 -300
- data/lib/skylight/deprecation.rb +4 -4
- data/lib/skylight/errors.rb +3 -4
- data/lib/skylight/extensions.rb +17 -29
- data/lib/skylight/extensions/source_location.rb +128 -128
- data/lib/skylight/formatters/http.rb +1 -3
- data/lib/skylight/gc.rb +30 -40
- data/lib/skylight/helpers.rb +43 -41
- data/lib/skylight/instrumenter.rb +25 -18
- data/lib/skylight/middleware.rb +31 -35
- data/lib/skylight/native.rb +8 -10
- data/lib/skylight/native_ext_fetcher.rb +10 -12
- data/lib/skylight/normalizers.rb +43 -39
- data/lib/skylight/normalizers/action_controller/process_action.rb +24 -25
- data/lib/skylight/normalizers/action_controller/send_file.rb +7 -6
- data/lib/skylight/normalizers/action_dispatch/route_set.rb +7 -7
- data/lib/skylight/normalizers/active_job/perform.rb +48 -44
- data/lib/skylight/normalizers/active_model_serializers/render.rb +7 -3
- data/lib/skylight/normalizers/active_storage.rb +11 -13
- data/lib/skylight/normalizers/active_support/cache.rb +1 -12
- data/lib/skylight/normalizers/coach/handler_finish.rb +1 -3
- data/lib/skylight/normalizers/default.rb +1 -9
- data/lib/skylight/normalizers/faraday/request.rb +1 -3
- data/lib/skylight/normalizers/grape/endpoint.rb +13 -19
- data/lib/skylight/normalizers/grape/endpoint_run.rb +16 -18
- data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +1 -3
- data/lib/skylight/normalizers/graphql/base.rb +23 -28
- data/lib/skylight/normalizers/render.rb +19 -21
- data/lib/skylight/normalizers/shrine.rb +15 -17
- data/lib/skylight/normalizers/sql.rb +4 -4
- data/lib/skylight/probes.rb +38 -46
- data/lib/skylight/probes/action_controller.rb +32 -28
- data/lib/skylight/probes/action_dispatch/request_id.rb +9 -5
- data/lib/skylight/probes/action_dispatch/routing/route_set.rb +7 -5
- data/lib/skylight/probes/action_view.rb +9 -10
- data/lib/skylight/probes/active_job_enqueue.rb +3 -9
- data/lib/skylight/probes/active_model_serializers.rb +8 -8
- data/lib/skylight/probes/delayed_job.rb +37 -42
- data/lib/skylight/probes/elasticsearch.rb +3 -5
- data/lib/skylight/probes/excon.rb +1 -1
- data/lib/skylight/probes/excon/middleware.rb +22 -23
- data/lib/skylight/probes/graphql.rb +2 -7
- data/lib/skylight/probes/middleware.rb +14 -5
- data/lib/skylight/probes/mongo.rb +83 -91
- data/lib/skylight/probes/net_http.rb +1 -1
- data/lib/skylight/probes/redis.rb +5 -17
- data/lib/skylight/probes/sequel.rb +7 -11
- data/lib/skylight/probes/sinatra.rb +8 -5
- data/lib/skylight/probes/tilt.rb +2 -4
- data/lib/skylight/railtie.rb +121 -135
- data/lib/skylight/sidekiq.rb +4 -5
- data/lib/skylight/subscriber.rb +31 -33
- data/lib/skylight/test.rb +89 -84
- data/lib/skylight/trace.rb +121 -115
- data/lib/skylight/user_config.rb +14 -17
- data/lib/skylight/util/clock.rb +1 -0
- data/lib/skylight/util/component.rb +18 -21
- data/lib/skylight/util/deploy.rb +11 -13
- data/lib/skylight/util/http.rb +104 -105
- data/lib/skylight/util/logging.rb +4 -6
- data/lib/skylight/util/lru_cache.rb +2 -6
- data/lib/skylight/util/platform.rb +2 -6
- data/lib/skylight/util/ssl.rb +1 -25
- data/lib/skylight/version.rb +1 -1
- data/lib/skylight/vm/gc.rb +1 -9
- metadata +6 -6
data/lib/skylight/deprecation.rb
CHANGED
@@ -3,14 +3,14 @@
|
|
3
3
|
require "active_support/deprecation"
|
4
4
|
|
5
5
|
module Skylight
|
6
|
-
SKYLIGHT_GEM_ROOT = "#{File.expand_path(
|
6
|
+
SKYLIGHT_GEM_ROOT = "#{File.expand_path("../..", __dir__)}/"
|
7
7
|
|
8
8
|
class Deprecation < ActiveSupport::Deprecation
|
9
9
|
private
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def ignored_callstack(path)
|
12
|
+
path.start_with?(SKYLIGHT_GEM_ROOT)
|
13
|
+
end
|
14
14
|
end
|
15
15
|
|
16
16
|
DEPRECATOR = Deprecation.new("6.0", "skylight")
|
data/lib/skylight/errors.rb
CHANGED
@@ -2,15 +2,14 @@ require "json"
|
|
2
2
|
|
3
3
|
module Skylight
|
4
4
|
# @api private
|
5
|
-
class ConfigError < RuntimeError
|
5
|
+
class ConfigError < RuntimeError
|
6
|
+
end
|
6
7
|
|
7
8
|
class NativeError < StandardError
|
8
9
|
@classes = {}
|
9
10
|
|
10
11
|
def self.register(code, name, message)
|
11
|
-
if @classes.key?(code)
|
12
|
-
raise "Duplicate error class code: #{code}; name=#{name}"
|
13
|
-
end
|
12
|
+
raise "Duplicate error class code: #{code}; name=#{name}" if @classes.key?(code)
|
14
13
|
|
15
14
|
Skylight.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
16
15
|
class #{name}Error < NativeError # class SqlLexError < NativeError
|
data/lib/skylight/extensions.rb
CHANGED
@@ -33,29 +33,21 @@ module Skylight
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def process_trace_meta(meta)
|
36
|
-
extensions.each
|
37
|
-
ext.process_trace_meta(meta)
|
38
|
-
end
|
36
|
+
extensions.each { |ext| ext.process_trace_meta(meta) }
|
39
37
|
end
|
40
38
|
|
41
39
|
# meta is a mutable hash that will be passed to the instrumenter.
|
42
40
|
# This method bridges Skylight.instrument and instrumenter.instrument.
|
43
41
|
def process_instrument_options(opts, meta)
|
44
|
-
extensions.each
|
45
|
-
ext.process_instrument_options(opts, meta)
|
46
|
-
end
|
42
|
+
extensions.each { |ext| ext.process_instrument_options(opts, meta) }
|
47
43
|
end
|
48
44
|
|
49
45
|
def process_normalizer_meta(payload, meta, **opts)
|
50
|
-
extensions.each
|
51
|
-
ext.process_normalizer_meta(payload, meta, **opts)
|
52
|
-
end
|
46
|
+
extensions.each { |ext| ext.process_normalizer_meta(payload, meta, **opts) }
|
53
47
|
end
|
54
48
|
|
55
49
|
def trace_preprocess_meta(meta)
|
56
|
-
extensions.each
|
57
|
-
ext.trace_preprocess_meta(meta)
|
58
|
-
end
|
50
|
+
extensions.each { |ext| ext.trace_preprocess_meta(meta) }
|
59
51
|
end
|
60
52
|
|
61
53
|
def allowed_meta_keys
|
@@ -64,24 +56,20 @@ module Skylight
|
|
64
56
|
|
65
57
|
private
|
66
58
|
|
67
|
-
|
68
|
-
|
69
|
-
def find_by_name(ext_name)
|
70
|
-
begin
|
71
|
-
Skylight::Extensions.const_get(
|
72
|
-
ActiveSupport::Inflector.classify(ext_name)
|
73
|
-
)
|
74
|
-
rescue NameError
|
75
|
-
return nil
|
76
|
-
end.tap do |const|
|
77
|
-
yield const if block_given?
|
78
|
-
end
|
79
|
-
end
|
59
|
+
attr_reader :extensions, :config
|
80
60
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
61
|
+
def find_by_name(ext_name)
|
62
|
+
begin
|
63
|
+
Skylight::Extensions.const_get(ActiveSupport::Inflector.classify(ext_name))
|
64
|
+
rescue NameError
|
65
|
+
return nil
|
66
|
+
end.tap { |const| yield const if block_given? }
|
67
|
+
end
|
68
|
+
|
69
|
+
def rememoize!
|
70
|
+
@allowed_meta_keys = nil
|
71
|
+
allowed_meta_keys
|
72
|
+
end
|
85
73
|
end
|
86
74
|
|
87
75
|
class Extension
|
@@ -35,8 +35,8 @@ module Skylight
|
|
35
35
|
source_location = opts[:source_location] || opts[:meta]&.[](:source_location)
|
36
36
|
source_file = opts[:source_file] || opts[:meta]&.[](:source_file)
|
37
37
|
source_line = opts[:source_line] || opts[:meta]&.[](:source_line)
|
38
|
-
source_name_hint, const_name, method_name =
|
39
|
-
|
38
|
+
source_name_hint, const_name, method_name =
|
39
|
+
opts[:source_location_hint] || opts[:meta]&.[](:source_location_hint)
|
40
40
|
instrument_location = opts[:sk_instrument_location]
|
41
41
|
|
42
42
|
if source_location
|
@@ -65,16 +65,12 @@ module Skylight
|
|
65
65
|
def process_normalizer_meta(payload, meta, **opts)
|
66
66
|
if opts[:source_location] && (opts[:source_file] || opts[:source_line])
|
67
67
|
warn "Found both source_location and source_file or source_line in normalizer\n" \
|
68
|
-
|
68
|
+
" location=#{opts[:source_location]}; file=#{opts[:source_file]}; line=#{opts[:source_line]}"
|
69
69
|
end
|
70
70
|
|
71
71
|
sl =
|
72
72
|
if (source_name, constant_name, method_name = opts[:source_location_hint])
|
73
|
-
dispatch_hinted_source_location(
|
74
|
-
source_name,
|
75
|
-
constant_name,
|
76
|
-
method_name
|
77
|
-
)
|
73
|
+
dispatch_hinted_source_location(source_name, constant_name, method_name)
|
78
74
|
elsif opts[:source_file]
|
79
75
|
[opts[:source_file], opts[:source_line]]
|
80
76
|
end
|
@@ -82,7 +78,7 @@ module Skylight
|
|
82
78
|
sl ||= source_location(payload, meta, cache_key: opts[:cache_key])
|
83
79
|
|
84
80
|
if sl
|
85
|
-
|
81
|
+
trace("normalizer source_location=#{sl}")
|
86
82
|
meta[:source_file], meta[:source_line] = sl
|
87
83
|
end
|
88
84
|
|
@@ -96,7 +92,7 @@ module Skylight
|
|
96
92
|
if meta[:source_location]
|
97
93
|
if source_file || source_line
|
98
94
|
warn "Found both source_location and source_file or source_line, using source_location\n" \
|
99
|
-
|
95
|
+
" location=#{meta[:source_location]}; file=#{source_file}; line=#{source_line}"
|
100
96
|
end
|
101
97
|
|
102
98
|
unless meta[:source_location].is_a?(String)
|
@@ -109,9 +105,7 @@ module Skylight
|
|
109
105
|
warn "Ignoring source_line without source_file; source_line=#{source_line}"
|
110
106
|
end
|
111
107
|
|
112
|
-
if meta[:source_location]
|
113
|
-
debug("source_location=#{meta[:source_location]}")
|
114
|
-
end
|
108
|
+
trace("source_location=#{meta[:source_location]}") if meta[:source_location]
|
115
109
|
end
|
116
110
|
|
117
111
|
def allowed_meta_keys
|
@@ -120,97 +114,101 @@ module Skylight
|
|
120
114
|
|
121
115
|
protected
|
122
116
|
|
123
|
-
|
124
|
-
|
117
|
+
def dispatch_hinted_source_location(source_name, const_name, method_name)
|
118
|
+
return unless const_name && method_name
|
125
119
|
|
126
|
-
|
127
|
-
|
120
|
+
instance_method_source_location(const_name, method_name, source_name: source_name)
|
121
|
+
end
|
128
122
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
end
|
123
|
+
# from normalizers.rb
|
124
|
+
# Returns an array of file and line
|
125
|
+
def source_location(payload, meta, cache_key: nil)
|
126
|
+
# FIXME: what should precedence be?
|
127
|
+
if meta.is_a?(Hash) && meta[:source_location]
|
128
|
+
meta.delete(:source_location)
|
129
|
+
elsif payload.is_a?(Hash) && payload[:sk_source_location]
|
130
|
+
payload[:sk_source_location]
|
131
|
+
elsif (location = find_caller(cache_key: cache_key))
|
132
|
+
[location.absolute_path, location.lineno]
|
140
133
|
end
|
134
|
+
end
|
141
135
|
|
142
|
-
|
143
|
-
|
144
|
-
|
136
|
+
def find_caller(cache_key: nil)
|
137
|
+
# starting at 4 to skip Skylight extension processing logic
|
138
|
+
locations = ::Kernel.caller_locations(4..MAX_CALLER_DEPTH)
|
145
139
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
end
|
140
|
+
if cache_key
|
141
|
+
localized_cache_key = [cache_key, locations.map(&:lineno)].hash
|
142
|
+
@caller_cache.fetch(localized_cache_key) { find_caller_inner(locations) }
|
143
|
+
else
|
144
|
+
find_caller_inner(locations)
|
152
145
|
end
|
146
|
+
end
|
153
147
|
|
154
|
-
|
155
|
-
|
148
|
+
def project_path?(path)
|
149
|
+
return false unless path
|
156
150
|
|
157
|
-
|
158
|
-
|
159
|
-
# Must not be Bundler's vendor location
|
160
|
-
return false if defined?(Bundler) && path.start_with?(Bundler.bundle_path.to_s)
|
161
|
-
# Must not be Ruby files
|
162
|
-
return false if path.include?("/ruby-#{RUBY_VERSION}/lib/ruby/")
|
151
|
+
# Must be in the project root
|
152
|
+
return false unless path.start_with?(config.root.to_s)
|
163
153
|
|
164
|
-
|
165
|
-
|
166
|
-
end
|
154
|
+
# Must not be Bundler's vendor location
|
155
|
+
return false if defined?(Bundler) && path.start_with?(Bundler.bundle_path.to_s)
|
167
156
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
when :instance_method_super
|
181
|
-
find_instance_method_super(constant, method_name)
|
182
|
-
when :class_method
|
183
|
-
find_class_method(constant, method_name)
|
184
|
-
end
|
185
|
-
|
186
|
-
unbound_method&.source_location
|
187
|
-
rescue NameError
|
188
|
-
nil
|
189
|
-
end
|
157
|
+
# Must not be Ruby files
|
158
|
+
return false if path.include?("/ruby-#{RUBY_VERSION}/lib/ruby/")
|
159
|
+
|
160
|
+
# So it must be a project file
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
164
|
+
def instance_method_source_location(constant_name, method_name, source_name: :instance_method)
|
165
|
+
@instance_method_source_location_cache.fetch([constant_name, method_name, source_name]) do
|
166
|
+
if (constant = ::ActiveSupport::Dependencies.safe_constantize(constant_name))
|
167
|
+
if constant.instance_methods.include?(:"before_instrument_#{method_name}")
|
168
|
+
method_name = :"before_instrument_#{method_name}"
|
190
169
|
end
|
191
|
-
|
192
|
-
|
170
|
+
begin
|
171
|
+
unbound_method =
|
172
|
+
case source_name
|
173
|
+
when :instance_method
|
174
|
+
find_instance_method(constant, method_name)
|
175
|
+
when :own_instance_method
|
176
|
+
find_own_instance_method(constant, method_name)
|
177
|
+
when :instance_method_super
|
178
|
+
find_instance_method_super(constant, method_name)
|
179
|
+
when :class_method
|
180
|
+
find_class_method(constant, method_name)
|
181
|
+
end
|
193
182
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
path = gem_name
|
199
|
-
line = nil
|
200
|
-
elsif project_path?(path)
|
201
|
-
# Get relative path to root
|
202
|
-
path = Pathname.new(path).relative_path_from(config.root).to_s
|
203
|
-
else
|
204
|
-
return
|
183
|
+
unbound_method&.source_location
|
184
|
+
rescue NameError
|
185
|
+
nil
|
186
|
+
end
|
205
187
|
end
|
188
|
+
end
|
189
|
+
end
|
206
190
|
|
207
|
-
|
191
|
+
def sanitize_source_location(path, line)
|
192
|
+
# Do this first since gems may be vendored in the app repo. However, it might be slower.
|
193
|
+
# Should we cache matches?
|
194
|
+
if (gem_name = find_source_gem(path))
|
195
|
+
path = gem_name
|
196
|
+
line = nil
|
197
|
+
elsif project_path?(path)
|
198
|
+
# Get relative path to root
|
199
|
+
path = Pathname.new(path).relative_path_from(config.root).to_s
|
200
|
+
else
|
201
|
+
return
|
208
202
|
end
|
209
203
|
|
204
|
+
line ? "#{path}:#{line}" : path
|
205
|
+
end
|
206
|
+
|
210
207
|
private
|
211
208
|
|
212
|
-
|
213
|
-
|
209
|
+
def gem_require_trie
|
210
|
+
@gem_require_trie ||=
|
211
|
+
begin
|
214
212
|
trie = {}
|
215
213
|
|
216
214
|
Gem.loaded_specs.each do |name, spec|
|
@@ -219,10 +217,12 @@ module Skylight
|
|
219
217
|
spec.full_require_paths.each do |path|
|
220
218
|
t1 = trie
|
221
219
|
|
222
|
-
path
|
223
|
-
|
224
|
-
|
225
|
-
|
220
|
+
path
|
221
|
+
.split(File::SEPARATOR)
|
222
|
+
.each do |segment|
|
223
|
+
t1[segment] ||= {}
|
224
|
+
t1 = t1[segment]
|
225
|
+
end
|
226
226
|
|
227
227
|
t1[:name] = name
|
228
228
|
end
|
@@ -230,62 +230,62 @@ module Skylight
|
|
230
230
|
|
231
231
|
trie
|
232
232
|
end
|
233
|
-
|
233
|
+
end
|
234
234
|
|
235
|
-
|
236
|
-
|
235
|
+
def find_source_gem(path)
|
236
|
+
return nil unless path
|
237
237
|
|
238
|
-
|
238
|
+
trie = gem_require_trie
|
239
239
|
|
240
|
-
|
240
|
+
path
|
241
|
+
.split(File::SEPARATOR)
|
242
|
+
.each do |segment|
|
241
243
|
trie = trie[segment]
|
242
244
|
break unless trie
|
243
245
|
return trie[:name] if trie[:name]
|
244
246
|
end
|
245
247
|
|
246
|
-
|
247
|
-
|
248
|
+
nil
|
249
|
+
end
|
248
250
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
end
|
251
|
+
def find_caller_inner(locations)
|
252
|
+
# Start at file before this one
|
253
|
+
# NOTE: We could start farther back now to avoid more Skylight files
|
254
|
+
locations.find do |l|
|
255
|
+
absolute_path = l.absolute_path
|
256
|
+
find_source_gem(absolute_path) || project_path?(absolute_path)
|
256
257
|
end
|
258
|
+
end
|
257
259
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
260
|
+
# walks up the inheritance tree until it finds the last method
|
261
|
+
# without a super_method definition.
|
262
|
+
def find_instance_method_super(constant, method_name)
|
263
|
+
return unless (unbound_method = find_instance_method(constant, method_name))
|
262
264
|
|
263
|
-
|
264
|
-
unbound_method = unbound_method.super_method
|
265
|
-
end
|
265
|
+
unbound_method = unbound_method.super_method while unbound_method.super_method
|
266
266
|
|
267
|
-
|
268
|
-
|
267
|
+
unbound_method
|
268
|
+
end
|
269
269
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
270
|
+
# walks up the inheritance tree until it finds the instance method
|
271
|
+
# belonging to the constant given (skip prepended modules)
|
272
|
+
def find_own_instance_method(constant, method_name)
|
273
|
+
return unless (unbound_method = find_instance_method(constant, method_name))
|
274
274
|
|
275
|
-
|
276
|
-
|
277
|
-
end
|
278
|
-
|
279
|
-
unbound_method if unbound_method.owner == constant
|
275
|
+
while unbound_method.owner != constant && unbound_method.super_method
|
276
|
+
unbound_method = unbound_method.super_method
|
280
277
|
end
|
281
278
|
|
282
|
-
|
283
|
-
|
284
|
-
end
|
279
|
+
unbound_method if unbound_method.owner == constant
|
280
|
+
end
|
285
281
|
|
286
|
-
|
287
|
-
|
288
|
-
|
282
|
+
def find_instance_method(constant, method_name)
|
283
|
+
constant.instance_method(method_name)
|
284
|
+
end
|
285
|
+
|
286
|
+
def find_class_method(constant, method_name)
|
287
|
+
constant.method(method_name)
|
288
|
+
end
|
289
289
|
end
|
290
290
|
end
|
291
291
|
end
|