skylight 5.0.0.beta → 5.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -1
  3. data/ext/extconf.rb +2 -2
  4. data/lib/skylight/cli/doctor.rb +11 -13
  5. data/lib/skylight/config.rb +18 -18
  6. data/lib/skylight/deprecation.rb +3 -1
  7. data/lib/skylight/extensions.rb +8 -0
  8. data/lib/skylight/extensions/source_location.rb +107 -76
  9. data/lib/skylight/instrumenter.rb +3 -2
  10. data/lib/skylight/middleware.rb +4 -4
  11. data/lib/skylight/native_ext_fetcher.rb +2 -2
  12. data/lib/skylight/normalizers.rb +2 -2
  13. data/lib/skylight/normalizers/action_controller/process_action.rb +1 -1
  14. data/lib/skylight/normalizers/action_dispatch/route_set.rb +1 -1
  15. data/lib/skylight/normalizers/active_job/perform.rb +5 -0
  16. data/lib/skylight/normalizers/graphql/base.rb +1 -0
  17. data/lib/skylight/normalizers/render.rb +1 -1
  18. data/lib/skylight/normalizers/sql.rb +3 -2
  19. data/lib/skylight/probes.rb +38 -10
  20. data/lib/skylight/probes/active_job.rb +4 -6
  21. data/lib/skylight/probes/active_job_enqueue.rb +12 -14
  22. data/lib/skylight/probes/active_model_serializers.rb +2 -6
  23. data/lib/skylight/probes/delayed_job.rb +111 -25
  24. data/lib/skylight/probes/middleware.rb +2 -1
  25. data/lib/skylight/probes/net_http.rb +0 -1
  26. data/lib/skylight/railtie.rb +1 -1
  27. data/lib/skylight/sidekiq.rb +12 -7
  28. data/lib/skylight/subscriber.rb +1 -1
  29. data/lib/skylight/trace.rb +5 -1
  30. data/lib/skylight/util/deploy.rb +3 -6
  31. data/lib/skylight/util/http.rb +1 -1
  32. data/lib/skylight/util/logging.rb +2 -2
  33. data/lib/skylight/util/lru_cache.rb +1 -3
  34. data/lib/skylight/version.rb +1 -1
  35. metadata +7 -8
  36. data/lib/skylight/fanout.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7cc16a9f8a59047a74b34d19ed0f85e696e893d0f5c1c6984604a1358df6efb
4
- data.tar.gz: b50cb4b7962f3b78bbccb5b3025bc051febcf6c8cd95303439add2f3df5cb75e
3
+ metadata.gz: 2b77ca3f00aac3011421d279776acf31f9259debecb3319689250e49fbd3310f
4
+ data.tar.gz: 6cee4d4a09aa991c3e5d3cf65bc9534797f59fc8951a7d088025271068d9e9cc
5
5
  SHA512:
6
- metadata.gz: dc3445e94e0b3c5dc4936569df35f60b7ce9ac5f82492f92c247aca1cb44f9570092b11f532d28a6d795b39d2a65eae4bac16b5ca1a9c4e7b1ae2b47eb6c87ff
7
- data.tar.gz: c591bb63cd56354d0954955f93720f1183afb0f71bd6c0fd2df7cfb8f32dab2a4988eaa20fe3018615cbb47531163b09dddd1c849f430989ffb589f98b03f7c5
6
+ metadata.gz: 98649cab11fb628dcfdade4a7dc5507d7640f5f90b6aa9230305fc1c5261d5f73ff3014bc399595f4bc6c85fafb334668f1b81e9020ea5e1f644f3e60ad966d7
7
+ data.tar.gz: edfacb9d5322768a0dbdd9acbdd31ea5305a077dee1a24076078c448c73a9cd7b0b1889ead269db9e661ed49d11e02e2da38326bad90448b12db72718b949a81
@@ -1,8 +1,14 @@
1
+ ## 5.0.0.beta2
2
+ * [FEATURE] Source Locations detection and reporting is now enabled by default (can be disabled with `SKYLIGHT_ENABLE_SOURCE_LOCATIONS=false`)
3
+ * [BREAKING] Rename `environment` keyword argument to `priority_key`. Note `env` has not changed.
4
+ * [BREAKING] Drop support for Ruby 2.4
5
+ * [IMPROVEMENT] Improved Delayed::Job probe
6
+
1
7
  ## 5.0.0.beta
2
8
  * [BREAKING] Merge skylight-core into skylight. All classes previously namespaced under `Skylight::Core` have been moved to `Skylight`.
3
9
  * [BREAKING] Remove `Skylight::Util::Inflector`
4
10
  * [BREAKING] Drop support for Rails 4
5
- * [BREAKING] Drop support for Rails 2.3
11
+ * [BREAKING] Drop support for Ruby 2.3
6
12
  * [IMPROVEMENT] Maintain method visibility when instrumenting with `instrument_method`
7
13
  * [IMPROVEMENT] Update probes to use `Module#prepend` where possible
8
14
  * [IMPROVEMENT] New tokio-based skylightd
@@ -42,7 +42,7 @@ SKYLIGHT_CHECKSUM = ENV["SKYLIGHT_CHECKSUM"]
42
42
  SKYLIGHT_EXT_STRICT = ENV.key?("SKYLIGHT_EXT_STRICT") && ENV["SKYLIGHT_EXT_STRICT"] =~ /^true$/i
43
43
 
44
44
  # Setup logger
45
- LOG = Logger.new(MultiIO.new(STDOUT, File.open(SKYLIGHT_INSTALL_LOG, "a")))
45
+ LOG = Logger.new(MultiIO.new($stdout, File.open(SKYLIGHT_INSTALL_LOG, "a")))
46
46
 
47
47
  # Handles terminating in the case of a failure. If we have a bug, we do not
48
48
  # want to break our customer's deploy, but extconf.rb requires a Makefile to be
@@ -69,7 +69,7 @@ end
69
69
  if Platform::OS == "darwin"
70
70
  # If the user installs Xcode-only, they have to approve the
