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,49 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Skylight::Core
|
|
4
|
+
module Normalizers
|
|
5
|
+
# Normalizer for SQL requests
|
|
6
|
+
class SQL < Normalizer
|
|
7
|
+
CAT = "db.sql.query".freeze
|
|
8
|
+
|
|
9
|
+
# @param trace [Skylight::Messages::Trace::Builder] ignored, only present to match API
|
|
10
|
+
# @param name [String] ignored, only present to match API
|
|
11
|
+
# @param payload [Hash]
|
|
12
|
+
# @option payload [String] [:name] The SQL operation
|
|
13
|
+
# @option payload [Hash] [:binds] The bound parameters
|
|
14
|
+
# @return [Array]
|
|
15
|
+
def normalize(trace, name, payload)
|
|
16
|
+
case payload[:name]
|
|
17
|
+
when "SCHEMA".freeze, "CACHE".freeze
|
|
18
|
+
return :skip
|
|
19
|
+
else
|
|
20
|
+
name = CAT
|
|
21
|
+
title = payload[:name] || "SQL".freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
binds = payload[:binds]
|
|
25
|
+
|
|
26
|
+
if binds && !binds.empty?
|
|
27
|
+
binds = binds.map { |_col, val| val.inspect }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
extracted_title, sql = extract_binds(trace.instrumenter, payload, binds)
|
|
32
|
+
[ name, extracted_title || title, sql ]
|
|
33
|
+
rescue => e
|
|
34
|
+
# FIXME: Rust errors get written to STDERR and don't come through here
|
|
35
|
+
if config[:log_sql_parse_errors]
|
|
36
|
+
config.logger.warn "failed to extract binds in SQL; sql=#{payload[:sql].inspect}; exception=#{e.inspect}"
|
|
37
|
+
end
|
|
38
|
+
[ name, title, nil ]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def extract_binds(instrumenter, payload, _precalculated)
|
|
45
|
+
instrumenter.process_sql(payload[:sql])
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
require 'skylight/core/normalizers/default'
|
|
2
|
+
|
|
3
|
+
module Skylight::Core
|
|
4
|
+
# @api private
|
|
5
|
+
# Convert AS::N events to Skylight events
|
|
6
|
+
module Normalizers
|
|
7
|
+
|
|
8
|
+
DEFAULT = Default.new
|
|
9
|
+
|
|
10
|
+
def self.register(name, klass)
|
|
11
|
+
(@registry ||= {})[name] = klass
|
|
12
|
+
klass
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.build(config)
|
|
16
|
+
normalizers = {}
|
|
17
|
+
|
|
18
|
+
(@registry || {}).each do |k, klass|
|
|
19
|
+
unless klass.method_defined?(:normalize)
|
|
20
|
+
# TODO: Warn
|
|
21
|
+
next
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
normalizers[k] = klass.new(config)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Container.new(normalizers)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Normalizer
|
|
31
|
+
def self.register(name)
|
|
32
|
+
Normalizers.register(name, self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :config
|
|
36
|
+
|
|
37
|
+
def initialize(config)
|
|
38
|
+
@config = config
|
|
39
|
+
setup if respond_to?(:setup)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def normalize(trace, name, payload)
|
|
43
|
+
:skip
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def normalize_after(trace, span, name, payload)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Base Normalizer for Rails rendering
|
|
51
|
+
class RenderNormalizer < Normalizer
|
|
52
|
+
include Util::AllocationFree
|
|
53
|
+
|
|
54
|
+
def setup
|
|
55
|
+
@paths = config['normalizers.render.view_paths'] || []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Generic normalizer for renders
|
|
59
|
+
# @param category [String]
|
|
60
|
+
# @param payload [Hash]
|
|
61
|
+
# @option payload [String] :identifier
|
|
62
|
+
# @return [Array]
|
|
63
|
+
def normalize_render(category, payload)
|
|
64
|
+
if path = payload[:identifier]
|
|
65
|
+
title = relative_path(path)
|
|
66
|
+
path = nil if path == title
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
[ category, title, nil ]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def relative_path(path)
|
|
73
|
+
return path if relative_path?(path)
|
|
74
|
+
|
|
75
|
+
root = array_find(@paths) { |p| path.start_with?(p) }
|
|
76
|
+
type = :project
|
|
77
|
+
|
|
78
|
+
unless root
|
|
79
|
+
root = array_find(Gem.path) { |p| path.start_with?(p) }
|
|
80
|
+
type = :gem
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if root
|
|
84
|
+
start = root.size
|
|
85
|
+
start += 1 if path.getbyte(start) == SEPARATOR_BYTE
|
|
86
|
+
if type == :gem
|
|
87
|
+
"$GEM_PATH/#{path[start, path.size]}"
|
|
88
|
+
else
|
|
89
|
+
path[start, path.size]
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
"Absolute Path"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
def relative_path?(path)
|
|
98
|
+
!absolute_path?(path)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
SEPARATOR_BYTE = File::SEPARATOR.ord
|
|
102
|
+
|
|
103
|
+
if File.const_defined?(:NULL) ? File::NULL == "NUL" : RbConfig::CONFIG['host_os'] =~ /mingw|mswin32/
|
|
104
|
+
# This is a DOSish environment
|
|
105
|
+
ALT_SEPARATOR_BYTE = File::ALT_SEPARATOR && File::ALT_SEPARATOR.ord
|
|
106
|
+
COLON_BYTE = ":".ord
|
|
107
|
+
def absolute_path?(path)
|
|
108
|
+
if alpha?(path.getbyte(0)) && path.getbyte(1) == COLON_BYTE
|
|
109
|
+
byte2 = path.getbyte(2)
|
|
110
|
+
byte2 == SEPARATOR_BYTE || byte2 == ALT_SEPARATOR_BYTE
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def alpha?(byte)
|
|
115
|
+
byte >= 65 and byte <= 90 || byte >= 97 and byte <= 122
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
def absolute_path?(path)
|
|
119
|
+
path.getbyte(0) == SEPARATOR_BYTE
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class Container
|
|
125
|
+
def initialize(normalizers)
|
|
126
|
+
@normalizers = normalizers
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def keys
|
|
130
|
+
@normalizers.keys
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def normalize(trace, name, payload)
|
|
134
|
+
normalizer_for(name).normalize(trace, name, payload)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def normalize_after(trace, span, name, payload)
|
|
138
|
+
normalizer_for(name).normalize_after(trace, span, name, payload)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def normalizer_for(name)
|
|
142
|
+
@normalizers[name] || DEFAULT
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
%w( action_controller/process_action
|
|
147
|
+
action_controller/send_file
|
|
148
|
+
action_view/render_collection
|
|
149
|
+
action_view/render_partial
|
|
150
|
+
action_view/render_template
|
|
151
|
+
active_model_serializers/render
|
|
152
|
+
active_record/instantiation
|
|
153
|
+
active_record/sql
|
|
154
|
+
active_support/cache
|
|
155
|
+
coach/handler_finish
|
|
156
|
+
coach/middleware_finish
|
|
157
|
+
couch_potato/query
|
|
158
|
+
data_mapper/sql
|
|
159
|
+
elasticsearch/request
|
|
160
|
+
faraday/request
|
|
161
|
+
grape/endpoint
|
|
162
|
+
moped/query
|
|
163
|
+
sequel/sql).each do |file|
|
|
164
|
+
require "skylight/core/normalizers/#{file}"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# The following are not required by default as they are of dubious usefulness:
|
|
168
|
+
# - active_job/enqueue_at
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Skylight::Core
|
|
2
|
+
module Probes
|
|
3
|
+
module ActionController
|
|
4
|
+
class Probe
|
|
5
|
+
def install
|
|
6
|
+
::ActionController::Instrumentation.class_eval do
|
|
7
|
+
private
|
|
8
|
+
alias append_info_to_payload_without_sk append_info_to_payload
|
|
9
|
+
def append_info_to_payload(payload)
|
|
10
|
+
append_info_to_payload_without_sk(payload)
|
|
11
|
+
rendered_mime = begin
|
|
12
|
+
if respond_to?(:rendered_format)
|
|
13
|
+
rendered_format
|
|
14
|
+
elsif content_type.is_a?(Mime::Type)
|
|
15
|
+
content_type
|
|
16
|
+
elsif content_type.respond_to?(:to_s)
|
|
17
|
+
type_str = content_type.to_s.split(';').first
|
|
18
|
+
Mime::Type.lookup(type_str) unless type_str.blank?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
payload[:rendered_format] = rendered_mime.try(:ref)
|
|
22
|
+
payload[:variant] = request.respond_to?(:variant) ? request.variant : nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
register("ActionController::Instrumentation", "action_controller/metal/instrumentation", ActionController::Probe.new)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Skylight::Core
|
|
2
|
+
module Probes
|
|
3
|
+
module ActionView
|
|
4
|
+
class Probe
|
|
5
|
+
def install
|
|
6
|
+
::ActionView::TemplateRenderer.class_eval do
|
|
7
|
+
alias render_with_layout_without_sk render_with_layout
|
|
8
|
+
|
|
9
|
+
def render_with_layout(path, locals, *args, &block) #:nodoc:
|
|
10
|
+
layout = nil
|
|
11
|
+
|
|
12
|
+
if path
|
|
13
|
+
if method(:find_layout).arity == 3
|
|
14
|
+
# Rails 5
|
|
15
|
+
layout = find_layout(path, locals.keys, [formats.first])
|
|
16
|
+
else
|
|
17
|
+
# Rails 4
|
|
18
|
+
layout = find_layout(path, locals.keys)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if layout
|
|
23
|
+
instrument(:template, :identifier => layout.identifier) do
|
|
24
|
+
render_with_layout_without_sk(path, locals, *args, &block)
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
render_with_layout_without_sk(path, locals, *args, &block)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
register("ActionView::TemplateRenderer", "action_view", ActionView::Probe.new)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Skylight::Core
|
|
2
|
+
module Probes
|
|
3
|
+
module ActiveModelSerializers
|
|
4
|
+
class Probe
|
|
5
|
+
def install
|
|
6
|
+
version = nil
|
|
7
|
+
|
|
8
|
+
# File moved location between version
|
|
9
|
+
%w(serializer serializers).each do |dir|
|
|
10
|
+
begin
|
|
11
|
+
require "active_model/#{dir}/version"
|
|
12
|
+
rescue LoadError
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if defined?(::ActiveModel::Serializer::VERSION)
|
|
17
|
+
version = Gem::Version.new(::ActiveModel::Serializer::VERSION)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if !version || version < Gem::Version.new("0.5.0")
|
|
21
|
+
# Using $stderr here isn't great, but we don't have a logger accessible
|
|
22
|
+
$stderr.puts "[SKYLIGHT] [#{Skylight::Core::VERSION}] Instrumention is only available for " \
|
|
23
|
+
"ActiveModelSerializers version 0.5.0 and greater."
|
|
24
|
+
return
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# We don't actually support the RCs correctly, requires
|
|
28
|
+
# a release after 0.10.0.rc3
|
|
29
|
+
if version >= Gem::Version.new("0.10.0.rc1")
|
|
30
|
+
# AS::N is built in to newer versions
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# End users could override as_json without calling super, but it's likely safer
|
|
35
|
+
# than overriding serializable_array/hash/object.
|
|
36
|
+
|
|
37
|
+
[::ActiveModel::Serializer, ::ActiveModel::ArraySerializer].each do |klass|
|
|
38
|
+
klass.class_eval do
|
|
39
|
+
alias as_json_without_sk as_json
|
|
40
|
+
def as_json(*args)
|
|
41
|
+
payload = { serializer: self.class }
|
|
42
|
+
ActiveSupport::Notifications.instrument('render.active_model_serializers', payload) do
|
|
43
|
+
as_json_without_sk(*args)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
register("ActiveModel::Serializer", "active_model/serializer", ActiveModelSerializers::Probe.new)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Skylight::Core
|
|
2
|
+
module Probes
|
|
3
|
+
module Elasticsearch
|
|
4
|
+
class Probe
|
|
5
|
+
def install
|
|
6
|
+
::Elasticsearch::Transport::Transport::Base.class_eval do
|
|
7
|
+
alias perform_request_without_sk perform_request
|
|
8
|
+
def perform_request(method, path, *args, &block)
|
|
9
|
+
ActiveSupport::Notifications.instrument "request.elasticsearch",
|
|
10
|
+
name: 'Request',
|
|
11
|
+
method: method,
|
|
12
|
+
path: path do
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Prevent HTTP-related probes from firing
|
|
16
|
+
Skylight::Core::Normalizers::Faraday::Request.disable do
|
|
17
|
+
disable_skylight_probe(:NetHTTP) do
|
|
18
|
+
disable_skylight_probe(:HTTPClient) do
|
|
19
|
+
perform_request_without_sk(method, path, *args, &block)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def disable_skylight_probe(class_name, &block)
|
|
27
|
+
klass = Probes.const_get(class_name).const_get(:Probe) rescue nil
|
|
28
|
+
klass ? klass.disable(&block) : block.call
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
register("Elasticsearch", "elasticsearch", Elasticsearch::Probe.new)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'skylight/core/formatters/http'
|
|
2
|
+
|
|
3
|
+
module Skylight::Core
|
|
4
|
+
module Probes
|
|
5
|
+
module Excon
|
|
6
|
+
|
|
7
|
+
# Middleware for Excon that instruments requests
|
|
8
|
+
class Middleware < ::Excon::Middleware::Base
|
|
9
|
+
|
|
10
|
+
# This probably won't work since config isn't defined
|
|
11
|
+
include Util::Logging
|
|
12
|
+
|
|
13
|
+
def initialize(*)
|
|
14
|
+
@requests = {}
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# TODO:
|
|
19
|
+
# - Consider whether a LIFO queue would be sufficient
|
|
20
|
+
# - Check that errors can't be called without a request
|
|
21
|
+
|
|
22
|
+
def request_call(datum)
|
|
23
|
+
begin_instrumentation(datum)
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def response_call(datum)
|
|
28
|
+
super
|
|
29
|
+
ensure
|
|
30
|
+
end_instrumentation(datum)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def error_call(datum)
|
|
34
|
+
super
|
|
35
|
+
ensure
|
|
36
|
+
end_instrumentation(datum)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def begin_instrumentation(datum)
|
|
42
|
+
method = datum[:method].to_s
|
|
43
|
+
scheme = datum[:scheme]
|
|
44
|
+
host = datum[:host]
|
|
45
|
+
# TODO: Maybe don't show other default ports like 443
|
|
46
|
+
port = datum[:port] != 80 ? datum[:port] : nil
|
|
47
|
+
path = datum[:path]
|
|
48
|
+
query = datum[:query]
|
|
49
|
+
|
|
50
|
+
opts = Formatters::HTTP.build_opts(method, scheme, host, port, path, query)
|
|
51
|
+
|
|
52
|
+
@requests[datum.object_id] = Skylight::Core::Fanout.instrument(opts)
|
|
53
|
+
rescue Exception => e
|
|
54
|
+
error "failed to begin instrumentation for Excon; msg=%s", e.message
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def end_instrumentation(datum)
|
|
58
|
+
if request = @requests.delete(datum.object_id)
|
|
59
|
+
meta = { }
|
|
60
|
+
if datum[:error].is_a?(Exception)
|
|
61
|
+
meta[:exception_object] = datum[:error]
|
|
62
|
+
end
|
|
63
|
+
Skylight::Core::Fanout.done(request, meta)
|
|
64
|
+
end
|
|
65
|
+
rescue Exception => e
|
|
66
|
+
error "failed to end instrumentation for Excon; msg=%s", e.message
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Skylight::Core
|
|
2
|
+
module Probes
|
|
3
|
+
module Excon
|
|
4
|
+
# Probe for instrumenting Excon requests. Installs {Excon::Middleware} to achieve this.
|
|
5
|
+
class Probe
|
|
6
|
+
def install
|
|
7
|
+
if defined?(::Excon::Middleware)
|
|
8
|
+
# Don't require until installation since it depends on Excon being loaded
|
|
9
|
+
require 'skylight/core/probes/excon/middleware'
|
|
10
|
+
|
|
11
|
+
idx = ::Excon.defaults[:middlewares].index(::Excon::Middleware::Instrumentor)
|
|
12
|
+
|
|
13
|
+
# TODO: Handle possibility of idx being nil
|
|
14
|
+
::Excon.defaults[:middlewares].insert(idx, Probes::Excon::Middleware)
|
|
15
|
+
else
|
|
16
|
+
# Using $stderr here isn't great, but we don't have a logger accessible
|
|
17
|
+
$stderr.puts "[SKYLIGHT] [#{Skylight::Core::VERSION}] The installed version of Excon doesn't " \
|
|
18
|
+
"support Middlewares. The Excon probe will be disabled."
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
register("Excon", "excon", Excon::Probe.new)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Skylight::Core
|
|
2
|
+
module Probes
|
|
3
|
+
module Faraday
|
|
4
|
+
class Probe
|
|
5
|
+
def install
|
|
6
|
+
::Faraday::Connection.class_eval do
|
|
7
|
+
alias initialize_without_sk initialize
|
|
8
|
+
|
|
9
|
+
def initialize(*args, &block)
|
|
10
|
+
initialize_without_sk(*args, &block)
|
|
11
|
+
|
|
12
|
+
@builder.insert 0, ::Faraday::Request::Instrumentation
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
register("Faraday", "faraday", Faraday::Probe.new)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Skylight::Core
|
|
2
|
+
module Probes
|
|
3
|
+
module Grape
|
|
4
|
+
class Probe
|
|
5
|
+
def install
|
|
6
|
+
version = Gem::Version.new(::Grape::VERSION)
|
|
7
|
+
|
|
8
|
+
if version > Gem::Version.new("0.12.1")
|
|
9
|
+
# AS::N is built in to newer versions
|
|
10
|
+
return
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if version < Gem::Version.new("0.10.0")
|
|
14
|
+
# Using $stderr here isn't great, but we don't have a logger accessible
|
|
15
|
+
$stderr.puts "[SKYLIGHT] [#{Skylight::Core::VERSION}] The Grape probe only works with version 0.10.0+ " \
|
|
16
|
+
"and will be disabled."
|
|
17
|
+
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
::Grape::Endpoint.class_eval do
|
|
22
|
+
alias initialize_without_sk initialize
|
|
23
|
+
def initialize(*args, &block)
|
|
24
|
+
initialize_without_sk(*args, &block)
|
|
25
|
+
|
|
26
|
+
# This solution of wrapping the block is effective, but potentially fragile.
|
|
27
|
+
# A cleaner solution would be to call the original initialize with the already
|
|
28
|
+
# modified block. However, Grape does some odd stuff with the block binding
|
|
29
|
+
# that makes this difficult to reason about.
|
|
30
|
+
if original_block = @block
|
|
31
|
+
@block = lambda do |endpoint_instance|
|
|
32
|
+
ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: endpoint_instance) do
|
|
33
|
+
original_block.call(endpoint_instance)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
alias run_without_sk run
|
|
40
|
+
def run(*args)
|
|
41
|
+
ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self) do
|
|
42
|
+
run_without_sk(*args)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
alias run_filters_without_sk run_filters
|
|
47
|
+
def run_filters(filters)
|
|
48
|
+
# Unfortunately, the type isn't provided to the method so we have
|
|
49
|
+
# to try to guess it by looking at the contents. This is only reliable
|
|
50
|
+
# if the filters aren't empty.
|
|
51
|
+
if filters && !filters.empty?
|
|
52
|
+
type = case filters
|
|
53
|
+
when befores then :before
|
|
54
|
+
when before_validations then :before_validation
|
|
55
|
+
when after_validations then :after_validation
|
|
56
|
+
when afters then :after
|
|
57
|
+
else :other
|
|
58
|
+
end
|
|
59
|
+
else
|
|
60
|
+
type = :unknown
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
payload = {
|
|
64
|
+
endpoint: self,
|
|
65
|
+
filters: filters,
|
|
66
|
+
type: type
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', payload) do
|
|
70
|
+
run_filters_without_sk(filters)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
register("Grape::Endpoint", "grape/endpoint", Grape::Probe.new)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'skylight/core/formatters/http'
|
|
2
|
+
|
|
3
|
+
module Skylight::Core
|
|
4
|
+
module Probes
|
|
5
|
+
module HTTPClient
|
|
6
|
+
class Probe
|
|
7
|
+
DISABLED_KEY = :__skylight_httpclient_disabled
|
|
8
|
+
|
|
9
|
+
def self.disable
|
|
10
|
+
Thread.current[DISABLED_KEY] = true
|
|
11
|
+
yield
|
|
12
|
+
ensure
|
|
13
|
+
Thread.current[DISABLED_KEY] = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.disabled?
|
|
17
|
+
!!Thread.current[DISABLED_KEY]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def install
|
|
21
|
+
::HTTPClient.class_eval do
|
|
22
|
+
# HTTPClient has request methods on the class object itself,
|
|
23
|
+
# but the internally instantiate a client and perform the method
|
|
24
|
+
# on that, so this instance method override will cover both
|
|
25
|
+
# `HTTPClient.get(...)` and `HTTPClient.new.get(...)`
|
|
26
|
+
|
|
27
|
+
alias do_request_without_sk do_request
|
|
28
|
+
def do_request(method, uri, query, body, header, &block)
|
|
29
|
+
if Probes::HTTPClient::Probe.disabled?
|
|
30
|
+
return do_request_without_sk(method, uri, query, body, header, &block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
opts = Formatters::HTTP.build_opts(method, uri.scheme, uri.host, uri.port, uri.path, uri.query)
|
|
34
|
+
|
|
35
|
+
Skylight::Core::Fanout.instrument(opts) do
|
|
36
|
+
do_request_without_sk(method, uri, query, body, header, &block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end # class Probe
|
|
42
|
+
end # module Probes::HTTPClient
|
|
43
|
+
|
|
44
|
+
register("HTTPClient", "httpclient", HTTPClient::Probe.new)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Skylight::Core
|
|
2
|
+
module Probes
|
|
3
|
+
module Middleware
|
|
4
|
+
class Probe
|
|
5
|
+
def install
|
|
6
|
+
ActionDispatch::MiddlewareStack::Middleware.class_eval do
|
|
7
|
+
alias build_without_sk build
|
|
8
|
+
def build(*args)
|
|
9
|
+
sk_instrument_middleware(build_without_sk(*args))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def sk_instrument_middleware(middleware)
|
|
13
|
+
return middleware if middleware.is_a?(Skylight::Core::Middleware)
|
|
14
|
+
|
|
15
|
+
# Not sure how this would actually happen
|
|
16
|
+
return middleware if middleware.respond_to?(:call_without_sk)
|
|
17
|
+
|
|
18
|
+
# On Rails 3, ActionDispatch::Session::CookieStore is frozen, for one
|
|
19
|
+
return middleware if middleware.frozen?
|
|
20
|
+
|
|
21
|
+
middleware.instance_eval do
|
|
22
|
+
alias call_without_sk call
|
|
23
|
+
def call(*args, &block)
|
|
24
|
+
traces = Skylight::Core::Fanout.registered.map do |r|
|
|
25
|
+
r.instrumenter ? r.instrumenter.current_trace : nil
|
|
26
|
+
end.compact
|
|
27
|
+
|
|
28
|
+
return call_without_sk(*args, &block) if traces.empty?
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
name = self.class.name || "Anonymous Middleware"
|
|
32
|
+
|
|
33
|
+
traces.each{|t| t.endpoint = name }
|
|
34
|
+
|
|
35
|
+
spans = Skylight::Core::Fanout.instrument(title: name, category: "rack.middleware")
|
|
36
|
+
resp = call_without_sk(*args, &block)
|
|
37
|
+
|
|
38
|
+
Skylight::Core::Middleware.with_after_close(resp) do
|
|
39
|
+
Skylight::Core::Fanout.done(spans)
|
|
40
|
+
end
|
|
41
|
+
rescue Exception => e
|
|
42
|
+
# FIXME: Log this?
|
|
43
|
+
Skylight::Core::Fanout.done(spans, exception_object: e)
|
|
44
|
+
raise
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
middleware
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
register("ActionDispatch::MiddlewareStack::Middleware", "actionpack/action_dispatch", Middleware::Probe.new)
|
|
57
|
+
end
|
|
58
|
+
end
|