skylight 5.0.0.beta → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -6
  3. data/CONTRIBUTING.md +1 -1
  4. data/ext/extconf.rb +2 -2
  5. data/ext/libskylight.yml +7 -5
  6. data/lib/skylight.rb +9 -2
  7. data/lib/skylight/api.rb +3 -0
  8. data/lib/skylight/cli/doctor.rb +11 -13
  9. data/lib/skylight/config.rb +25 -32
  10. data/lib/skylight/deprecation.rb +3 -1
  11. data/lib/skylight/errors.rb +4 -4
  12. data/lib/skylight/extensions.rb +8 -0
  13. data/lib/skylight/extensions/source_location.rb +123 -81
  14. data/lib/skylight/formatters/http.rb +2 -1
  15. data/lib/skylight/helpers.rb +44 -35
  16. data/lib/skylight/instrumenter.rb +3 -2
  17. data/lib/skylight/middleware.rb +4 -4
  18. data/lib/skylight/native.rb +1 -1
  19. data/lib/skylight/native_ext_fetcher.rb +2 -2
  20. data/lib/skylight/normalizers.rb +6 -4
  21. data/lib/skylight/normalizers/action_controller/process_action.rb +1 -1
  22. data/lib/skylight/normalizers/action_dispatch/route_set.rb +1 -1
  23. data/lib/skylight/normalizers/active_job/perform.rb +5 -0
  24. data/lib/skylight/normalizers/graphql/base.rb +1 -0
  25. data/lib/skylight/normalizers/render.rb +1 -1
  26. data/lib/skylight/normalizers/shrine.rb +34 -0
  27. data/lib/skylight/normalizers/sql.rb +3 -2
  28. data/lib/skylight/probes.rb +38 -10
  29. data/lib/skylight/probes/active_job.rb +4 -6
  30. data/lib/skylight/probes/active_job_enqueue.rb +18 -14
  31. data/lib/skylight/probes/active_model_serializers.rb +2 -6
  32. data/lib/skylight/probes/delayed_job.rb +112 -25
  33. data/lib/skylight/probes/elasticsearch.rb +1 -1
  34. data/lib/skylight/probes/excon/middleware.rb +4 -4
  35. data/lib/skylight/probes/middleware.rb +2 -1
  36. data/lib/skylight/probes/mongo.rb +2 -1
  37. data/lib/skylight/probes/net_http.rb +0 -1
  38. data/lib/skylight/probes/redis.rb +6 -3
  39. data/lib/skylight/railtie.rb +1 -1
  40. data/lib/skylight/sidekiq.rb +12 -7
  41. data/lib/skylight/subscriber.rb +1 -1
  42. data/lib/skylight/trace.rb +10 -4
  43. data/lib/skylight/util/deploy.rb +3 -6
  44. data/lib/skylight/util/instrumenter_method.rb +11 -11
  45. data/lib/skylight/util/logging.rb +6 -6
  46. data/lib/skylight/util/lru_cache.rb +1 -3
  47. data/lib/skylight/util/platform.rb +1 -1
  48. data/lib/skylight/version.rb +1 -1
  49. metadata +27 -13
  50. 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: 66c646baeaeddece5a75140908f8c7d1aec447945903ff5681c1e4de58839e79
4
+ data.tar.gz: a3fe10a6b05963038f40f4b82d77cd545c42fa546d63cd440a21eb3839280b71
5
5
  SHA512:
