skylight-core 2.0.0.beta1

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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/lib/skylight/core/config.rb +454 -0
  3. data/lib/skylight/core/errors.rb +6 -0
  4. data/lib/skylight/core/fanout.rb +44 -0
  5. data/lib/skylight/core/formatters/http.rb +23 -0
  6. data/lib/skylight/core/gc.rb +107 -0
  7. data/lib/skylight/core/instrumentable.rb +144 -0
  8. data/lib/skylight/core/instrumenter.rb +249 -0
  9. data/lib/skylight/core/middleware.rb +101 -0
  10. data/lib/skylight/core/normalizers/action_controller/process_action.rb +50 -0
  11. data/lib/skylight/core/normalizers/action_controller/send_file.rb +50 -0
  12. data/lib/skylight/core/normalizers/action_view/render_collection.rb +22 -0
  13. data/lib/skylight/core/normalizers/action_view/render_partial.rb +21 -0
  14. data/lib/skylight/core/normalizers/action_view/render_template.rb +21 -0
  15. data/lib/skylight/core/normalizers/active_job/enqueue_at.rb +21 -0
  16. data/lib/skylight/core/normalizers/active_model_serializers/render.rb +26 -0
  17. data/lib/skylight/core/normalizers/active_record/instantiation.rb +17 -0
  18. data/lib/skylight/core/normalizers/active_record/sql.rb +33 -0
  19. data/lib/skylight/core/normalizers/active_support/cache.rb +20 -0
  20. data/lib/skylight/core/normalizers/active_support/cache_clear.rb +16 -0
  21. data/lib/skylight/core/normalizers/active_support/cache_decrement.rb +16 -0
  22. data/lib/skylight/core/normalizers/active_support/cache_delete.rb +16 -0
  23. data/lib/skylight/core/normalizers/active_support/cache_exist.rb +16 -0
  24. data/lib/skylight/core/normalizers/active_support/cache_fetch_hit.rb +16 -0
  25. data/lib/skylight/core/normalizers/active_support/cache_generate.rb +16 -0
  26. data/lib/skylight/core/normalizers/active_support/cache_increment.rb +16 -0
  27. data/lib/skylight/core/normalizers/active_support/cache_read.rb +16 -0
  28. data/lib/skylight/core/normalizers/active_support/cache_read_multi.rb +16 -0
  29. data/lib/skylight/core/normalizers/active_support/cache_write.rb +16 -0
  30. data/lib/skylight/core/normalizers/coach/handler_finish.rb +36 -0
  31. data/lib/skylight/core/normalizers/coach/middleware_finish.rb +23 -0
  32. data/lib/skylight/core/normalizers/couch_potato/query.rb +20 -0
  33. data/lib/skylight/core/normalizers/data_mapper/sql.rb +12 -0
  34. data/lib/skylight/core/normalizers/default.rb +27 -0
  35. data/lib/skylight/core/normalizers/elasticsearch/request.rb +20 -0
  36. data/lib/skylight/core/normalizers/faraday/request.rb +37 -0
  37. data/lib/skylight/core/normalizers/grape/endpoint.rb +30 -0
  38. data/lib/skylight/core/normalizers/grape/endpoint_render.rb +26 -0
  39. data/lib/skylight/core/normalizers/grape/endpoint_run.rb +33 -0
  40. data/lib/skylight/core/normalizers/grape/endpoint_run_filters.rb +23 -0
  41. data/lib/skylight/core/normalizers/moped/query.rb +100 -0
  42. data/lib/skylight/core/normalizers/sequel/sql.rb +12 -0
  43. data/lib/skylight/core/normalizers/sql.rb +49 -0
  44. data/lib/skylight/core/normalizers.rb +170 -0
  45. data/lib/skylight/core/probes/action_controller.rb +31 -0
  46. data/lib/skylight/core/probes/action_view.rb +37 -0
  47. data/lib/skylight/core/probes/active_model_serializers.rb +55 -0
  48. data/lib/skylight/core/probes/elasticsearch.rb +37 -0
  49. data/lib/skylight/core/probes/excon/middleware.rb +72 -0
  50. data/lib/skylight/core/probes/excon.rb +26 -0
  51. data/lib/skylight/core/probes/faraday.rb +22 -0
  52. data/lib/skylight/core/probes/grape.rb +80 -0
  53. data/lib/skylight/core/probes/httpclient.rb +46 -0
  54. data/lib/skylight/core/probes/middleware.rb +58 -0
  55. data/lib/skylight/core/probes/mongo.rb +171 -0
  56. data/lib/skylight/core/probes/mongoid.rb +21 -0
  57. data/lib/skylight/core/probes/moped.rb +39 -0
  58. data/lib/skylight/core/probes/net_http.rb +64 -0
  59. data/lib/skylight/core/probes/redis.rb +71 -0
  60. data/lib/skylight/core/probes/sequel.rb +33 -0
  61. data/lib/skylight/core/probes/sinatra.rb +69 -0
  62. data/lib/skylight/core/probes/tilt.rb +27 -0
  63. data/lib/skylight/core/probes.rb +129 -0
  64. data/lib/skylight/core/railtie.rb +166 -0
  65. data/lib/skylight/core/subscriber.rb +124 -0
  66. data/lib/skylight/core/test.rb +98 -0
  67. data/lib/skylight/core/trace.rb +190 -0
  68. data/lib/skylight/core/user_config.rb +61 -0
  69. data/lib/skylight/core/util/allocation_free.rb +26 -0
  70. data/lib/skylight/core/util/clock.rb +56 -0
  71. data/lib/skylight/core/util/deploy.rb +132 -0
  72. data/lib/skylight/core/util/gzip.rb +21 -0
  73. data/lib/skylight/core/util/inflector.rb +112 -0
  74. data/lib/skylight/core/util/logging.rb +127 -0
  75. data/lib/skylight/core/util/platform.rb +77 -0
  76. data/lib/skylight/core/util/proxy.rb +13 -0
  77. data/lib/skylight/core/util.rb +14 -0
  78. data/lib/skylight/core/vendor/active_support/notifications.rb +207 -0
  79. data/lib/skylight/core/vendor/active_support/per_thread_registry.rb +52 -0
  80. data/lib/skylight/core/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
  81. data/lib/skylight/core/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
  82. data/lib/skylight/core/vendor/thread_safe.rb +126 -0
  83. data/lib/skylight/core/version.rb +6 -0
  84. data/lib/skylight/core/vm/gc.rb +70 -0
  85. data/lib/skylight/core.rb +99 -0
  86. metadata +254 -0