71
71
  # license or no "xc*" tool will work.
72
- if `/usr/bin/xcrun clang 2>&1` =~ /license/ && !$CHILD_STATUS.success?
72
+ if `/usr/bin/xcrun clang 2>&1` =~ /license/ && !$CHILD_STATUS.success? # rubocop:disable Style/SoleNestedConditional
73
73
  fail <<~MESSAGE
74
74
  You have not agreed to the Xcode license and so we are unable to build the native agent.
75
75
  To resolve this, you can agree to the license by opening Xcode.app or running:
@@ -85,20 +85,18 @@ module Skylight
85
85
  say "Checking for valid configuration"
86
86
 
87
87
  indent do
88
- begin
89
- config.validate!
90
- say "Configuration is valid", :green
91
- rescue ConfigError => e
92
- encountered_error!
93
-
94
- say "Configuration is invalid", :red
95
- indent do
96
- say e.message, :red
97
- say "This may occur if you are configuring with ENV variables and didn't set them in this shell."
98
- end
99
-
100
- done!
88
+ config.validate!
89
+ say "Configuration is valid", :green
90
+ rescue ConfigError => e
91
+ encountered_error!
92
+
93
+ say "Configuration is invalid", :red
94
+ indent do
95
+ say e.message, :red
96
+ say "This may occur if you are configuring with ENV variables and didn't set them in this shell."
101
97
  end
98
+
99
+ done!
102
100
  end
103
101
 
104
102
  puts "\n"
@@ -119,9 +119,7 @@ module Skylight
119
119
  native_log_level: "LOG_LEVEL"
120
120
  }.freeze
121
121
 
122
- SERVER_VALIDATE = %i[
123
- enable_source_locations
124
- ].freeze
122
+ SERVER_VALIDATE = %i[].freeze
125
123
 