6
- metadata.gz: dc3445e94e0b3c5dc4936569df35f60b7ce9ac5f82492f92c247aca1cb44f9570092b11f532d28a6d795b39d2a65eae4bac16b5ca1a9c4e7b1ae2b47eb6c87ff
7
- data.tar.gz: c591bb63cd56354d0954955f93720f1183afb0f71bd6c0fd2df7cfb8f32dab2a4988eaa20fe3018615cbb47531163b09dddd1c849f430989ffb589f98b03f7c5
6
+ metadata.gz: ca6b42cfa51a39f1cccfc8f169a9bdc1560ec9774423c1ca335ec6fc26bd70d47f40c62d9e645ea626b00626e962a9ef3dbda49c693de0a56fef10bc04ab1d76
7
+ data.tar.gz: ccb18fe7cc713c03544db2a0d1dc5c42d86fe5ee14c0db11e208884203b7141cd39032bdb75f5c7eaf341b1ded6e9c761e8529e1bf50d1c499b1bab85a545fcd
data/CHANGELOG.md CHANGED
@@ -1,18 +1,38 @@
1
- ## 5.0.0.beta
2
- * [BREAKING] Merge skylight-core into skylight. All classes previously namespaced under `Skylight::Core` have been moved to `Skylight`.
3
- * [BREAKING] Remove `Skylight::Util::Inflector`
4
- * [BREAKING] Drop support for Rails 4
5
- * [BREAKING] Drop support for Rails 2.3
1
+ ## 5.0.0 (March 5, 2021)
2
+
3
+ * [FEATURE] Add normalizer for Shrine events (thanks @janko!)
4
+ * [FEATURE] Source Locations detection and reporting is now enabled by default (can be disabled with `SKYLIGHT_ENABLE_SOURCE_LOCATIONS=false`)
5
+ * [FEATURE] Configuration for the Source Locations caches via `SYLIGHT_SOURCE_LOCATION_CACHE_SIZE`
6
+
7
+ * [IMPROVEMENT] Improve keyword argument handling in Skylight::Helpers (thanks @lukebooth!)
8
+ * [IMPROVEMENT] Replace a Kernel.puts with Skylight.log (thanks @johnnyshields!)
9
+ * [IMPROVEMENT] Various updates to the SQL lexer
10
+ * [IMPROVEMENT] Reduce volume of log messages sent to the native logger in debug level
11
+ * [IMPROVEMENT] Optimizations for the Source Locations extension
12
+ * [IMPROVEMENT] Improved Delayed::Job probe
6
13
  * [IMPROVEMENT] Maintain method visibility when instrumenting with `instrument_method`
7
14
  * [IMPROVEMENT] Update probes to use `Module#prepend` where possible
8
15
  * [IMPROVEMENT] New tokio-based skylightd
9
16
  * [IMPROVEMENT] Support `render_layout` notifications in Rails 6.1
10
17
  * [IMPROVEMENT] Support `ActionMailer::MailDeliveryJob` in Rails 6.1
11
- * [IMPROVEMENT] Better logging config
18
+ * [IMPROVEMENT] Better logging config. `SKYLIGHT_NATIVE_LOG_LEVEL` now defaults to `warn`.
19
+
20
+ * [BREAKING] Rename `environment` keyword argument to `priority_key`. Note `env` has not changed.
21
+ * [BREAKING] Drop support for Ruby 2.4
22
+ * [BREAKING] Merge skylight-core into skylight. All classes previously namespaced under `Skylight::Core` have been moved to `Skylight`.
23
+ * [BREAKING] Remove `Skylight::Util::Inflector`
24
+ * [BREAKING] Drop support for Rails 4
25
+ * [BREAKING] Drop support for Ruby 2.3
26
+
27
+ * [BUGFIX] Fix issue with missing metadata in MongoDB probe
28
+ * [BUGFIX] Resolve an inability to parse certain SQL queries containing arrays
12
29
  * [BUGFIX] Allow multiple probes to be registered under the same key
13
30
  * [BUGFIX] Do not refer to Redis constant until the probe is installed
14
31
  * [BUGFIX] Fix nested calls to `Normalizers::Faraday::Request.disable`
15
32
 
33
+ ## 4.3.2 (December 14, 2020)
34
+ * [BUGFIX] Backport an ActionView fix from Skylight 5 (makes Skylight 4 compatible with Rails 6.1)
35
+
16
36
  ## 4.3.1 (June 24, 2020)
