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.
- checksums.yaml +7 -0
- data/lib/skylight/core/config.rb +454 -0
- data/lib/skylight/core/errors.rb +6 -0
- data/lib/skylight/core/fanout.rb +44 -0
- data/lib/skylight/core/formatters/http.rb +23 -0
- data/lib/skylight/core/gc.rb +107 -0
- data/lib/skylight/core/instrumentable.rb +144 -0
- data/lib/skylight/core/instrumenter.rb +249 -0
- data/lib/skylight/core/middleware.rb +101 -0
- data/lib/skylight/core/normalizers/action_controller/process_action.rb +50 -0
- data/lib/skylight/core/normalizers/action_controller/send_file.rb +50 -0
- data/lib/skylight/core/normalizers/action_view/render_collection.rb +22 -0
- data/lib/skylight/core/normalizers/action_view/render_partial.rb +21 -0
- data/lib/skylight/core/normalizers/action_view/render_template.rb +21 -0
- data/lib/skylight/core/normalizers/active_job/enqueue_at.rb +21 -0
- data/lib/skylight/core/normalizers/active_model_serializers/render.rb +26 -0
- data/lib/skylight/core/normalizers/active_record/instantiation.rb +17 -0
- data/lib/skylight/core/normalizers/active_record/sql.rb +33 -0
- data/lib/skylight/core/normalizers/active_support/cache.rb +20 -0
- data/lib/skylight/core/normalizers/active_support/cache_clear.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_decrement.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_delete.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_exist.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_fetch_hit.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_generate.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_increment.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_read.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_read_multi.rb +16 -0
- data/lib/skylight/core/normalizers/active_support/cache_write.rb +16 -0
- data/lib/skylight/core/normalizers/coach/handler_finish.rb +36 -0
- data/lib/skylight/core/normalizers/coach/middleware_finish.rb +23 -0
- data/lib/skylight/core/normalizers/couch_potato/query.rb +20 -0
- data/lib/skylight/core/normalizers/data_mapper/sql.rb +12 -0
- data/lib/skylight/core/normalizers/default.rb +27 -0
- data/lib/skylight/core/normalizers/elasticsearch/request.rb +20 -0
- data/lib/skylight/core/normalizers/faraday/request.rb +37 -0
- data/lib/skylight/core/normalizers/grape/endpoint.rb +30 -0
- data/lib/skylight/core/normalizers/grape/endpoint_render.rb +26 -0
- data/lib/skylight/core/normalizers/grape/endpoint_run.rb +33 -0
- data/lib/skylight/core/normalizers/grape/endpoint_run_filters.rb +23 -0
- data/lib/skylight/core/normalizers/moped/query.rb +100 -0
- data/lib/skylight/core/normalizers/sequel/sql.rb +12 -0
- data/lib/skylight/core/normalizers/sql.rb +49 -0
- data/lib/skylight/core/normalizers.rb +170 -0
- data/lib/skylight/core/probes/action_controller.rb +31 -0
- data/lib/skylight/core/probes/action_view.rb +37 -0
- data/lib/skylight/core/probes/active_model_serializers.rb +55 -0
- data/lib/skylight/core/probes/elasticsearch.rb +37 -0
- data/lib/skylight/core/probes/excon/middleware.rb +72 -0
- data/lib/skylight/core/probes/excon.rb +26 -0
- data/lib/skylight/core/probes/faraday.rb +22 -0
- data/lib/skylight/core/probes/grape.rb +80 -0
- data/lib/skylight/core/probes/httpclient.rb +46 -0
- data/lib/skylight/core/probes/middleware.rb +58 -0
- data/lib/skylight/core/probes/mongo.rb +171 -0
- data/lib/skylight/core/probes/mongoid.rb +21 -0
- data/lib/skylight/core/probes/moped.rb +39 -0
- data/lib/skylight/core/probes/net_http.rb +64 -0
- data/lib/skylight/core/probes/redis.rb +71 -0
- data/lib/skylight/core/probes/sequel.rb +33 -0
- data/lib/skylight/core/probes/sinatra.rb +69 -0
- data/lib/skylight/core/probes/tilt.rb +27 -0
- data/lib/skylight/core/probes.rb +129 -0
- data/lib/skylight/core/railtie.rb +166 -0
- data/lib/skylight/core/subscriber.rb +124 -0
- data/lib/skylight/core/test.rb +98 -0
- data/lib/skylight/core/trace.rb +190 -0
- data/lib/skylight/core/user_config.rb +61 -0
- data/lib/skylight/core/util/allocation_free.rb +26 -0
- data/lib/skylight/core/util/clock.rb +56 -0
- data/lib/skylight/core/util/deploy.rb +132 -0
- data/lib/skylight/core/util/gzip.rb +21 -0
- data/lib/skylight/core/util/inflector.rb +112 -0
- data/lib/skylight/core/util/logging.rb +127 -0
- data/lib/skylight/core/util/platform.rb +77 -0
- data/lib/skylight/core/util/proxy.rb +13 -0
- data/lib/skylight/core/util.rb +14 -0
- data/lib/skylight/core/vendor/active_support/notifications.rb +207 -0
- data/lib/skylight/core/vendor/active_support/per_thread_registry.rb +52 -0
- data/lib/skylight/core/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
- data/lib/skylight/core/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
- data/lib/skylight/core/vendor/thread_safe.rb +126 -0
- data/lib/skylight/core/version.rb +6 -0
- data/lib/skylight/core/vm/gc.rb +70 -0
- data/lib/skylight/core.rb +99 -0
- 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
|