@@ -0,0 +1,171 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Mongo
4
+ CAT = "db.mongo.command".freeze
5
+
6
+ class Probe
7
+ def install
8
+ ::Mongo::Monitoring::Global.subscribe(::Mongo::Monitoring::COMMAND, Subscriber.new)
9
+ end
10
+ end
11
+
12
+ class Subscriber
13
+ include Util::Logging
14
+
15
+ COMMANDS = [:insert, :find, :count, :distinct, :update, :findandmodify, :delete].freeze
16
+
17
+ COMMAND_NAMES = {
18
+ findandmodify: 'findAndModify'.freeze
19
+ }.freeze
20
+
21
+ def initialize
22
+ @events = {}
23
+ end
24
+
25
+ def started(event)
26
+ begin_instrumentation(event)
27
+ end
28
+
29
+ def succeeded(event)
30
+ end_instrumentation(event)
31
+ end
32
+
33
+ def failed(event)
34
+ end_instrumentation(event)
35
+ end
36
+
37
+ # For logging
38
+ def config
39
+ # Just log to the first instrumentable's config for now.
40
+ # FIXME: Revisit this
41
+ if (instrumentable = Fanout.registered.first)
42
+ if (instrumenter = instrumentable.instrumenter)
43
+ instrumenter.config
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def begin_instrumentation(event)
51
+ return unless COMMANDS.include?(event.command_name.to_sym)
52
+
53
+ command_name = COMMAND_NAMES[event.command_name] || event.command_name
54
+
55
+ title = "#{event.database_name}.#{command_name}"
56
+
57
+ command = event.command
58
+
59
+ # Not sure if this will always exist
60
+ # Delete so the description will be less redundant
61
+ if target = command[event.command_name]
62
+ title << " #{target}"
63
+ end
64
+
65
+ payload = {}
66
+
67
+ # Ruby Hashes are ordered based on insertion so do the most important ones first
68
+
69
+ add_value('key'.freeze, command, payload)
70
+ add_bound('query'.freeze, command, payload)
71
+ add_bound('filter'.freeze, command, payload)
72
+ add_value('sort'.freeze, command, payload)
73
+
74
+ if event.command_name == :findandmodify
75
+ add_bound('update'.freeze, command, payload)
76
+ end
77
+
78
+ add_value('remove'.freeze, command, payload)
79
+ add_value('new'.freeze, command, payload)
80
+
81
+ if updates = command['updates'.freeze]
82
+ # AFAICT the gem generally just sends one item in the updates array
83
+ update = updates[0]
84
+ update_payload = {}
85
+ add_bound('q'.freeze, update, update_payload)
86
+ add_bound('u'.freeze, update, update_payload)
87
+ add_value('multi'.freeze, update, update_payload)
88
+ add_value('upsert'.freeze, update, update_payload)
89
+
90
+ payload['updates'.freeze] = [update_payload]
91
+
92
+ if updates.length > 1
93
+ payload['updates'.freeze] << '...'
94
+ end
95
+ end
96
+
97
+ if deletes = command['deletes'.freeze]
98
+ # AFAICT the gem generally just sends one item in the updates array
99
+ delete = deletes[0]
100
+ delete_payload = {}
101
+ add_bound('q'.freeze, delete, delete_payload)
102
+ add_value('limit'.freeze, delete, delete_payload)
103
+
104
+ payload['deletes'.freeze] = [delete_payload]
105
+
106
+ if deletes.length > 1
107
+ payload['deletes'.freeze] << '...'
108
+ end
109
+ end
110
+
111
+
112
+ # We're ignoring documents from insert because they could have completely inconsistent
113
+ # format which would make it hard to merge.
114
+
115
+ opts = {
116
+ category: CAT,
117
+ title: title,
118
+ description: payload.empty? ? nil : payload.to_json,
119
+ meta: { database: event.database_name }
120
+ }
121
+
122
+ @events[event.operation_id] = Skylight::Core::Fanout.instrument(opts)
123
+ rescue Exception => e
124
+ error "failed to begin instrumentation for Mongo; msg=%s", e.message
125
+ end
126
+
127
+ def end_instrumentation(event)
128
+ if original_event = @events.delete(event.operation_id)
129
+ meta = {}
130
+ if event.is_a?(::Mongo::Monitoring::Event::CommandFailed)
131
+ meta[:exception] = ["CommandFailed", event.message]
132
+ end
133
+ Skylight::Core::Fanout.done(original_event, meta)
134
+ end
135
+ rescue Exception => e
136
+ error "failed to end instrumentation for Mongo; msg=%s", e.message
137
+ end
138
+
139
+ def add_value(key, command, payload)
140
+ if command.has_key?(key)
141
+ value = command[key]
142
+ payload[key] = value
143
+ end
144
+ end
145
+
146
+ def add_bound(key, command, payload)
147
+ if value = command[key]
148
+ payload[key] = extract_binds(value)
149
+ end
150
+ end
151
+
152
+ def extract_binds(hash)
153
+ ret = {}
154
+
155
+ hash.each do |k,v|
156
+ if v.is_a?(Hash)
157
+ ret[k] = extract_binds(v)
158
+ else
159
+ ret[k] = '?'.freeze
160
+ end
161
+ end
162
+
163
+ ret
164
+ end
165
+
166
+ end
167
+ end
168
+
169
+ register("Mongo", "mongo", Mongo::Probe.new)
170
+ end
171
+ end
@@ -0,0 +1,21 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Mongoid
4
+ class Probe
5
+
6
+ def install
7
+ require 'mongoid/version'
8
+ version = Gem::Version.new(::Mongoid::VERSION)
9
+
10
+ if version < Gem::Version.new("5.0")
11
+ require 'skylight/core/probes/moped'
12
+ else
13
+ require 'skylight/core/probes/mongo'
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ register("Mongoid", "mongoid", Mongoid::Probe.new)
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Moped
4
+ class Probe
5
+
6
+ def install
7
+ unless defined?(::Moped::Instrumentable)
8
+ # Using $stderr here isn't great, but we don't have a logger accessible
9
+ $stderr.puts "[SKYLIGHT] [#{Skylight::Core::VERSION}] The installed version of Moped doesn't " \
10
+ "support instrumentation. The Moped probe will be disabled."
11
+
12
+ return
13
+ end
14
+
15
+ ::Moped::Instrumentable.module_eval do
16
+ alias instrument_without_sk instrument
17
+
18
+ def instrument(*args, &block)
19
+ # Mongoid sets the instrumenter to AS::N
20
+ if instrumenter == ActiveSupport::Notifications
21
+ asn_block = block
22
+ else
23
+ # If the instrumenter hasn't been changed to AS::N use both
24
+ asn_block = Proc.new do
25
+ ActiveSupport::Notifications.instrument(*args, &block)
26
+ end
27
+ end
28
+
29
+ instrument_without_sk(*args, &asn_block)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+
37
+ register("Moped", "moped", Moped::Probe.new)
38
+ end
39
+ end
@@ -0,0 +1,64 @@
1
+ require 'skylight/core/formatters/http'
2
+
3
+ module Skylight::Core
4
+ module Probes
5
+ module NetHTTP
6
+ # Probe for instrumenting Net::HTTP requests. Works by monkeypatching the default Net::HTTP#request method.
7
+ class Probe
8
+ DISABLED_KEY = :__skylight_net_http_disabled
9
+
10
+ def self.disable
11
+ Thread.current[DISABLED_KEY] = true
12
+ yield
13
+ ensure
14
+ Thread.current[DISABLED_KEY] = false
15
+ end
16
+
17
+ def self.disabled?
18
+ !!Thread.current[DISABLED_KEY]
19
+ end
20
+
21
+ def install
22
+ Net::HTTP.class_eval do
23
+ alias request_without_sk request
24
+
25
+ def request(req, body = nil, &block)
26
+ if !started? || Probes::NetHTTP::Probe.disabled?
27
+ return request_without_sk(req, body, &block)
28
+ end
29
+
30
+ method = req.method
31
+
32
+ # req['host'] also includes special handling for default ports
33
+ host, port = req['host'] ? req['host'].split(':') : nil
34
+
35
+ # If we're connected with a persistent socket
36
+ host ||= self.address
37
+ port ||= self.port
38
+
39
+ path = req.path
40
+ scheme = use_ssl? ? "https" : "http"
41
+
42
+ # Contained in the path
43
+ query = nil
44
+
45
+ opts = Formatters::HTTP.build_opts(method, scheme, host, port, path, query)
46
+
47
+ Skylight::Core::Fanout.instrument(opts) do |spans|
48
+ spans.each do |(instrumentable, span)|
49
+ # TODO: Should we make something more generic?
50
+ if (header = instrumentable.correlation_header)
51
+ req[header] = instrumentable.span_correlation_header(span)
52
+ end
53
+ end
54
+ request_without_sk(req, body, &block)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ register("Net::HTTP", "net/http", NetHTTP::Probe.new)
63
+ end
64
+ end
@@ -0,0 +1,71 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Redis
4
+ class Probe
5
+ def install
6
+ version = defined?(::Redis::VERSION) ? Gem::Version.new(::Redis::VERSION) : nil
7
+
8
+ if !version || version < Gem::Version.new("3.0.0")
9
+ # Using $stderr here isn't great, but we don't have a logger accessible
10
+ $stderr.puts "[SKYLIGHT] [#{Skylight::Core::VERSION}] The installed version of Redis doesn't " \
11
+ "support Middlewares. At least version 3.0.0 is required."
12
+ return
13
+ end
14
+
15
+ ::Redis::Client.class_eval do
16
+ alias call_without_sk call
17
+
18
+ def call(command, &block)
19
+ command_name = command[0]
20
+
21
+ return call_without_sk(command, &block) if command_name == :auth
22
+
23
+ opts = {
24
+ category: "db.redis.command",
25
+ title: command_name.upcase.to_s
26
+ }
27
+
28
+ Skylight::Core::Fanout.instrument(opts) do
29
+ call_without_sk(command, &block)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # Unfortunately, because of the nature of pipelining, there's no way for us to
36
+ # give a time breakdown on the individual items.
37
+
38
+ PIPELINED_OPTS = {
39
+ category: "db.redis.pipelined".freeze,
40
+ title: "PIPELINE".freeze
41
+ }.freeze
42
+
43
+ MULTI_OPTS = {
44
+ category: "db.redis.multi".freeze,
45
+ title: "MULTI".freeze
46
+ }.freeze
47
+
48
+ ::Redis.class_eval do
49
+ alias pipelined_without_sk pipelined
50
+
51
+ def pipelined(&block)
52
+ Skylight::Core::Fanout.instrument(PIPELINED_OPTS) do
53
+ pipelined_without_sk(&block)
54
+ end
55
+ end
56
+
57
+
58
+ alias multi_without_sk multi
59
+
60
+ def multi(&block)
61
+ Skylight::Core::Fanout.instrument(MULTI_OPTS) do
62
+ multi_without_sk(&block)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ register("Redis", "redis", Redis::Probe.new)
70
+ end
71
+ end
@@ -0,0 +1,33 @@
1
+ # Supports 3.12.0+
2
+ module Skylight::Core
3
+ module Probes
4
+ module Sequel
5
+ class Probe
6
+ def install
7
+ require 'sequel/database/logging'
8
+
9
+ method_name = ::Sequel::Database.method_defined?(:log_connection_yield) ? 'log_connection_yield' : 'log_yield'
10
+
11
+ ::Sequel::Database.class_eval <<-end_eval
12
+ alias #{method_name}_without_sk #{method_name}
13
+
14
+ def #{method_name}(sql, *args, &block)
15
+ #{method_name}_without_sk(sql, *args) do
16
+ ::ActiveSupport::Notifications.instrument(
17
+ "sql.sequel",
18
+ sql: sql,
19
+ name: "SQL",
20
+ binds: args
21
+ ) do
22
+ block.call
23
+ end
24
+ end
25
+ end
26
+ end_eval
27
+ end
28
+ end
29
+ end
30
+
31
+ register("Sequel", "sequel", Sequel::Probe.new)
32
+ end
33
+ end
@@ -0,0 +1,69 @@
1
+ module Skylight::Core
2
+ module Probes
3
+ module Sinatra
4
+ class Probe
5
+ def install
6
+ if ::Sinatra::VERSION < '1.4.0'
7
+ # Using $stderr here isn't great, but we don't have a logger accessible
8
+ $stderr.puts "[SKYLIGHT] [#{Skylight::VERSION}] Sinatra must be version 1.4.0 or greater."
9
+ return
10
+ end
11
+
12
+ class << ::Sinatra::Base
13
+ alias compile_without_sk! compile!
14
+
15
+ def compile!(verb, path, *args, &block)
16
+ compile_without_sk!(verb, path, *args, &block).tap do |_, _, keys_or_wrapper, wrapper|
17
+ wrapper ||= keys_or_wrapper
18
+
19
+ # Deal with the situation where the path is a regex, and the default behavior
20
+ # of Ruby stringification produces an unreadable mess
21
+ if path.is_a?(Regexp)
22
+ human_readable = "<sk-regex>%r{#{path.source}}</sk-regex>"
23
+ wrapper.instance_variable_set(:@route_name, "#{verb} #{human_readable}")
24
+ else
25
+ wrapper.instance_variable_set(:@route_name, "#{verb} #{path}")
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ ::Sinatra::Base.class_eval do
32
+ alias dispatch_without_sk! dispatch!
33
+ alias compile_template_without_sk compile_template
34
+
35
+ def dispatch!(*args, &block)
36
+ dispatch_without_sk!(*args, &block).tap do
37
+ Skylight::Core::Fanout.registered.each do |target|
38
+ instrumenter = target.instrumenter
39
+ next unless instrumenter
40
+ trace = instrumenter.current_trace
41
+ next unless trace
42
+
43
+ # Set the endpoint name to the route name
44
+ route = env['sinatra.route']
45
+ trace.endpoint = route if route
46
+ end
47
+ end
48
+ end
49
+
50
+ def compile_template(engine, data, options, *args, &block)
51
+ # Pass along a useful "virtual path" to Tilt. The Tilt probe will handle
52
+ # instrumenting correctly.
53
+ case data
54
+ when Symbol
55
+ options[:sky_virtual_path] = data.to_s
56
+ else
57
+ options[:sky_virtual_path] = "Inline template (#{engine})"
58
+ end
59
+
60
+ compile_template_without_sk(engine, data, options, *args, &block)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ register("Sinatra::Base", "sinatra/base", Sinatra::Probe.new)
68
+ end
69
+ end
@@ -0,0 +1,27 @@
1
+ # Should support 0.2+, though not tested against older versions
2
+ module Skylight::Core
3
+ module Probes
4
+ module Tilt
5
+ class Probe
6
+ def install
7
+ ::Tilt::Template.class_eval do
8
+ alias render_without_sk render
9
+
10
+ def render(*args, &block)
11
+ opts = {
12
+ category: "view.render.template",
13
+ title: options[:sky_virtual_path] || "Unknown template name"
14
+ }
15
+
16
+ Skylight::Core::Fanout.instrument(opts) do
17
+ render_without_sk(*args, &block)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ register("Tilt::Template", "tilt/template", Tilt::Probe.new)
26
+ end
27
+ end
@@ -0,0 +1,129 @@
1
+ module Skylight::Core
2
+ # @api private
3
+ module Probes
4
+
5
+ @@available = nil
6
+
7
+ def self.available
8
+ unless @@available
9
+ root = File.expand_path("../probes", __FILE__)
10
+ @@available = {}
11
+ Dir["#{root}/*.rb"].each do |f|
12
+ name = File.basename(f, '.rb')
13
+ @@available[name] = "skylight/core/probes/#{name}"
14
+ end
15
+ end
16
+ @@available
17
+ end
18
+
19
+ def self.probe(*probes)
20
+ unknown = probes.map(&:to_s) - available.keys
21
+ unless unknown.empty?
22
+ raise ArgumentError, "unknown probes: #{unknown.join(', ')}"
23
+ end
24
+
25
+ probes.each do |p|
26
+ require available[p.to_s]
27
+ end
28
+ end
29
+
30
+ class ProbeRegistration
31
+ attr_reader :klass_name, :require_paths, :probe
32
+
33
+ def initialize(klass_name, require_paths, probe)
34
+ @klass_name = klass_name
35
+ @require_paths = Array(require_paths)
36
+ @probe = probe
37
+ end
38
+
39
+ def install
40
+ probe.install
41
+ end
42
+ end
43
+
44
+ def self.require_hooks
45
+ @require_hooks ||= {}
46
+ end
47
+
48
+ def self.installed
49
+ @installed ||= {}
50
+ end
51
+
52
+ def self.is_available?(klass_name)
53
+ !!Util::Inflector.safe_constantize(klass_name)
54
+ end
55
+
56
+ def self.register(*args)
57
+ registration = ProbeRegistration.new(*args)
58
+
59
+ if is_available?(registration.klass_name)
60
+ installed[registration.klass_name] ||= []
61
+ installed[registration.klass_name] << registration
62
+ registration.install
63
+ else
64
+ register_require_hook(registration)
65
+ end
66
+ end
67
+
68
+ def self.require_hook(require_path)
69
+ registrations = lookup_by_require_path(require_path)
70
+ return unless registrations
71
+
72
+ registrations.each do |registration|
73
+ # Double check constant is available
74
+ if is_available?(registration.klass_name)
75
+ installed[registration.klass_name] ||= []
76
+ installed[registration.klass_name] << registration
77
+ registration.install
78
+
79
+ # Don't need this to be called again
80
+ unregister_require_hook(registration)
81
+ end
82
+ end
83
+ end
84
+
85
+ def self.register_require_hook(registration)
86
+ registration.require_paths.each do |p|
87
+ require_hooks[p] ||= []
88
+ require_hooks[p] << registration
89
+ end
90
+ end
91
+
92
+ def self.unregister_require_hook(registration)
93
+ registration.require_paths.each do |p|
94
+ require_hooks[p].delete(registration)
95
+ require_hooks.delete(p) if require_hook[p].empty?
96
+ end
97
+ end
98
+
99
+ def self.lookup_by_require_path(require_path)
100
+ require_hooks[require_path]
101
+ end
102
+ end
103
+ end
104
+
105
+ # Allow hooking require
106
+ # @api private
107
+ module ::Kernel
108
+ alias require_without_sk require
109
+
110
+ def require(name)
111
+ ret = require_without_sk(name)
112
+
113
+ begin
114
+ Skylight::Core::Probes.require_hook(name)
115
+ rescue Exception
116
+ # FIXME: Log these errors
117
+ end
118
+
119
+ ret
120
+ rescue LoadError
121
+ # Support pre-2.0 style requires
122
+ if name =~ %r{^skylight/probes/(.+)}
123
+ warn "[DEPRECATION] Requiring Skylight probes by path is deprecated. Use `Skylight.probe(:#{$1})` instead."
124
+ require "skylight/core/probes/#{$1}"
125
+ else
126
+ raise
127
+ end
128
+ end
129
+ end