126
124
  DEFAULT_IGNORED_SOURCE_LOCATION_GEMS = [
127
125
  -"skylight",
@@ -150,7 +148,7 @@ module Skylight
150
148
  enable_segments: true,
151
149
  enable_sidekiq: false,
152
150
  sinatra_route_prefixes: false,
153
- enable_source_locations: false,
151
+ enable_source_locations: true,
154
152
 
155
153
  # Deploys
156
154
  'heroku.dyno_info_path': -"/etc/heroku/dyno",
@@ -243,7 +241,7 @@ module Skylight
243
241
  end
244
242
 
245
243
  # @api private
246
- attr_reader :environment
244
+ attr_reader :priority_key
247
245
 
248
246
  # @api private
249
247
  def initialize(*args)
@@ -253,16 +251,16 @@ module Skylight
253
251
  attrs = args.pop.dup
254
252
  end
255
253
 
256
- @values = {}
254
+ @values = {}
257
255
  @priority = {}
258
- @regexp = nil
256
+ @priority_regexp = nil
259
257
  @alert_logger = nil
260
258
  @logger = nil
261
259
 
262
260
  p = attrs.delete(:priority)
263
261
 
264
- if (@environment = args[0])
265
- @regexp = /^#{Regexp.escape(@environment)}\.(.+)$/
262
+ if (@priority_key = args[0])
263
+ @priority_regexp = /^#{Regexp.escape(priority_key)}\.(.+)$/
266
264
  end
267
265
 
268
266
  attrs.each do |k, v|
@@ -277,7 +275,8 @@ module Skylight
277
275
  def self.load(opts = {}, env = ENV)
278
276
  attrs = {}
279
277
  path = opts.delete(:file)
280
- environment = opts.delete(:environment)
278
+ priority_key = opts.delete(:priority_key)
279
+ priority_key ||= opts[:env] # if a priority_key is not given, use env if available
281
280
 
282
281
  if path
283
282
  error = nil
@@ -295,11 +294,14 @@ module Skylight
295
294
  raise ConfigError, "could not load config file; msg=#{error}" if error
296
295
  end
297
296
 
297
+ # The key-value pairs in this `priority` option are inserted into the
298
+ # config's @priority hash *after* anything listed under priority_key;
299
+ # i.e., ENV takes precendence over priority_key
298
300
  if env
299
301
  attrs[:priority] = remap_env(env)
300
302
  end
301
303
 
302
- config = new(environment, attrs)
304
+ config = new(priority_key, attrs)
303
305
 
304
306
  opts.each do |k, v|
305
307
  config[k] = v
@@ -439,7 +441,7 @@ module Skylight
439
441
  end
440
442
  end
441
443
 
442
- if @regexp && k =~ @regexp
444
+ if @priority_regexp && k =~ @priority_regexp
443
445
  @priority[$1.to_sym] = val
444
446
  end
445
447
 
@@ -586,7 +588,7 @@ module Skylight
586
588
  end
587
589
  end
588
590
 
589
- attr_writer :logger
591
+ attr_writer :logger, :alert_logger
590
592
 
591
593
  def alert_logger
592
594
  @alert_logger ||= MUTEX.synchronize do
@@ -601,8 +603,6 @@ module Skylight
601
603
  end
602
604
  end
603
605
 
604
- attr_writer :alert_logger
605
-
606
606
  def enable_segments?
607
607
  !!get(:enable_segments)
608
608
  end
@@ -638,13 +638,13 @@ module Skylight
638
638
 
639
639
  Logger.new(out, progname: "Skylight", level: level)
640
640
  rescue
641
- Logger.new(STDOUT, progname: "Skylight", level: level)
641
+ Logger.new($stdout, progname: "Skylight", level: level)
642
642
  end
643
643
 
644
644
  def load_logger
645
645
  unless (l = @logger)
646
646
  out = get(:log_file)
647
- out = STDOUT if out == "-"
647
+ out = $stdout if out == "-"
648
648
  l = create_logger(out, level: log_level)
649
649
  end
650
650
 
@@ -700,7 +700,7 @@ module Skylight
700
700
 
701
701
  # This is a weird way to handle priorities
702
702
  # See https://github.com/tildeio/direwolf-agent/issues/275
703
- k = "#{environment}.#{k}" if environment
703
+ k = "#{priority_key}.#{k}" if priority_key
704
704
 
705
705
  set(k, v)
706
706
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/deprecation"
2
4
 
3
5
  module Skylight
4
- SKYLIGHT_GEM_ROOT = File.expand_path("../..", __dir__) + "/"
6
+ SKYLIGHT_GEM_ROOT = "#{File.expand_path('../..', __dir__)}/"
5
7
 
6
8
  class Deprecation < ActiveSupport::Deprecation
7
9
  private
@@ -32,6 +32,12 @@ module Skylight
32
32
  !!extensions.detect { |x| x.is_a?(ext_class) }
33
33
  end
34
34
 
35
+ def process_trace_meta(meta)
36
+ extensions.each do |ext|
37
+ ext.process_trace_meta(meta)
38
+ end
39
+ end
40
+
35
41
  # meta is a mutable hash that will be passed to the instrumenter.
36
42
  # This method bridges Skylight.instrument and instrumenter.instrument.
37
43
  def process_instrument_options(opts, meta)
@@ -83,6 +89,8 @@ module Skylight
83
89
  @config = config
84
90
  end
85
91
 
92
+ def process_trace_meta(_meta); end
93
+
86
94
  def process_instrument_options(_opts, _meta); end
87
95
 
88
96
  def process_normalizer_meta(_payload, _meta, **opts); end
@@ -19,13 +19,29 @@ module Skylight
19
19
  gem_require_trie # memoize this at startup
20
20
  end
21
21
 
22
+ def process_trace_meta(meta)
23
+ unless meta[:source_location] || meta[:source_file]
24
+ warn "Ignoring source_line without source_file" if meta[:source_line]
25
+ if (location = find_caller)
26
+ meta[:source_file] = location.absolute_path
27
+ meta[:source_line] = location.lineno
28
+ end
29
+ end
30
+ end
31
+
22
32
  def process_instrument_options(opts, meta)
23
33
  source_location = opts[:source_location] || opts[:meta]&.[](:source_location)
24
34
  source_file = opts[:source_file] || opts[:meta]&.[](:source_file)
25
35
  source_line = opts[:source_line] || opts[:meta]&.[](:source_line)
36
+ source_name_hint, const_name, method_name = opts[:source_location_hint] ||
37
+ opts[:meta]&.[](:source_location_hint)
26
38
 
27
39
  if source_location
28
40
  meta[:source_location] = source_location
41
+ elsif source_name_hint
42
+ source_location = dispatch_hinted_source_location(source_name_hint, const_name, method_name)
43
+ meta[:source_file], meta[:source_line] = source_location
44
+ meta.delete(:source_location_hint) if source_location
29
45
  elsif source_file
30
46
  meta[:source_file] = source_file
31
47
  meta[:source_line] = source_line
@@ -41,14 +57,21 @@ module Skylight
41
57
  end
42
58
 
43
59
  def process_normalizer_meta(payload, meta, **opts)
44
- sl = if ((source_name, *args) = opts[:source_location])
45
- dispatch_hinted_source_location(
46
- source_name,
47
- payload,
48
- meta,
49
- args: args, **opts
50
- )
51
- end
60
+ if opts[:source_location] && (opts[:source_file] || opts[:source_line])
61
+ warn "Found both source_location and source_file or source_line in normalizer\n" \
62
+ " location=#{opts[:source_location]}; file=#{opts[:source_file]}; line=#{opts[:source_line]}"
63
+ end
64
+
65
+ sl =
66
+ if (source_name, constant_name, method_name = opts[:source_location_hint])
67
+ dispatch_hinted_source_location(
68
+ source_name,
69
+ constant_name,
70
+ method_name
71
+ )
72
+ elsif opts[:source_file]
73
+ [opts[:source_file], opts[:source_line]]
74
+ end
52
75
 
53
76
  sl ||= source_location(payload, meta, cache_key: opts[:cache_key])
54
77
 
@@ -91,86 +114,88 @@ module Skylight
91
114
 
92
115
  protected
93
116
 
94
- def dispatch_hinted_source_location(source_name, payload, meta, args:, **opts)
95
- const_name, method_name = args
96
- return unless const_name && method_name
117
+ def dispatch_hinted_source_location(source_name, const_name, method_name)
118
+ return unless const_name && method_name
97
119
 
98
- instance_method_source_location(const_name, method_name, source_name: source_name)
99
- end
120
+ instance_method_source_location(const_name, method_name, source_name: source_name)
121
+ end
100
122
 
101
- # from normalizers.rb
102
- # Returns an array of file and line
103
- def source_location(payload, meta, cache_key: nil)
104
- # FIXME: what should precedence be?
105
- if meta.is_a?(Hash) && meta[:source_location]
106
- meta.delete(:source_location)
107
- elsif payload.is_a?(Hash) && payload[:sk_source_location]
108
- payload[:sk_source_location]
109
- elsif (location = find_caller(cache_key: cache_key))
110
- [location.absolute_path, location.lineno]
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]
133
+ end
111
134
  end
112
- end
113
135
 
114
- def find_caller(cache_key: nil)
115
- if cache_key
116
- @caller_cache.fetch(cache_key) { find_caller_inner }
117
- else
118
- find_caller_inner
136
+ def find_caller(cache_key: nil)
137
+ if cache_key
138
+ @caller_cache.fetch(cache_key) { find_caller_inner }
139
+ else
140
+ find_caller_inner
141
+ end
119
142
  end
120
- end
121
143
 
122
- def project_path?(path)
123
- # Must be in the project root
124
- return false unless path.start_with?(config.root.to_s)
125
- # Must not be Bundler's vendor location
126
- return false if defined?(Bundler) && path.start_with?(Bundler.bundle_path.to_s)
127
- # Must not be Ruby files
128
- return false if path.include?("/ruby-#{RUBY_VERSION}/lib/ruby/")
144
+ def project_path?(path)
145
+ return false unless path
129
146
 
