skylight 4.2.3 → 5.3.0
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 +4 -4
- data/CHANGELOG.md +420 -331
- data/CLA.md +1 -1
- data/CONTRIBUTING.md +2 -8
- data/ERRORS.md +3 -0
- data/LICENSE.md +7 -17
- data/README.md +1 -1
- data/ext/extconf.rb +61 -56
- data/ext/libskylight.yml +8 -6
- data/ext/skylight_native.c +26 -100
- data/lib/skylight/api.rb +32 -21
- data/lib/skylight/cli/doctor.rb +64 -65
- data/lib/skylight/cli/helpers.rb +19 -19
- data/lib/skylight/cli/merger.rb +142 -138
- data/lib/skylight/cli.rb +48 -46
- data/lib/skylight/config.rb +640 -201
- data/lib/skylight/data/cacert.pem +730 -1023
- data/lib/skylight/deprecation.rb +17 -0
- data/lib/skylight/errors.rb +26 -9
- data/lib/skylight/extensions/source_location.rb +291 -0
- data/lib/skylight/extensions.rb +95 -0
- data/lib/skylight/formatters/http.rb +18 -0
- data/lib/skylight/gc.rb +99 -0
- data/lib/skylight/helpers.rb +81 -36
- data/lib/skylight/instrumenter.rb +336 -18
- data/lib/skylight/middleware.rb +147 -1
- data/lib/skylight/native.rb +60 -12
- data/lib/skylight/native_ext_fetcher.rb +13 -14
- data/lib/skylight/normalizers/action_controller/process_action.rb +68 -0
- data/lib/skylight/normalizers/action_controller/send_file.rb +51 -0
- data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
- data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
- data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
- data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
- data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
- data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
- data/lib/skylight/normalizers/active_job/perform.rb +87 -0
- data/lib/skylight/normalizers/active_model_serializers/render.rb +32 -0
- data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
- data/lib/skylight/normalizers/active_record/sql.rb +20 -0
- data/lib/skylight/normalizers/active_storage.rb +28 -0
- data/lib/skylight/normalizers/active_support/cache.rb +11 -0
- data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
- data/lib/skylight/normalizers/coach/handler_finish.rb +44 -0
- data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
- data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
- data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
- data/lib/skylight/normalizers/default.rb +24 -0
- data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
- data/lib/skylight/normalizers/faraday/request.rb +38 -0
- data/lib/skylight/normalizers/grape/endpoint.rb +28 -0
- data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
- data/lib/skylight/normalizers/grape/endpoint_run.rb +39 -0
- data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +20 -0
- data/lib/skylight/normalizers/grape/format_response.rb +20 -0
- data/lib/skylight/normalizers/graphiti/render.rb +22 -0
- data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
- data/lib/skylight/normalizers/graphql/base.rb +127 -0
- data/lib/skylight/normalizers/render.rb +79 -0
- data/lib/skylight/normalizers/sequel/sql.rb +12 -0
- data/lib/skylight/normalizers/shrine.rb +32 -0
- data/lib/skylight/normalizers/sql.rb +41 -0
- data/lib/skylight/normalizers.rb +157 -0
- data/lib/skylight/probes/action_controller.rb +52 -0
- data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
- data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
- data/lib/skylight/probes/action_dispatch.rb +2 -0
- data/lib/skylight/probes/action_view.rb +42 -0
- data/lib/skylight/probes/active_job.rb +27 -0
- data/lib/skylight/probes/active_job_enqueue.rb +35 -0
- data/lib/skylight/probes/active_model_serializers.rb +50 -0
- data/lib/skylight/probes/active_record_async.rb +96 -0
- data/lib/skylight/probes/delayed_job.rb +144 -0
- data/lib/skylight/probes/elasticsearch.rb +36 -0
- data/lib/skylight/probes/excon/middleware.rb +65 -0
- data/lib/skylight/probes/excon.rb +25 -0
- data/lib/skylight/probes/faraday.rb +23 -0
- data/lib/skylight/probes/graphql.rb +38 -0
- data/lib/skylight/probes/httpclient.rb +44 -0
- data/lib/skylight/probes/middleware.rb +135 -0
- data/lib/skylight/probes/mongo.rb +156 -0
- data/lib/skylight/probes/mongoid.rb +13 -0
- data/lib/skylight/probes/net_http.rb +54 -0
- data/lib/skylight/probes/rack_builder.rb +37 -0
- data/lib/skylight/probes/redis.rb +51 -0
- data/lib/skylight/probes/sequel.rb +29 -0
- data/lib/skylight/probes/sinatra.rb +66 -0
- data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
- data/lib/skylight/probes/tilt.rb +25 -0
- data/lib/skylight/probes.rb +173 -0
- data/lib/skylight/railtie.rb +166 -28
- data/lib/skylight/sidekiq.rb +47 -0
- data/lib/skylight/sinatra.rb +1 -1
- data/lib/skylight/subscriber.rb +130 -0
- data/lib/skylight/test.rb +147 -0
- data/lib/skylight/trace.rb +325 -22
- data/lib/skylight/user_config.rb +58 -0
- data/lib/skylight/util/allocation_free.rb +26 -0
- data/lib/skylight/util/clock.rb +57 -0
- data/lib/skylight/util/component.rb +22 -22
- data/lib/skylight/util/deploy.rb +19 -24
- data/lib/skylight/util/gzip.rb +20 -0
- data/lib/skylight/util/http.rb +106 -113
- data/lib/skylight/util/instrumenter_method.rb +26 -0
- data/lib/skylight/util/logging.rb +136 -0
- data/lib/skylight/util/lru_cache.rb +36 -0
- data/lib/skylight/util/platform.rb +3 -7
- data/lib/skylight/util/ssl.rb +1 -25
- data/lib/skylight/util.rb +12 -0
- data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
- data/lib/skylight/version.rb +5 -1
- data/lib/skylight/vm/gc.rb +60 -0
- data/lib/skylight.rb +201 -14
- metadata +134 -18
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Probes
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module FutureResult
|
|
5
|
+
# Applied to ActiveSupport::Notifications::Event
|
|
6
|
+
module AsyncEventExtensions
|
|
7
|
+
# Notify Skylight that the event has started
|
|
8
|
+
def __sk_start!
|
|
9
|
+
subscriber = Skylight.instrumenter.subscriber
|
|
10
|
+
subscriber.start(name, nil, payload)
|
|
11
|
+
trace = Skylight.instrumenter.current_trace
|
|
12
|
+
|
|
13
|
+
# Set a finisher to end the event
|
|
14
|
+
@__sk_finisher = ->(name, payload) do
|
|
15
|
+
subscriber.with_trace(trace) { subscriber.finish(name, nil, payload) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# End it immediately if we've actually already ended
|
|
19
|
+
__sk_finish! if @end
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
Skylight.error("Unable to start event for FutureResult: #{e}")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Notify Skylight that the event has finished
|
|
25
|
+
def __sk_finish!
|
|
26
|
+
return unless @__sk_finisher
|
|
27
|
+
|
|
28
|
+
@__sk_finisher.call(name, payload)
|
|
29
|
+
@__sk_finisher = nil
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
Skylight.error("Unable to finish event for FutureResult: #{e}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# When the event is marked as finish make sure we notify Skylight
|
|
35
|
+
def finish!
|
|
36
|
+
super
|
|
37
|
+
__sk_finish!
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Applied to FutureResult
|
|
42
|
+
module Instrumentation
|
|
43
|
+
def result(*, **)
|
|
44
|
+
# This instruments the whole FutureResult so that we know when we were executing (potenially) async.
|
|
45
|
+
ActiveSupport::Notifications.instrument("future_result.active_record", { args: @args, kwargs: @kwargs }) do
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def execute_or_wait(*, **)
|
|
53
|
+
# At this point we're actually waiting for the query to have finished executing.
|
|
54
|
+
|
|
55
|
+
begin
|
|
56
|
+
# If the query has already started async, the @event_buffer will be defined.
|
|
57
|
+
# We grab the events (currently only the SQL queries), extend them with our
|
|
58
|
+
# special methods and notify Skylight.
|
|
59
|
+
# We act as if the event has just stared, though the query may already have been
|
|
60
|
+
# running. This means we're essentially just logging blocking time right now.
|
|
61
|
+
|
|
62
|
+
# Dup here just in case more get added somehow during the super call
|
|
63
|
+
events = @event_buffer&.instance_variable_get(:@events).dup
|
|
64
|
+
|
|
65
|
+
events&.each do |event|
|
|
66
|
+
event.singleton_class.prepend(AsyncEventExtensions)
|
|
67
|
+
event.__sk_start!
|
|
68
|
+
end
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
Skylight.error("Unable to start events for FutureResult: #{e}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
super
|
|
74
|
+
ensure
|
|
75
|
+
# Once we've actually got a result, we mark each one as finished.
|
|
76
|
+
# Note that it may have already finished, but if it didn't we need to say so now.
|
|
77
|
+
events&.reverse_each(&:__sk_finish!)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class Probe
|
|
82
|
+
def install
|
|
83
|
+
::ActiveRecord::FutureResult.prepend(Instrumentation)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
register(
|
|
90
|
+
:active_record_async,
|
|
91
|
+
"ActiveRecord::FutureResult",
|
|
92
|
+
"active_record/future_result",
|
|
93
|
+
ActiveRecord::FutureResult::Probe.new
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
5
|
+
module Skylight
|
|
6
|
+
module Probes
|
|
7
|
+
module DelayedJob
|
|
8
|
+
begin
|
|
9
|
+
require "delayed/plugin"
|
|
10
|
+
|
|
11
|
+
class Plugin < ::Delayed::Plugin
|
|
12
|
+
callbacks do |lifecycle|
|
|
13
|
+
lifecycle.around(:perform) { |worker, job, &block| sk_instrument(worker, job, &block) }
|
|
14
|
+
|
|
15
|
+
lifecycle.after(:error) { |_worker, _job| Skylight.trace&.segment = "error" }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
include Skylight::Util::Logging
|
|
20
|
+
|
|
21
|
+
def sk_instrument(_worker, job)
|
|
22
|
+
endpoint = Skylight::Probes::DelayedJob.handler_name(job)
|
|
23
|
+
|
|
24
|
+
Skylight.trace(
|
|
25
|
+
endpoint,
|
|
26
|
+
"app.delayed_job.worker",
|
|
27
|
+
"Delayed::Worker#run",
|
|
28
|
+
component: :worker,
|
|
29
|
+
segment: job.queue,
|
|
30
|
+
meta: {
|
|
31
|
+
source_location: "delayed_job"
|
|
32
|
+
}
|
|
33
|
+
) do
|
|
34
|
+
t { "Delayed::Job beginning trace" }
|
|
35
|
+
yield
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
rescue LoadError
|
|
41
|
+
$stderr.puts "[SKYLIGHT] The delayed_job probe was requested, but Delayed::Plugin was not defined."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
UNKNOWN = "<Delayed::Job Unknown>"
|
|
45
|
+
|
|
46
|
+
def self.handler_name(job)
|
|
47
|
+
payload_object =
|
|
48
|
+
job.respond_to?(:payload_object_without_sk) ? job.payload_object_without_sk : job.payload_object
|
|
49
|
+
|
|
50
|
+
payload_object_name(payload_object)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.payload_object_name(payload_object)
|
|
54
|
+
if payload_object.is_a?(::Delayed::PerformableMethod)
|
|
55
|
+
payload_object.display_name
|
|
56
|
+
else
|
|
57
|
+
# In the case of ActiveJob-wrapped jobs, there is quite a bit of job-specific metadata
|
|
58
|
+
# in `job.name`, which would break aggregation and potentially leak private data in job args.
|
|
59
|
+
# Use class name instead to avoid this.
|
|
60
|
+
payload_object.class.name
|
|
61
|
+
end
|
|
62
|
+
rescue StandardError
|
|
63
|
+
UNKNOWN
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.payload_object_source_meta(payload_object)
|
|
67
|
+
if payload_object.is_a?(::Delayed::PerformableMethod)
|
|
68
|
+
if payload_object.object.is_a?(Module)
|
|
69
|
+
[:class_method, payload_object.object.name, payload_object.method_name.to_s]
|
|
70
|
+
else
|
|
71
|
+
[:instance_method, payload_object.object.class.name, payload_object.method_name.to_s]
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
[:instance_method, payload_object.class.name, "perform"]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class InstrumentationProxy < SimpleDelegator
|
|
79
|
+
def perform
|
|
80
|
+
source_meta = Skylight::Probes::DelayedJob.payload_object_source_meta(__getobj__)
|
|
81
|
+
|
|
82
|
+
opts = {
|
|
83
|
+
category: "app.delayed_job.job",
|
|
84
|
+
title: format_source(*source_meta),
|
|
85
|
+
meta: {
|
|
86
|
+
source_location_hint: source_meta
|
|
87
|
+
},
|
|
88
|
+
internal: true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Skylight.instrument(opts) { __getobj__.perform }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Used by Delayed::Backend::Base to determine Job#name
|
|
95
|
+
def display_name
|
|
96
|
+
__getobj__.respond_to?(:display_name) ? __getobj__.display_name : __getobj__.class.name
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def format_source(method_type, constant_name, method_name)
|
|
102
|
+
method_type == :instance_method ? "#{constant_name}##{method_name}" : "#{constant_name}.#{method_name}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class Probe
|
|
107
|
+
def install
|
|
108
|
+
return unless validate_version && plugin_defined?
|
|
109
|
+
|
|
110
|
+
::Delayed::Worker.plugins = [Skylight::Probes::DelayedJob::Plugin] | ::Delayed::Worker.plugins
|
|
111
|
+
::Delayed::Backend::Base.class_eval do
|
|
112
|
+
alias_method :payload_object_without_sk, :payload_object
|
|
113
|
+
|
|
114
|
+
def payload_object
|
|
115
|
+
Skylight::Probes::DelayedJob::InstrumentationProxy.new(payload_object_without_sk)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def plugin_defined?
|
|
123
|
+
defined?(::Skylight::Probes::DelayedJob::Plugin)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def validate_version
|
|
127
|
+
spec = Gem.loaded_specs["delayed_job"]
|
|
128
|
+
version = spec&.version
|
|
129
|
+
|
|
130
|
+
if !version || version < Gem::Version.new("4.0.0")
|
|
131
|
+
Skylight.error "The installed version of DelayedJob is not supported on Skylight. " \
|
|
132
|
+
"Your jobs will not be tracked."
|
|
133
|
+
|
|
134
|
+
return false
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
true
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
register(:delayed_job, "Delayed::Worker", "delayed_job", DelayedJob::Probe.new)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Probes
|
|
3
|
+
module Elasticsearch
|
|
4
|
+
class Probe
|
|
5
|
+
def install
|
|
6
|
+
# Prepending doesn't work here since this a module that's already been included
|
|
7
|
+
::Elasticsearch::Transport::Transport::Base.class_eval do
|
|
8
|
+
alias_method :perform_request_without_sk, :perform_request
|
|
9
|
+
def perform_request(method, path, *args, &block)
|
|
10
|
+
ActiveSupport::Notifications.instrument(
|
|
11
|
+
"request.elasticsearch",
|
|
12
|
+
name: "Request",
|
|
13
|
+
method: method,
|
|
14
|
+
path: path
|
|
15
|
+
) do
|
|
16
|
+
# Prevent HTTP-related probes from firing
|
|
17
|
+
Skylight::Normalizers::Faraday::Request.disable do
|
|
18
|
+
disable_skylight_probe(:NetHTTP) do
|
|
19
|
+
disable_skylight_probe(:HTTPClient) { perform_request_without_sk(method, path, *args, &block) }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def disable_skylight_probe(class_name)
|
|
26
|
+
klass = ::ActiveSupport::Inflector.safe_constantize("Skylight::Probes::#{class_name}::Probe")
|
|
27
|
+
(klass ? klass.disable { yield } : yield).tap { Skylight.log(:debug, "re-enabling: #{klass}") }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
register(:elasticsearch, "Elasticsearch", "elasticsearch", Elasticsearch::Probe.new)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require "skylight/formatters/http"
|
|
2
|
+
|
|
3
|
+
module Skylight
|
|
4
|
+
module Probes
|
|
5
|
+
module Excon
|
|
6
|
+
# Middleware for Excon that instruments requests
|
|
7
|
+
class Middleware < ::Excon::Middleware::Base
|
|
8
|
+
def initialize(*)
|
|
9
|
+
@requests = {}.compare_by_identity
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# TODO: Review the following:
|
|
14
|
+
# - Consider whether a LIFO queue would be sufficient
|
|
15
|
+
# - Check that errors can't be called without a request
|
|
16
|
+
|
|
17
|
+
def request_call(datum)
|
|
18
|
+
begin_instrumentation(datum)
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def response_call(datum)
|
|
23
|
+
super
|
|
24
|
+
ensure
|
|
25
|
+
end_instrumentation(datum)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def error_call(datum)
|
|
29
|
+
super
|
|
30
|
+
ensure
|
|
31
|
+
end_instrumentation(datum)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def begin_instrumentation(datum)
|
|
37
|
+
method = datum[:method].to_s
|
|
38
|
+
scheme = datum[:scheme]
|
|
39
|
+
host = datum[:host]
|
|
40
|
+
|
|
41
|
+
# TODO: Maybe don't show other default ports like 443
|
|
42
|
+
port = datum[:port] == 80 ? nil : datum[:port]
|
|
43
|
+
path = datum[:path]
|
|
44
|
+
query = datum[:query]
|
|
45
|
+
|
|
46
|
+
opts = Formatters::HTTP.build_opts(method, scheme, host, port, path, query)
|
|
47
|
+
|
|
48
|
+
@requests[datum] = Skylight.instrument(opts)
|
|
49
|
+
rescue Exception => e
|
|
50
|
+
Skylight.error "failed to begin instrumentation for Excon; msg=%s", e.message
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def end_instrumentation(datum)
|
|
54
|
+
if (request = @requests.delete(datum))
|
|
55
|
+
meta = {}
|
|
56
|
+
meta[:exception_object] = datum[:error] if datum[:error].is_a?(Exception)
|
|
57
|
+
Skylight.done(request, meta)
|
|
58
|
+
end
|
|
59
|
+
rescue Exception => e
|
|
60
|
+
Skylight.error "failed to end instrumentation for Excon; msg=%s", e.message
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Skylight
|
|
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/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
|
+
Skylight.error "The installed version of Excon doesn't support Middlewares. " \
|
|
17
|
+
"The Excon probe will be disabled."
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
register(:excon, "Excon", "excon", Excon::Probe.new)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Probes
|
|
3
|
+
module Faraday
|
|
4
|
+
module Instrumentation
|
|
5
|
+
def builder
|
|
6
|
+
unless defined?(@__sk__setup)
|
|
7
|
+
@__sk__setup = true
|
|
8
|
+
@builder.insert 0, ::Faraday::Request::Instrumentation
|
|
9
|
+
end
|
|
10
|
+
@builder
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Probe
|
|
15
|
+
def install
|
|
16
|
+
::Faraday::Connection.prepend(Instrumentation)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
register(:faraday, "Faraday", "faraday", Faraday::Probe.new)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/inflector"
|
|
4
|
+
|
|
5
|
+
module Skylight
|
|
6
|
+
module Probes
|
|
7
|
+
module GraphQL
|
|
8
|
+
module Instrumentation
|
|
9
|
+
def initialize(*, **)
|
|
10
|
+
super
|
|
11
|
+
|
|
12
|
+
return unless defined?(@tracers)
|
|
13
|
+
|
|
14
|
+
unless @tracers.include?(::GraphQL::Tracing::ActiveSupportNotificationsTracing)
|
|
15
|
+
@tracers << ::GraphQL::Tracing::ActiveSupportNotificationsTracing
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Probe
|
|
21
|
+
def install
|
|
22
|
+
tracing_klass_name = "::GraphQL::Tracing::ActiveSupportNotificationsTracing"
|
|
23
|
+
klasses_to_probe = %w[::GraphQL::Execution::Multiplex ::GraphQL::Query]
|
|
24
|
+
|
|
25
|
+
return unless ([tracing_klass_name] + klasses_to_probe).all?(&method(:safe_constantize))
|
|
26
|
+
|
|
27
|
+
klasses_to_probe.each { |klass_name| safe_constantize(klass_name).prepend(Instrumentation) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def safe_constantize(klass_name)
|
|
31
|
+
ActiveSupport::Inflector.safe_constantize(klass_name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
register(:graphql, "GraphQL", "graphql", GraphQL::Probe.new)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require "skylight/formatters/http"
|
|
2
|
+
|
|
3
|
+
module Skylight
|
|
4
|
+
module Probes
|
|
5
|
+
module HTTPClient
|
|
6
|
+
module Instrumentation
|
|
7
|
+
# HTTPClient has request methods on the class object itself,
|
|
8
|
+
# but they internally instantiate a client and perform the method
|
|
9
|
+
# on that, so this instance method override will cover both
|
|
10
|
+
# `HTTPClient.get(...)` and `HTTPClient.new.get(...)`
|
|
11
|
+
|
|
12
|
+
def do_request(method, uri, *)
|
|
13
|
+
return super if Probes::HTTPClient::Probe.disabled?
|
|
14
|
+
|
|
15
|
+
opts = Formatters::HTTP.build_opts(method, uri.scheme, uri.host, uri.port, uri.path, uri.query)
|
|
16
|
+
|
|
17
|
+
Skylight.instrument(opts) { super }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Probe
|
|
22
|
+
DISABLED_KEY = :__skylight_httpclient_disabled
|
|
23
|
+
|
|
24
|
+
def self.disable
|
|
25
|
+
old_value = Thread.current[DISABLED_KEY]
|
|
26
|
+
Thread.current[DISABLED_KEY] = true
|
|
27
|
+
yield
|
|
28
|
+
ensure
|
|
29
|
+
Thread.current[DISABLED_KEY] = old_value
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.disabled?
|
|
33
|
+
!!Thread.current[DISABLED_KEY]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def install
|
|
37
|
+
::HTTPClient.prepend(Instrumentation)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
register(:httpclient, "HTTPClient", "httpclient", HTTPClient::Probe.new)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Probes
|
|
3
|
+
module Middleware
|
|
4
|
+
# for Rails >= 6.0, which includes InstrumentationProxy
|
|
5
|
+
module InstrumentationExtensions
|
|
6
|
+
def initialize(middleware, class_name)
|
|
7
|
+
super
|
|
8
|
+
|
|
9
|
+
# NOTE: Caching here leads to better performance, but will not notice if the method is overridden
|
|
10
|
+
# We don't have access to the config here so we can't check whether source locations are enabled.
|
|
11
|
+
# However, this only happens once per middleware so it should be minimal impact.
|
|
12
|
+
@payload[:sk_source_location] =
|
|
13
|
+
begin
|
|
14
|
+
if middleware.is_a?(Proc)
|
|
15
|
+
middleware.source_location
|
|
16
|
+
elsif middleware.respond_to?(:call)
|
|
17
|
+
middleware.method(:call).source_location
|
|
18
|
+
end
|
|
19
|
+
rescue StandardError
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# for Rails <= 5.2 ActionDispatch::MiddlewareStack::Middleware
|
|
26
|
+
module Instrumentation
|
|
27
|
+
def build(*)
|
|
28
|
+
Instrumentation.sk_instrument_middleware(super)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.sk_instrument_middleware(middleware)
|
|
32
|
+
return middleware if middleware.is_a?(Skylight::Middleware)
|
|
33
|
+
|
|
34
|
+
# Not sure how this would actually happen
|
|
35
|
+
return middleware if middleware.respond_to?(:__has_sk__)
|
|
36
|
+
|
|
37
|
+
# On Rails 3, ActionDispatch::Session::CookieStore is frozen, for one
|
|
38
|
+
return middleware if middleware.frozen?
|
|
39
|
+
|
|
40
|
+
Skylight::Probes::Middleware::Probe.add_instrumentation(middleware)
|
|
41
|
+
|
|
42
|
+
middleware
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Probe
|
|
47
|
+
DISABLED_KEY = :__skylight_middleware_disabled
|
|
48
|
+
|
|
49
|
+
def self.disable!
|
|
50
|
+
@disabled = true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.enable!
|
|
54
|
+
@disabled = false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.disabled?
|
|
58
|
+
!!@disabled
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
module InstanceInstrumentation
|
|
62
|
+
def call(*args)
|
|
63
|
+
return super(*args) if Skylight::Probes::Middleware::Probe.disabled?
|
|
64
|
+
|
|
65
|
+
trace = Skylight.instrumenter&.current_trace
|
|
66
|
+
return super(*args) unless trace
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
name = self.class.name || __sk_default_name
|
|
70
|
+
|
|
71
|
+
trace.endpoint = name
|
|
72
|
+
|
|
73
|
+
source_file, source_line = method(__method__).super_method.source_location
|
|
74
|
+
|
|
75
|
+
spans =
|
|
76
|
+
Skylight.instrument(
|
|
77
|
+
title: name,
|
|
78
|
+
category: __sk_category,
|
|
79
|
+
source_file: source_file,
|
|
80
|
+
source_line: source_line
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
proxied_response =
|
|
84
|
+
Skylight::Middleware.with_after_close(super(*args), debug_identifier: "Middleware: #{name}") do
|
|
85
|
+
Skylight.done(spans)
|
|
86
|
+
end
|
|
87
|
+
rescue Exception => e
|
|
88
|
+
Skylight.done(spans, exception_object: e)
|
|
89
|
+
raise
|
|
90
|
+
ensure
|
|
91
|
+
unless e || proxied_response
|
|
92
|
+
# If we've gotten to this point, the most likely scenario is that
|
|
93
|
+
# a throw/catch has bypassed a portion of the callstack. Since these spans would not otherwise
|
|
94
|
+
# be closed, mark them deferred to indicate that they should be implicitly closed.
|
|
95
|
+
# See Trace#deferred_spans or Trace#stop for more information.
|
|
96
|
+
Skylight.done(spans, defer: true)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def __sk_default_name
|
|
102
|
+
"Anonymous Middleware"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def __sk_category
|
|
106
|
+
"rack.middleware"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def __has_sk__
|
|
110
|
+
true
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.add_instrumentation(middleware)
|
|
115
|
+
middleware.singleton_class.prepend(InstanceInstrumentation)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def install
|
|
119
|
+
if defined?(::ActionDispatch::MiddlewareStack::InstrumentationProxy)
|
|
120
|
+
::ActionDispatch::MiddlewareStack::InstrumentationProxy.prepend(InstrumentationExtensions)
|
|
121
|
+
else
|
|
122
|
+
::ActionDispatch::MiddlewareStack::Middleware.prepend(Instrumentation)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
register(
|
|
129
|
+
:middleware,
|
|
130
|
+
"ActionDispatch::MiddlewareStack::Middleware",
|
|
131
|
+
"actionpack/action_dispatch",
|
|
132
|
+
Middleware::Probe.new
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
end
|