17
37
  * [BUGFIX] Fix an issue in which `Mime::NullType` would result in an exception
18
38
 
data/CONTRIBUTING.md CHANGED
@@ -16,7 +16,7 @@ If you prefer to run tests in your own environment, you may do so as follows:
16
16
 
17
17
  ```shell
18
18
  # Select a gemfile and bundle install
19
- export BUNDLE_GEMFILE=$PWD/gemfiles/Gemfile.rails-5.2.x
19
+ export BUNDLE_GEMFILE=$PWD/gemfiles/rails-5.2.x/Gemfile
20
20
  bundle install
21
21
  # Run the test suite (takes 5-10 minutes)
22
22
  bundle exec rspec
data/ext/extconf.rb CHANGED
@@ -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:
data/ext/libskylight.yml CHANGED
@@ -1,6 +1,8 @@
1
- version: "5.0.0-45b02dd"
1
+ # commit: ea59cc7bdbbee0f69d1cf7e69827974a9ea67645
2
+ ---
3
+ version: "5.0.0-488d432"
2
4
  checksums:
3
- x86-linux: "218a51683cbe3f6e7f1d31097a7e732c6bac3e3780a707312f8441dd45d8f3a6"
4
- x86_64-linux: "2426968fef8cd7fe0fd0ea582fd8098db78025ce936bac9d72b7753995bd1ea9"
5
- x86_64-linux-musl: "ca07748d43fac0b4a05b38b5ce0fc4cb7e43469a183919c5d9980a8b2e0b01ab"
6
- x86_64-darwin: "8b19eb2ffd19f78ee6e547b37fcf5cad2e49aa9a8bc2b9615a791ccfc91c2552"
5
+ x86-linux: "3c16b6db1508f35720258551783fbcd30fd231638bad316ea76748d659838399"
6
+ x86_64-linux: "94383aa3359c3f2e9c0e3c16ee263d46c5673dd255f8842e6acadf5ec3131c06"
7
+ x86_64-linux-musl: "d2e2e2e61c321315f9bcaa157426f33aef8ffc2330ba46b2cdcbd9442e65f728"
8
+ x86_64-darwin: "bcc925d0bcbae83a484f35dbc9729dcf262e1f3a8c29b8d387d0f58ad8f3afa3"
data/lib/skylight.rb CHANGED
@@ -101,7 +101,7 @@ module Skylight
101
101
  message: e.message, klass: e.class)]
102
102
  end
103
103
 
104
- if config&.respond_to?("log_#{level}") && config&.respond_to?(:log_trace)
104
+ if config.respond_to?("log_#{level}") && config.respond_to?(:log_trace)
105
105
  config.send("log_#{level}", message)
106
106
  config.log_trace e.backtrace.join("\n")
107
107
  else
@@ -194,10 +194,17 @@ module Skylight
194
194
  opts = {}
195
195
  end
196
196
 
197
+ # NOTE: unless we have `:internal` (indicating a built-in Skylight instrument block),
198
+ # or we already have a `source_file` or `source_line` (probably set by `instrument_method`),
199
+ # we set the caller location to the second item on the stack
200
+ # (immediate caller of the `instrument` method).
201
+ unless opts[:source_file] || opts[:source_line] || opts[:internal]
202
+ opts = opts.merge(sk_instrument_location: caller_locations(1..1).first)
203
+ end
204
+
197
205
  meta ||= {}
198
206
 
199
207
  instrumenter.extensions.process_instrument_options(opts, meta)
200
-
201
208
  instrumenter.instrument(category, title, desc, meta, &block)
202
209
  end
203
210
 
data/lib/skylight/api.rb CHANGED
@@ -9,8 +9,11 @@ module Skylight
9
9
  attr_reader :config
10
10
 
11
11
  class Error < StandardError; end
12
+
12
13
  class Unauthorized < Error; end
14
+
13
15
  class Conflict < Error; end
16
+
14
17
  class CreateFailed < Error
15
18
  attr_reader :res
16
19
 
@@ -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"
@@ -44,6 +44,7 @@ module Skylight
44
44
  -"LOG_LEVEL" => :log_level,
45
45
  -"ALERT_LOG_FILE" => :alert_log_file,
46
46
  -"NATIVE_LOG_FILE" => :native_log_file,
47
+ -"NATIVE_LOG_LEVEL" => :native_log_level,
47
48
  -"LOG_SQL_PARSE_ERRORS" => :log_sql_parse_errors,
48
49
 
49
50
  # == Proxy ==
@@ -110,7 +111,8 @@ module Skylight
110
111
  -"HEROKU_DYNO_INFO_PATH" => :'heroku.dyno_info_path',
111
112
 
112
113
  # == Source Location ==
113
- -"SOURCE_LOCATION_IGNORED_GEMS" => :source_location_ignored_gems
114
+ -"SOURCE_LOCATION_IGNORED_GEMS" => :source_location_ignored_gems,
115
+ -"SOURCE_LOCATION_CACHE_SIZE" => :source_location_cache_size
114
116
  }.freeze
115
117
 
116
118
  KEY_TO_NATIVE_ENV = {
@@ -119,9 +121,7 @@ module Skylight
119
121
  native_log_level: "LOG_LEVEL"
120
122
  }.freeze
121
123
 
122
- SERVER_VALIDATE = %i[
123
- enable_source_locations
124
- ].freeze
124
+ SERVER_VALIDATE = %i[].freeze
125
125
 
126
126
  DEFAULT_IGNORED_SOURCE_LOCATION_GEMS = [
127
127
  -"skylight",
@@ -145,12 +145,13 @@ module Skylight
145
145
  log_level: -"INFO",
146
146
  alert_log_file: -"-",
147
147
  log_sql_parse_errors: true,
148
+ native_log_level: -"warn",
148
149
 
149
150
  # Features
150
151
  enable_segments: true,
151
152
  enable_sidekiq: false,
152
153
  sinatra_route_prefixes: false,
153
- enable_source_locations: false,
154
+ enable_source_locations: true,
154
155
 
155
156
  # Deploys
156
157
  'heroku.dyno_info_path': -"/etc/heroku/dyno",
@@ -243,7 +244,7 @@ module Skylight
243
244
  end
244
245
 
245
246
  # @api private
246
- attr_reader :environment
247
+ attr_reader :priority_key
247
248
 
248
249
  # @api private
249
250
  def initialize(*args)
@@ -253,16 +254,16 @@ module Skylight
253
254
  attrs = args.pop.dup
254
255
  end
255
256
 
256
- @values = {}
257
+ @values = {}
257
258
  @priority = {}
258
- @regexp = nil
259
+ @priority_regexp = nil
259
260
  @alert_logger = nil
260
261
  @logger = nil
261
262
 
262
263
  p = attrs.delete(:priority)
263
264
 
264
- if (@environment = args[0])
265
- @regexp = /^#{Regexp.escape(@environment)}\.(.+)$/
265
+ if (@priority_key = args[0])
266
+ @priority_regexp = /^#{Regexp.escape(priority_key)}\.(.+)$/
266
267
  end
267
268
 
268
269
  attrs.each do |k, v|
@@ -277,7 +278,8 @@ module Skylight
277
278
  def self.load(opts = {}, env = ENV)
278
279
  attrs = {}
279
280
  path = opts.delete(:file)
280
- environment = opts.delete(:environment)
281
+ priority_key = opts.delete(:priority_key)
282
+ priority_key ||= opts[:env] # if a priority_key is not given, use env if available
281
283
 
282
284
  if path
283
285
  error = nil
@@ -295,11 +297,14 @@ module Skylight
295
297
  raise ConfigError, "could not load config file; msg=#{error}" if error
296
298
  end
297
299
 
300
+ # The key-value pairs in this `priority` option are inserted into the
301
+ # config's @priority hash *after* anything listed under priority_key;
302
+ # i.e., ENV takes precendence over priority_key
298
303
  if env
299
304
  attrs[:priority] = remap_env(env)
300
305
  end
301
306
 
302
- config = new(environment, attrs)
307
+ config = new(priority_key, attrs)
303
308
 
304
309
  opts.each do |k, v|
305
310
  config[k] = v
@@ -439,7 +444,7 @@ module Skylight
439
444
  end
440
445
  end
441
446
 
442
- if @regexp && k =~ @regexp
447
+ if @priority_regexp && k =~ @priority_regexp
443
448
  @priority[$1.to_sym] = val
444
449
  end
445
450
 
@@ -549,23 +554,13 @@ module Skylight
549
554
  when /^warn$/i then Logger::WARN
550
555
  when /^error$/i then Logger::ERROR
551
556
  when /^fatal$/i then Logger::FATAL
552
- else Logger::ERROR
557
+ else Logger::ERROR # rubocop:disable Lint/DuplicateBranch
553
558
  end
554
559
  end
555
560
  end
556
561
 
557
562
  def native_log_level
558
- @native_log_level ||=
559
- if trace?
560
- "trace"
561
- else
562
- case log_level
563
- when Logger::DEBUG then "debug"
564
- when Logger::INFO then "info"
565
- when Logger::WARN then "warn"
566
- else "error"
567
- end
568
- end
563
+ get(:native_log_level).to_s.downcase
569
564
  end
570
565
 
571
566
  def logger
@@ -586,7 +581,7 @@ module Skylight
586
581
  end
587
582
  end
588
583
 
589
- attr_writer :logger
584
+ attr_writer :logger, :alert_logger
590
585
 
591
586
  def alert_logger
592
587
  @alert_logger ||= MUTEX.synchronize do
@@ -601,8 +596,6 @@ module Skylight
601
596
  end
602
597
  end
603
598
 
604
- attr_writer :alert_logger
605
-
606
599
  def enable_segments?
607
600
  !!get(:enable_segments)
608
601
  end
@@ -638,13 +631,13 @@ module Skylight
638
631
 
639
632
  Logger.new(out, progname: "Skylight", level: level)
640
633
  rescue
641
- Logger.new(STDOUT, progname: "Skylight", level: level)
634
+ Logger.new($stdout, progname: "Skylight", level: level)
642
635
  end
643
636
 
644
637
  def load_logger
645
638
  unless (l = @logger)
646
639
  out = get(:log_file)
647
- out = STDOUT if out == "-"
640
+ out = $stdout if out == "-"
648
641
  l = create_logger(out, level: log_level)
649
642
  end
650
643
 
@@ -690,7 +683,7 @@ module Skylight
690
683
  corrected_config = res.corrected_config
691
684
 
692
685
  # Use defaults if no corrected config is available. This will happen if the request failed.
693
- corrected_config ||= Hash[SERVER_VALIDATE.map { |k| [k, self.class.default_values.fetch(k)] }]
686
+ corrected_config ||= SERVER_VALIDATE.map { |k| [k, self.class.default_values.fetch(k)] }.to_h
694
687
 
695
688
  config_to_update = corrected_config.reject { |k, v| get(k) == v }
696
689
  unless config_to_update.empty?
@@ -700,7 +693,7 @@ module Skylight
700
693
 
701
694
  # This is a weird way to handle priorities
702
695
  # See https://github.com/tildeio/direwolf-agent/issues/275
703
- k = "#{environment}.#{k}" if environment
696
+ k = "#{priority_key}.#{k}" if priority_key
704
697
 
705
698
  set(k, v)
706
699
  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
@@ -13,10 +13,10 @@ module Skylight
13
13
  end
14
14
 
15
15
  Skylight.module_eval <<-RUBY, __FILE__, __LINE__ + 1
16
- class #{name}Error < NativeError
17
- def self.code; #{code}; end
18
- def self.message; #{message.to_json}; end
19
- end
16
+ class #{name}Error < NativeError # class SqlLexError < NativeError
17
+ def self.code; #{code}; end # def self.code; 4; end
18
+ def self.message; #{message.to_json}; end # def self.message; "Failed to lex SQL query."; end
19
+ end # end
20
20
  RUBY
21
21
 
22
22
  klass = Skylight.const_get("#{name}Error")
@@ -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
@@ -11,24 +11,46 @@ module Skylight
11
11
  include Util::Logging
12
12
 
13
13
  META_KEYS = %i[source_location source_file source_line].freeze
14
+ MAX_CALLER_DEPTH = 75
14
15
 
15
16
  def initialize(*)
16
17
  super
17
- @caller_cache = Util::LruCache.new(100)
18
- @instance_method_source_location_cache = Util::LruCache.new(100)
18
+ cache_size = (config[:source_location_cache_size] || 1000).to_i
19
+ @caller_cache = Util::LruCache.new(cache_size)
20
+ @instance_method_source_location_cache = Util::LruCache.new(cache_size)
19
21
  gem_require_trie # memoize this at startup
20
22
  end
21
23
 
24
+ def process_trace_meta(meta)
25
+ unless meta[:source_location] || meta[:source_file]
26
+ warn "Ignoring source_line without source_file" if meta[:source_line]
27
+ if (location = find_caller)
28
+ meta[:source_file] = location.absolute_path
29
+ meta[:source_line] = location.lineno
30
+ end
31
+ end
32
+ end
33
+
22
34
  def process_instrument_options(opts, meta)
23
35
  source_location = opts[:source_location] || opts[:meta]&.[](:source_location)
24
36
  source_file = opts[:source_file] || opts[:meta]&.[](:source_file)
25
37
  source_line = opts[:source_line] || opts[:meta]&.[](:source_line)
38
+ source_name_hint, const_name, method_name = opts[:source_location_hint] ||
39
+ opts[:meta]&.[](:source_location_hint)
40
+ instrument_location = opts[:sk_instrument_location]
26
41
 
27
42
  if source_location
28
43
  meta[:source_location] = source_location
44
+ elsif source_name_hint
45
+ source_location = dispatch_hinted_source_location(source_name_hint, const_name, method_name)
46
+ meta[:source_file], meta[:source_line] = source_location
47
+ meta.delete(:source_location_hint) if source_location
29
48
  elsif source_file
30
49
  meta[:source_file] = source_file
31
50
  meta[:source_line] = source_line
51
+ elsif instrument_location && project_path?(instrument_location.absolute_path)
52
+ meta[:source_file] = instrument_location.absolute_path
53
+ meta[:source_line] = instrument_location.lineno
32
54
  else
33
55
  warn "Ignoring source_line without source_file" if source_line
34
56
  if (location = find_caller(cache_key: opts.hash))
@@ -41,14 +63,21 @@ module Skylight
41
63
  end
42
64
 
43
65
  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
66
+ if opts[:source_location] && (opts[:source_file] || opts[:source_line])
67
+ warn "Found both source_location and source_file or source_line in normalizer\n" \
68
+ " location=#{opts[:source_location]}; file=#{opts[:source_file]}; line=#{opts[:source_line]}"
69
+ end
70
+
71
+ sl =
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
+ )
78
+ elsif opts[:source_file]
79
+ [opts[:source_file], opts[:source_line]]
80
+ end
52
81
 
53
82
  sl ||= source_location(payload, meta, cache_key: opts[:cache_key])
54
83
 
@@ -91,86 +120,92 @@ module Skylight
91
120
 
92
121
  protected
93
122
 
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
123
+ def dispatch_hinted_source_location(source_name, const_name, method_name)
124
+ return unless const_name && method_name
97
125
 
98
- instance_method_source_location(const_name, method_name, source_name: source_name)
99
- end
126
+ instance_method_source_location(const_name, method_name, source_name: source_name)
127
+ end
100
128
 
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]
129
+ # from normalizers.rb
130
+ # Returns an array of file and line
131
+ def source_location(payload, meta, cache_key: nil)
132
+ # FIXME: what should precedence be?
133
+ if meta.is_a?(Hash) && meta[:source_location]
134
+ meta.delete(:source_location)
135
+ elsif payload.is_a?(Hash) && payload[:sk_source_location]
136
+ payload[:sk_source_location]
137
+ elsif (location = find_caller(cache_key: cache_key))
138
+ [location.absolute_path, location.lineno]
139
+ end
111
140
  end
112
- end
113
141
 
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
142
+ def find_caller(cache_key: nil)
143
+ # starting at 4 to skip Skylight extension processing logic
144
+ locations = ::Kernel.caller_locations(4..MAX_CALLER_DEPTH)
145
+
146
+ if cache_key
147
+ localized_cache_key = [cache_key, locations.map(&:lineno)].hash
148
+ @caller_cache.fetch(localized_cache_key) { find_caller_inner(locations) }
149
+ else
150
+ find_caller_inner(locations)
151
+ end
119
152
  end
120
- end
121
153
 
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/")
154
+ def project_path?(path)
155
+ return false unless path
129
156
 
130
- # So it must be a project file
131
- true
132
- end
157
+ # Must be in the project root
158
+ return false unless path.start_with?(config.root.to_s)
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/")
133
163
 
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
164
+ # So it must be a project file
165
+ true
166
+ end
167
+
168
+ def instance_method_source_location(constant_name, method_name, source_name: :instance_method)
169
+ @instance_method_source_location_cache.fetch([constant_name, method_name, source_name]) do
170
+ if (constant = ::ActiveSupport::Dependencies.safe_constantize(constant_name))
171
+ if constant.instance_methods.include?(:"before_instrument_#{method_name}")
172
+ method_name = :"before_instrument_#{method_name}"
173
+ end
174
+ begin
175
+ unbound_method = case source_name
176
+ when :instance_method
177
+ find_instance_method(constant, method_name)
178
+ when :own_instance_method
179
+ find_own_instance_method(constant, method_name)
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
153
190
  end
154
191
  end
155
192
  end
156
- end
157
193
 
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
194
+ def sanitize_source_location(path, line)
195
+ # Do this first since gems may be vendored in the app repo. However, it might be slower.
196
+ # Should we cache matches?
197
+ if (gem_name = find_source_gem(path))
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
205
+ end
171
206
 
172
- line ? "#{path}:#{line}" : path
173
- end
207
+ line ? "#{path}:#{line}" : path
208
+ end
174
209
 
175
210
  private
176
211
 
@@ -198,21 +233,25 @@ module Skylight
198
233
  end
199
234
 
200
235
  def find_source_gem(path)
236
+ return nil unless path
237
+
201
238
  trie = gem_require_trie
202
239
 
203
240
  path.split(File::SEPARATOR).each do |segment|
204
241
  trie = trie[segment]
205
- return unless trie
242
+ break unless trie
206
243
  return trie[:name] if trie[:name]
207
244
  end
208
245
 
209
246
  nil
210
247
  end
211
248
 
212
- def find_caller_inner
249
+ def find_caller_inner(locations)
213
250
  # Start at file before this one
214
- caller_locations(1).find do |l|
215
- find_source_gem(l.absolute_path) || project_path?(l.absolute_path)
251
+ # NOTE: We could start farther back now to avoid more Skylight files
252
+ locations.find do |l|
253
+ absolute_path = l.absolute_path
254
+ find_source_gem(absolute_path) || project_path?(absolute_path)
216
255
  end
217
256
  end
218
257
 
@@ -244,6 +283,9 @@ module Skylight
244
283
  constant.instance_method(method_name)
245
284
  end
246
285
 
286
+ def find_class_method(constant, method_name)
287
+ constant.method(method_name)
288
+ end
247
289
  end
248
290
  end
249
291
  end