130
- # So it must be a project file
131
- true
132
- end
147
+ # Must be in the project root
148
+ return false unless path.start_with?(config.root.to_s)
149
+ # Must not be Bundler's vendor location
150
+ return false if defined?(Bundler) && path.start_with?(Bundler.bundle_path.to_s)
151
+ # Must not be Ruby files
152
+ return false if path.include?("/ruby-#{RUBY_VERSION}/lib/ruby/")
133
153
 
134
- def instance_method_source_location(constant_name, method_name, source_name: :instance_method)
135
- @instance_method_source_location_cache.fetch([constant_name, method_name, source_name]) do
136
- if (constant = ::ActiveSupport::Dependencies.safe_constantize(constant_name))
137
- if constant.instance_methods.include?(:"before_instrument_#{method_name}")
138
- method_name = :"before_instrument_#{method_name}"
139
- end
140
- begin
141
- unbound_method = case source_name
142
- when :instance_method
143
- find_instance_method(constant, method_name)
144
- when :own_instance_method
145
- find_own_instance_method(constant, method_name)
146
- when :instance_method_super
147
- find_instance_method_super(constant, method_name)
148
- end
149
-
150
- unbound_method&.source_location
151
- rescue NameError
152
- nil
154
+ # So it must be a project file
155
+ true
156
+ end
157
+
158
+ def instance_method_source_location(constant_name, method_name, source_name: :instance_method)
159
+ @instance_method_source_location_cache.fetch([constant_name, method_name, source_name]) do
160
+ if (constant = ::ActiveSupport::Dependencies.safe_constantize(constant_name))
161
+ if constant.instance_methods.include?(:"before_instrument_#{method_name}")
162
+ method_name = :"before_instrument_#{method_name}"
163
+ end
164
+ begin
165
+ unbound_method = case source_name
166
+ when :instance_method
167
+ find_instance_method(constant, method_name)
168
+ when :own_instance_method
169
+ find_own_instance_method(constant, method_name)
170
+ when :instance_method_super
171
+ find_instance_method_super(constant, method_name)
172
+ when :class_method
173
+ find_class_method(constant, method_name)
174
+ end
175
+
176
+ unbound_method&.source_location
177
+ rescue NameError
178
+ nil
179
+ end
153
180
  end
154
181
  end
155
182
  end
156
- end
157
183
 
158
- def sanitize_source_location(path, line)
159
- # Do this first since gems may be vendored in the app repo. However, it might be slower.
160
- # Should we cache matches?
161
- if (gem_name = find_source_gem(path))
162
- find_source_gem(path)
163
- path = gem_name
164
- line = nil
165
- elsif project_path?(path)
166
- # Get relative path to root
167
- path = Pathname.new(path).relative_path_from(config.root).to_s
168
- else
169
- return
170
- end
184
+ def sanitize_source_location(path, line)
185
+ # Do this first since gems may be vendored in the app repo. However, it might be slower.
186
+ # Should we cache matches?
187
+ if (gem_name = find_source_gem(path))
188
+ path = gem_name
189
+ line = nil
190
+ elsif project_path?(path)
191
+ # Get relative path to root
192
+ path = Pathname.new(path).relative_path_from(config.root).to_s
193
+ else
194
+ return
195
+ end
171
196
 
172
- line ? "#{path}:#{line}" : path
173
- end
197
+ line ? "#{path}:#{line}" : path
198
+ end
174
199
 
175
200
  private
176
201
 
@@ -198,11 +223,13 @@ module Skylight
198
223
  end
199
224
 
200
225
  def find_source_gem(path)
226
+ return nil unless path
227
+
201
228
  trie = gem_require_trie
202
229
 
203
230
  path.split(File::SEPARATOR).each do |segment|
204
231
  trie = trie[segment]
205
- return unless trie
232
+ break unless trie
206
233
  return trie[:name] if trie[:name]
207
234
  end
208
235
 
@@ -211,6 +238,7 @@ module Skylight
211
238
 
212
239
  def find_caller_inner
213
240
  # Start at file before this one
241
+ # NOTE: We could start farther back now to avoid more Skylight files
214
242
  caller_locations(1).find do |l|
215
243
  find_source_gem(l.absolute_path) || project_path?(l.absolute_path)
216
244
  end
@@ -244,6 +272,9 @@ module Skylight
244
272
  constant.instance_method(method_name)
245
273
  end
246
274
 
275
+ def find_class_method(constant, method_name)
276
+ constant.method(method_name)
277
+ end
247
278
  end
248
279
  end
249
280
  end
@@ -60,8 +60,6 @@ module Skylight
60
60
  @trace_info = @config[:trace_info] || TraceInfo.new(KEY)
61
61
  @mutex = Mutex.new
62
62
  @extensions = Skylight::Extensions::Collection.new(@config)
63
-
64
- enable_extension!(:source_location) if @config.enable_source_locations?
65
63
  end
66
64
 
67
65
  def enable_extension!(name)
@@ -176,6 +174,7 @@ module Skylight
176
174
  return
177
175
  end
178
176
 
177
+ enable_extension!(:source_location) if @config.enable_source_locations?
179
178
  config.gc.enable
180
179
  @subscriber.register!
181
180
 
@@ -202,6 +201,8 @@ module Skylight
202
201
  end
203
202
 
204
203
  begin
204
+ meta ||= {}
205
+ extensions.process_trace_meta(meta)
205
206
  trace = Trace.new(self, endpoint, Skylight::Util::Clock.nanos, cat, title, desc,
206
207
  meta: meta, segment: segment, component: component)
207
208
  rescue Exception => e
@@ -10,10 +10,10 @@ module Skylight
10
10
  @closed = false
11
11
  end
12
12
 
13
- def respond_to_missing?(*args)
14
- return false if args.first.to_s !~ /^to_ary$/
13
+ def respond_to_missing?(name, include_all = false) # rubocop:disable Lint/MissingSuper, Style/OptionalBooleanParameter
14
+ return false if name.to_s !~ /^to_ary$/
15
15
 
16
- @body.respond_to?(*args)
16
+ @body.respond_to?(name, include_all)
17
17
  end
18
18
 
19
19
  def close
@@ -117,7 +117,7 @@ module Skylight
117
117
  end
118
118
 
119
119
  def endpoint_meta(_env)
120
- nil
120
+ { source_location: Trace::SYNTHETIC }
121
121
  end
122
122
 
123
123
  # Request ID code based on ActionDispatch::RequestId
@@ -23,7 +23,7 @@ module Skylight
23
23
  # @param opts [Hash]
24
24
  def self.fetch(**args)
25
25
  args[:source] ||= BASE_URL
26
- args[:logger] ||= Logger.new(STDOUT)
26
+ args[:logger] ||= Logger.new($stdout)
27
27
  new(**args).fetch
28
28
  end
29
29
 
@@ -35,7 +35,7 @@ module Skylight
35
35
  # @param required [Boolean] whether the download is required to be successful
36
36
  # @param platform
37
37
  # @param log [Logger]
38
- def initialize(source:, target:, version:, checksum:, arch:, required: false, platform: nil, logger:)
38
+ def initialize(source:, target:, version:, checksum:, arch:, logger:, required: false, platform: nil)
39
39
  raise "source required" unless source
40
40
  raise "target required" unless target
41
41
  raise "checksum required" unless checksum
@@ -20,7 +20,7 @@ module Skylight
20
20
  matches = registry.select { |n, _| n =~ /(^|\.)#{name}$/ }
21
21
  raise ArgumentError, "no normalizers match #{name}" if matches.empty?
22
22
 
23
- matches.values.each { |v| v[1] = enabled }
23
+ matches.each_value { |v| v[1] = enabled }
24
24
  end
25
25
  end
26
26
 
@@ -78,7 +78,7 @@ module Skylight
78
78
 
79
79
  private
80
80
 
81
- def process_meta(trace, name, payload, meta, cache_key: nil)
81
+ def process_meta(trace, _name, payload, meta, cache_key: nil)
82
82
  trace.instrumenter.extensions.process_normalizer_meta(
83
83
  payload,
84
84
  meta,
@@ -38,7 +38,7 @@ module Skylight
38
38
 
39
39
  def process_meta_options(payload)
40
40
  # provide hints to override default source_location behavior
41
- super.merge(source_location: [:instance_method, payload[:controller], payload[:action]])
41
+ super.merge(source_location_hint: [:instance_method, payload[:controller], payload[:action]])
42
42
  end
43
43
 
44
44
  def segment_from_payload(payload)
@@ -19,7 +19,7 @@ module Skylight
19
19
 
20
20
  def process_meta_options(_payload)
21
21
  # provide hints to override default source_location behavior
22
- super.merge(source_location: [:own_instance_method, router_class_name, "call"])
22
+ super.merge(source_location_hint: [:own_instance_method, router_class_name, "call"])
23
23
  end
24
24
  end
25
25
  end
@@ -36,6 +36,11 @@ module Skylight
36
36
 
37
37
  private
38
38
 
39
+ def process_meta_options(payload)
40
+ # provide hints to override default source_location behavior
41
+ super.merge(source_location_hint: [:instance_method, payload[:job].class.to_s, "perform"])
42
+ end
43
+
39
44
  def normalize_adapter_name(adapter)
40
45
  adapter_string = adapter.is_a?(Class) ? adapter.to_s : adapter.class.to_s
41
46
  adapter_string[/ActiveJob::QueueAdapters::(\w+)Adapter/, 1].underscore
@@ -24,6 +24,7 @@ module Skylight::Normalizers::GraphQL
24
24
  end
25
25
 
26
26
  def self.inherited(klass)
27
+ super
27
28
  klass.const_set(
28
29
  :KEY,
29
30
  ActiveSupport::Inflector.underscore(
@@ -40,7 +40,7 @@ module Skylight
40
40
  # Matches a Gem Version or 12-digit hex (sha)
41
41
  # that is preceeded by a `-` and followed by `/`
42
42
  # Also matches 'app/views/' if it exists
43
- %r{-(?:#{Gem::Version::VERSION_PATTERN}|[0-9a-f]{12})\/(?:app\/views\/)*},
43
+ %r{-(?:#{Gem::Version::VERSION_PATTERN}|[0-9a-f]{12})/(?:app/views/)*},
44
44
  ": ".freeze
45
45
  )
46
46
  else
@@ -14,7 +14,7 @@ module Skylight
14
14
  # @option payload [String] [:name] The SQL operation
15
15
  # @option payload [Hash] [:binds] The bound parameters
16
16
  # @return [Array]
17
- def normalize(trace, name, payload)
17
+ def normalize(_trace, name, payload)
18
18
  case payload[:name]
19
19
  when "SCHEMA", "CACHE"
20
20
  return :skip
@@ -29,7 +29,8 @@ module Skylight
29
29
 
30
30
  unless sql.valid_encoding?
31
31
  if config[:log_sql_parse_errors]
32
- config.logger.error "[#{Skylight::SqlLexError.formatted_code}] Unable to extract binds from non-UTF-8 query. " \
32
+ config.logger.error "[#{Skylight::SqlLexError.formatted_code}] Unable to extract binds from non-UTF-8 " \
33
+ "query. " \
33
34
  "encoding=#{payload[:sql].encoding.name} " \
34
35
  "sql=#{payload[:sql].inspect} "
35
36
  end
@@ -16,11 +16,41 @@ module Skylight
16
16
 
17
17
  def install
18
18
  probe.install
19
+ rescue StandardError, LoadError => e
20
+ log_install_exception(e)
19
21
  end
20
22
 
21
23
  def constant_available?
22
24
  Skylight::Probes.constant_available?(const_name)
23
25
  end
26
+
27
+ private
28
+
29
+ def log_install_exception(err)
30
+ description = err.class.to_s
31
+ description << ": #{err.message}" unless err.message.empty?
32
+
33
+ backtrace = err.backtrace.map { |l| " #{l}" }.join("\n")
34
+
35
+ gems =
36
+ begin
37
+ Bundler.locked_gems.dependencies.map { |d| [d.name, d.requirement.to_s] }
38
+ rescue # rubocop:disable Lint/SuppressedException
39
+ end
40
+
41
+ error = "[SKYLIGHT] [#{Skylight::VERSION}] Encountered an error while installing the " \
42
+ "probe for #{const_name}. Please notify support@skylight.io with the debugging " \
43
+ "information below. It's recommended that you disable this probe until the " \
44
+ "issue is resolved." \
45
+ "\n\nERROR: #{description}\n\n#{backtrace}\n\n"
46
+
47
+ if gems
48
+ gems_string = gems.map { |g| " #{g[0]} #{g[1]}" }.join("\n")
49
+ error << "GEMS:\n\n#{gems_string}\n\n"
50
+ end
51
+
52
+ $stderr.puts(error)
53
+ end
24
54
  end
25
55
 
26
56
  class << self
@@ -99,12 +129,12 @@ module Skylight
99
129
  def require_hook(require_path)
100
130
  each_by_require_path(require_path) do |registration|
101
131
  # Double check constant is available
102
- if registration.constant_available?
103
- install_probe(registration)
132
+ next unless registration.constant_available?
104
133
 
105
- # Don't need this to be called again
106
- unregister_require_hook(registration)
107
- end
134
+ install_probe(registration)
135
+
136
+ # Don't need this to be called again
137
+ unregister_require_hook(registration)
108
138
  end
109
139
  end
110
140
 
@@ -143,11 +173,9 @@ module Kernel
143
173
 
144
174
  def require(name)
145
175
  require_without_sk(name).tap do
146
- begin
147
- Skylight::Probes.require_hook(name)
148
- rescue Exception => e # rubocop:disable Lint/SuppressedException
149
- warn("[SKYLIGHT] Rescued exception in require hook", e)
150
- end
176
+ Skylight::Probes.require_hook(name)
177
+ rescue Exception => e
178
+ warn("[SKYLIGHT] Rescued exception in require hook", e)
151
179
  end
152
180
  end
153
181
  end
@@ -7,12 +7,10 @@ module Skylight
7
7
  def execute(*)
8
8
  Skylight.trace(TITLE, "app.job.execute", component: :worker) do |trace|
9
9
  # See normalizers/active_job/perform for endpoint/segment assignment
10
- begin
11
- super
12
- rescue Exception
13
- trace.segment = "error" if trace
14
- raise
15
- end
10
+ super
11
+ rescue Exception
12
+ trace.segment = "error" if trace
13
+ raise
16
14
  end
17
15
  end
18
16
  end
@@ -6,21 +6,19 @@ module Skylight
6
6
 
7
7
  def install
8
8
  ::ActiveJob::Base.around_enqueue do |job, block|
9
- begin
10
- job_class = job.class
11
- adapter_name = EnqueueProbe.normalize_adapter_name(job_class)
9
+ job_class = job.class
10
+ adapter_name = EnqueueProbe.normalize_adapter_name(job_class)
12
11
 
13
- # If this is an ActionMailer::DeliveryJob, we'll report this as the mailer title
14
- # and include ActionMailer::DeliveryJob in the description.
15
- name, job_class_name = Normalizers::ActiveJob::Perform.normalize_title(job)
16
- descriptors = ["adapter: '#{adapter_name}'", "queue: '#{job.queue_name}'"]
17
- descriptors << "job: '#{job_class_name}'" if job_class_name
18
- desc = "{ #{descriptors.join(', ')} }"
19
- rescue
20
- block.call
21
- else
22
- Skylight.instrument(title: "Enqueue #{name}", category: CAT, description: desc, &block)
23
- end
12
+ # If this is an ActionMailer::DeliveryJob, we'll report this as the mailer title
13
+ # and include ActionMailer::DeliveryJob in the description.
14
+ name, job_class_name = Normalizers::ActiveJob::Perform.normalize_title(job)
15
+ descriptors = ["adapter: '#{adapter_name}'", "queue: '#{job.queue_name}'"]
16
+ descriptors << "job: '#{job_class_name}'" if job_class_name
17
+ desc = "{ #{descriptors.join(', ')} }"
18
+ rescue
19
+ block.call
20
+ else
21
+ Skylight.instrument(title: "Enqueue #{name}", category: CAT, description: desc, &block)
24
22
  end
25
23
 
26
24
  self.class.instance_eval do
@@ -14,12 +14,8 @@ module Skylight
14
14
 
15
15
  # File moved location between version
16
16
  %w[serializer serializers].each do |dir|
17
- # rubocop:disable Lint/SuppressedException
18
- begin
19
- require "active_model/#{dir}/version"
20
- rescue LoadError
21
- end
22
- # rubocop:enable Lint/SuppressedException
17
+ require "active_model/#{dir}/version"
18
+ rescue LoadError # rubocop:disable Lint/SuppressedException
23
19
  end
24
20
 
25
21
  if Gem.loaded_specs["active_model_serializers"]
@@ -1,46 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
1
5
  module Skylight
2
6
  module Probes
3
7
  module DelayedJob
4
- module Instrumentation
5
- include Skylight::Util::Logging
6
-
7
- def run(job, *)
8
- t { "Delayed::Job beginning trace" }
9
-
10
- handler_name =
11
- begin
12
- if defined?(::Delayed::PerformableMethod) && job.payload_object.is_a?(::Delayed::PerformableMethod)
13
- job.name
14
- else
15
- job.payload_object.class.name
16
- end
17
- rescue
18
- UNKNOWN
8
+ begin
9
+ require "delayed/plugin"
10
+
11
+ class Plugin < ::Delayed::Plugin
12
+ callbacks do |lifecycle|
13
+ lifecycle.around(:perform) do |worker, job, &block|
14
+ sk_instrument(worker, job, &block)
19
15
  end
20
16
 
21
- Skylight.trace(handler_name, "app.delayed_job.worker", "Delayed::Worker#run",
22
- component: :worker, segment: job.queue) { super }
17
+ lifecycle.after(:error) do |_worker, _job|
18
+ Skylight.trace&.segment = "error"
19
+ end
20
+ end
21
+
22
+ class << self
23
+ include Skylight::Util::Logging
24
+
25
+ def sk_instrument(_worker, job)
26
+ endpoint = Skylight::Probes::DelayedJob.handler_name(job)
27
+
28
+ Skylight.trace(endpoint,
29
+ "app.delayed_job.worker",
30
+ "Delayed::Worker#run",
31
+ component: :worker,
32
+ segment: job.queue,
33
+ meta: { source_location: "delayed_job" }) do
34
+ t { "Delayed::Job beginning trace" }
35
+ yield
36
+ end
37
+ end
38
+ end
23
39
  end
40
+ rescue LoadError
41
+ $stderr.puts "[SKYLIGHT] The delayed_job probe was requested, but Delayed::Plugin was not defined."
42
+ end
43
+
44
+ UNKNOWN = "<Delayed::Job Unknown>"
24
45
 
25
- def handle_failed_job(*)
26
- super
27
- return unless Skylight.trace
46
+ def self.handler_name(job)
47
+ payload_object = if job.respond_to?(:payload_object_without_sk)
48
+ job.payload_object_without_sk
49
+ else
50
+ job.payload_object
51
+ end
52
+
53
+ payload_object_name(payload_object)
54
+ end
28
55
 
29
- Skylight.trace.segment = "error"
56
+ def self.payload_object_name(payload_object)
57
+ if payload_object.is_a?(::Delayed::PerformableMethod)
58
+ payload_object.display_name
59
+ else
60
+ # In the case of ActiveJob-wrapped jobs, there is quite a bit of job-specific metadata
61
+ # in `job.name`, which would break aggregation and potentially leak private data in job args.
62
+ # Use class name instead to avoid this.
63
+ payload_object.class.name
30
64
  end
65
+ rescue
66
+ UNKNOWN
31
67
  end
32
68
 
33
- class Probe
34
- UNKNOWN = "<Delayed::Job Unknown>".freeze
69
+ def self.payload_object_source_meta(payload_object)
70
+ if payload_object.is_a?(::Delayed::PerformableMethod)
71
+ if payload_object.object.is_a?(Module)
72
+ [:class_method, payload_object.object.name, payload_object.method_name.to_s]
73
+ else
74
+ [:instance_method, payload_object.object.class.name, payload_object.method_name.to_s]
75
+ end
76
+ else
77
+ [:instance_method, payload_object.class.name, "perform"]
78
+ end
79
+ end
35
80
 
81
+ class InstrumentationProxy < SimpleDelegator
82
+ def perform
83
+ source_meta = Skylight::Probes::DelayedJob.payload_object_source_meta(__getobj__)
84
+
85
+ opts = {
86
+ category: "app.delayed_job.job",
87
+ title: format_source(*source_meta),
88
+ meta: { source_location_hint: source_meta }
89
+ }
90
+
91
+ Skylight.instrument(opts) { __getobj__.perform }
92
+ end
93
+
94
+ # Used by Delayed::Backend::Base to determine Job#name
95
+ def display_name
96
+ __getobj__.respond_to?(:display_name) ? __getobj__.display_name : __getobj__.class.name
97
+ end
98
+
99
+ private
100
+
101
+ def format_source(method_type, constant_name, method_name)
102
+ if method_type == :instance_method
103
+ "#{constant_name}##{method_name}"
104
+ else
105
+ "#{constant_name}.#{method_name}"
106
+ end
107
+ end
108
+ end
109
+
110
+ class Probe
36
111
  def install
37
- return unless validate_version
112
+ return unless validate_version && plugin_defined?
113
+
114
+ ::Delayed::Worker.plugins = [Skylight::Probes::DelayedJob::Plugin] | ::Delayed::Worker.plugins
115
+ ::Delayed::Backend::Base.class_eval do
116
+ alias_method :payload_object_without_sk, :payload_object
38
117
 
39
- ::Delayed::Worker.prepend(Instrumentation)
118
+ def payload_object
119
+ Skylight::Probes::DelayedJob::InstrumentationProxy.new(payload_object_without_sk)
120
+ end
121
+ end
40
122
  end
41
123
 
42
124
  private
43
125
 
126
+ def plugin_defined?
127
+ defined?(::Skylight::Probes::DelayedJob::Plugin)
128
+ end
129
+
44
130
  def validate_version
45
131
  spec = Gem.loaded_specs["delayed_job"]
46
132
  version = spec&.version
@@ -72,7 +72,8 @@ module Skylight
72
72
 
73
73
  source_file, source_line = method(__method__).super_method.source_location
74
74
 
75
- spans = Skylight.instrument(title: name, category: __sk_category, source_file: source_file, source_line: source_line)
75
+ spans = Skylight.instrument(title: name, category: __sk_category,
76
+ source_file: source_file, source_line: source_line)
76
77
 
77
78
  proxied_response =
78
79
  Skylight::Middleware.with_after_close(super(*args), debug_identifier: "Middleware: #{name}") do
@@ -14,7 +14,6 @@ module Skylight
14
14
 
15
15
  # If we're connected with a persistent socket
16
16
  host ||= address
17
- port ||= port
18
17
 
19
18
  path = req.path
20
19
  scheme = use_ssl? ? "https" : "http"
@@ -99,7 +99,7 @@ module Skylight
99
99
  return nil
100
100
  end
101
101
 
102
- config = Config.load(file: path, environment: Rails.env.to_s)
102
+ config = Config.load(file: path, priority_key: Rails.env.to_s)
103
103
  config[:root] = Rails.root
104
104
 
105
105
  configure_logging(config, app)
@@ -19,16 +19,21 @@ module Skylight
19
19
  class ServerMiddleware
20
20
  include Util::Logging
21
21
 
22
- def call(_worker, job, queue)
22
+ def call(worker, job, queue)
23
23
  t { "Sidekiq middleware beginning trace" }
24
24
  title = job["wrapped"] || job["class"]
25
- Skylight.trace(title, "app.sidekiq.worker", title, segment: queue, component: :worker) do |trace|
26
- begin
27
- yield
28
- rescue Exception # includes Sidekiq::Shutdown
29
- trace.segment = "error" if trace
30
- raise
25
+
26
+ # TODO: Using hints here would be ideal but requires further refactoring
27
+ meta =
28
+ if (source_location = worker.method(:perform).source_location)
29
+ { source_file: source_location[0], source_line: source_location[1] }
31
30
  end
31
+
32
+ Skylight.trace(title, "app.sidekiq.worker", title, meta: meta, segment: queue, component: :worker) do |trace|
33
+ yield
34
+ rescue Exception # includes Sidekiq::Shutdown
35
+ trace.segment = "error" if trace
36
+ raise
32
37
  end
33
38
  end
34
39
  end
@@ -14,7 +14,7 @@ module Skylight
14
14
 
15
15
  def register!
16
16
  unregister!
17
- @normalizers.keys.each do |key|
17
+ @normalizers.keys.each do |key| # rubocop:disable Style/HashEachMethods
18
18
  @subscribers << ActiveSupport::Notifications.subscribe(key, self)
19
19
  end
20
20
  end
@@ -4,6 +4,7 @@ require "skylight/util/logging"
4
4
  module Skylight
5
5
  class Trace
6
6
  GC_CAT = "noise.gc".freeze
7
+ SYNTHETIC = "<synthetic>".freeze
7
8
 
8
9
  META_KEYS = %i[mute_children].freeze
9
10
 
@@ -39,6 +40,8 @@ module Skylight
39
40
 
40
41
  @spans = []
41
42
 
43
+ preprocess_meta(meta) if meta
44
+
42
45
  # create the root node
43
46
  @root = start(native_get_started_at, cat, title, desc, meta, normalize: false)
44
47
 
@@ -217,7 +220,8 @@ module Skylight
217
220
 
218
221
  if time > 0
219
222
  t { fmt "tracking GC time; duration=%d", time }
220
- stop(start(now - time, GC_CAT, nil, nil, nil), now)
223
+ meta = { source_location: SYNTHETIC }
224
+ stop(start(now - time, GC_CAT, nil, nil, meta), now)
221
225
  end
222
226
  end
223
227
 
@@ -13,8 +13,7 @@ module Skylight
13
13
  end
14
14
 
15
15
  class EmptyDeploy
16
- attr_reader :config
17
- attr_reader :timestamp
16
+ attr_reader :config, :timestamp
18
17
 
19
18
  def initialize(config)
20
19
  @config = config
@@ -90,10 +89,8 @@ module Skylight
90
89
  def get_info
91
90
  info_path = config[:'heroku.dyno_info_path']
92
91
 
93
- if File.exist?(info_path)
94
- if (info = JSON.parse(File.read(info_path)))
95
- info["release"]
96
- end
92
+ if File.exist?(info_path) && (info = JSON.parse(File.read(info_path)))
93
+ info["release"]
97
94
  end
98
95
  end
99
96
  end
@@ -192,7 +192,7 @@ module Skylight
192
192
  body.dig(*key.split(".")) if body.is_a?(Hash)
193
193
  end
194
194
 
195
- def respond_to_missing?(name, include_all = false)
195
+ def respond_to_missing?(name, include_all = false) # rubocop:disable Style/OptionalBooleanParameter
196
196
  super || body.respond_to?(name, include_all)
197
197
  end
198
198
 
@@ -9,12 +9,12 @@ module Skylight
9
9
  end
10
10
 
11
11
  def write(*args)
12
- STDERR.write(*args)
12
+ $stderr.write(*args)
13
13
 
14
14
  # Try to avoid writing to STDOUT/STDERR twice
15
15
  logger_logdev = @logger.instance_variable_get(:@logdev)
16
16
  logger_out = logger_logdev&.respond_to?(:dev) ? logger_logdev.dev : nil
17
- if logger_out != STDOUT && logger_out != STDERR
17
+ if logger_out != $stdout && logger_out != $stderr
18
18
  @logger.<<(*args)
19
19
  end
20
20
  end
@@ -11,9 +11,7 @@ module Skylight
11
11
  raise ArgumentError, :max_size if @max_size < 1
12
12
 
13
13
  @max_size = size
14
- while @data.size > @max_size
15
- @data.shift
16
- end
14
+ @data.shift while @data.size > @max_size
17
15
  end
18
16
 
19
17
  # Individual hash operations here are atomic in MRI.
@@ -3,5 +3,5 @@ module Skylight
3
3
  # for compatibility with semver when it is parsed by the rust agent.
4
4
  # This string will be transformed in the gemspec to "5.0.0.alpha"
5
5
  # to conform with rubygems.
6
- VERSION = "5.0.0-beta".freeze
6
+ VERSION = "5.0.0-beta2".freeze
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skylight
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0.beta
4
+ version: 5.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tilde, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-06 00:00:00.000000000 Z
11
+ date: 2020-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -86,28 +86,28 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 12.3.3
89
+ version: 13.0.1
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 12.3.3
96
+ version: 13.0.1
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rake-compiler
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 1.0.4
103
+ version: 1.1.1
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 1.0.4
110
+ version: 1.1.1
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rspec
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -211,7 +211,6 @@ files:
211
211
  - lib/skylight/errors.rb
212
212
  - lib/skylight/extensions.rb
213
213
  - lib/skylight/extensions/source_location.rb
214
- - lib/skylight/fanout.rb
215
214
  - lib/skylight/formatters/http.rb
216
215
  - lib/skylight/gc.rb
217
216
  - lib/skylight/helpers.rb
@@ -361,7 +360,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
361
360
  requirements:
362
361
  - - ">="
363
362
  - !ruby/object:Gem::Version
364
- version: '2.4'
363
+ version: '2.5'
365
364
  required_rubygems_version: !ruby/object:Gem::Requirement
366
365
  requirements:
367
366
  - - ">"
File without changes