skylight 3.1.4 → 5.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +465 -294
  3. data/CLA.md +1 -1
  4. data/CONTRIBUTING.md +11 -3
  5. data/ERRORS.md +3 -0
  6. data/LICENSE.md +8 -18
  7. data/README.md +1 -2
  8. data/bin/skylight +1 -1
  9. data/ext/extconf.rb +118 -122
  10. data/ext/libskylight.yml +8 -6
  11. data/ext/skylight_native.c +56 -100
  12. data/lib/skylight/api.rb +41 -27
  13. data/lib/skylight/cli/doctor.rb +68 -70
  14. data/lib/skylight/cli/helpers.rb +3 -5
  15. data/lib/skylight/cli/merger.rb +99 -92
  16. data/lib/skylight/cli.rb +40 -43
  17. data/lib/skylight/config.rb +656 -201
  18. data/lib/skylight/data/cacert.pem +730 -1023
  19. data/lib/skylight/deprecation.rb +17 -0
  20. data/lib/skylight/errors.rb +34 -16
  21. data/lib/skylight/extensions/source_location.rb +291 -0
  22. data/lib/skylight/extensions.rb +95 -0
  23. data/lib/skylight/formatters/http.rb +18 -0
  24. data/lib/skylight/gc.rb +99 -0
  25. data/lib/skylight/helpers.rb +82 -39
  26. data/lib/skylight/instrumenter.rb +339 -9
  27. data/lib/skylight/middleware.rb +147 -1
  28. data/lib/skylight/native.rb +71 -23
  29. data/lib/skylight/native_ext_fetcher.rb +39 -47
  30. data/lib/skylight/normalizers/action_controller/process_action.rb +68 -0
  31. data/lib/skylight/normalizers/action_controller/send_file.rb +51 -0
  32. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  33. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  34. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  35. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  36. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  37. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  38. data/lib/skylight/normalizers/active_job/perform.rb +87 -0
  39. data/lib/skylight/normalizers/active_model_serializers/render.rb +32 -0
  40. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  41. data/lib/skylight/normalizers/active_record/sql.rb +20 -0
  42. data/lib/skylight/normalizers/active_storage.rb +28 -0
  43. data/lib/skylight/normalizers/active_support/cache.rb +11 -0
  44. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  49. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  50. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  51. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  52. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  53. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  54. data/lib/skylight/normalizers/coach/handler_finish.rb +44 -0
  55. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  56. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  57. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  58. data/lib/skylight/normalizers/default.rb +24 -0
  59. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  60. data/lib/skylight/normalizers/faraday/request.rb +38 -0
  61. data/lib/skylight/normalizers/grape/endpoint.rb +28 -0
  62. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  63. data/lib/skylight/normalizers/grape/endpoint_run.rb +39 -0
  64. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +20 -0
  65. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  66. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  67. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  68. data/lib/skylight/normalizers/graphql/base.rb +127 -0
  69. data/lib/skylight/normalizers/render.rb +79 -0
  70. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  71. data/lib/skylight/normalizers/shrine.rb +32 -0
  72. data/lib/skylight/normalizers/sql.rb +41 -0
  73. data/lib/skylight/normalizers.rb +157 -0
  74. data/lib/skylight/probes/action_controller.rb +52 -0
  75. data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
  76. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
  77. data/lib/skylight/probes/action_dispatch.rb +2 -0
  78. data/lib/skylight/probes/action_view.rb +42 -0
  79. data/lib/skylight/probes/active_job.rb +27 -0
  80. data/lib/skylight/probes/active_job_enqueue.rb +35 -0
  81. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  82. data/lib/skylight/probes/active_record_async.rb +96 -0
  83. data/lib/skylight/probes/delayed_job.rb +144 -0
  84. data/lib/skylight/probes/elasticsearch.rb +45 -0
  85. data/lib/skylight/probes/excon/middleware.rb +65 -0
  86. data/lib/skylight/probes/excon.rb +25 -0
  87. data/lib/skylight/probes/faraday.rb +23 -0
  88. data/lib/skylight/probes/graphql.rb +38 -0
  89. data/lib/skylight/probes/httpclient.rb +44 -0
  90. data/lib/skylight/probes/middleware.rb +135 -0
  91. data/lib/skylight/probes/mongo.rb +169 -0
  92. data/lib/skylight/probes/mongoid.rb +6 -0
  93. data/lib/skylight/probes/net_http.rb +54 -0
  94. data/lib/skylight/probes/rack_builder.rb +37 -0
  95. data/lib/skylight/probes/redis.rb +68 -0
  96. data/lib/skylight/probes/sequel.rb +29 -0
  97. data/lib/skylight/probes/sinatra.rb +66 -0
  98. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  99. data/lib/skylight/probes/tilt.rb +25 -0
  100. data/lib/skylight/probes.rb +172 -0
  101. data/lib/skylight/railtie.rb +172 -15
  102. data/lib/skylight/sidekiq.rb +47 -0
  103. data/lib/skylight/sinatra.rb +2 -2
  104. data/lib/skylight/subscriber.rb +130 -0
  105. data/lib/skylight/test.rb +147 -0
  106. data/lib/skylight/trace.rb +331 -15
  107. data/lib/skylight/user_config.rb +60 -0
  108. data/lib/skylight/util/allocation_free.rb +26 -0
  109. data/lib/skylight/util/clock.rb +57 -0
  110. data/lib/skylight/util/component.rb +47 -9
  111. data/lib/skylight/util/deploy.rb +24 -40
  112. data/lib/skylight/util/gzip.rb +20 -0
  113. data/lib/skylight/util/hostname.rb +4 -4
  114. data/lib/skylight/util/http.rb +62 -71
  115. data/lib/skylight/util/instrumenter_method.rb +26 -0
  116. data/lib/skylight/util/logging.rb +136 -0
  117. data/lib/skylight/util/lru_cache.rb +36 -0
  118. data/lib/skylight/util/platform.rb +74 -0
  119. data/lib/skylight/util/proxy.rb +13 -0
  120. data/lib/skylight/util/ssl.rb +4 -28
  121. data/lib/skylight/util.rb +12 -0
  122. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  123. data/lib/skylight/version.rb +5 -1
  124. data/lib/skylight/vm/gc.rb +60 -0
  125. data/lib/skylight.rb +213 -24
  126. metadata +171 -53
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Skylight
4
+ module Probes
5
+ module ActionView
6
+ module Instrumentation
7
+ def render_with_layout(*args) #:nodoc:
8
+ path, locals =
9
+ case args.length
10
+ when 2
11
+ args
12
+ when 4
13
+ # Rails > 6.0.0.beta3 arguments are (view, template, path, locals)
14
+ [args[2], args[3]]
15
+ end
16
+
17
+ layout = nil
18
+
19
+ layout = find_layout(path, locals.keys, [formats.first]) if path
20
+
21
+ if layout
22
+ ActiveSupport::Notifications.instrument("render_template.action_view", identifier: layout.identifier) do
23
+ super
24
+ end
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+
31
+ class Probe
32
+ def install
33
+ return if ::ActionView.gem_version >= Gem::Version.new("6.1.0.alpha")
34
+
35
+ ::ActionView::TemplateRenderer.prepend(Instrumentation)
36
+ end
37
+ end
38
+ end
39
+
40
+ register(:action_view, "ActionView::TemplateRenderer", "action_view", ActionView::Probe.new)
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ module Skylight
2
+ module Probes
3
+ module ActiveJob
4
+ TITLE = "ActiveJob.execute".freeze
5
+
6
+ module Instrumentation
7
+ def execute(*)
8
+ Skylight.trace(TITLE, "app.job.execute", component: :worker) do |trace|
9
+ # See normalizers/active_job/perform for endpoint/segment assignment
10
+ super
11
+ rescue Exception
12
+ trace.segment = "error" if trace
13
+ raise
14
+ end
15
+ end
16
+ end
17
+
18
+ class Probe
19
+ def install
20
+ ::ActiveJob::Base.singleton_class.prepend(Instrumentation)
21
+ end
22
+ end
23
+ end
24
+
25
+ register(:active_job, "ActiveJob::Base", "active_job/base", ActiveJob::Probe.new)
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ module Skylight
2
+ module Probes
3
+ module ActiveJob
4
+ class EnqueueProbe
5
+ CAT = "other.active_job.enqueue".freeze
6
+
7
+ def install
8
+ ::ActiveJob::Base.around_enqueue do |job, block|
9
+ job_class = job.class
10
+ adapter_name = EnqueueProbe.normalize_adapter_name(job_class)
11
+
12
+ # If this is an ActionMailer::DeliveryJob, we'll report this as the mailer title
13
+ # and include ActionMailer::DeliveryJob in the description.
14
+ name, job_class_name = Normalizers::ActiveJob::Perform.normalize_title(job)
15
+ descriptors = ["adapter: '#{adapter_name}'", "queue: '#{job.queue_name}'"]
16
+ descriptors << "job: '#{job_class_name}'" if job_class_name
17
+ desc = "{ #{descriptors.join(", ")} }"
18
+ rescue StandardError
19
+ block.call
20
+ else
21
+ Skylight.instrument(title: "Enqueue #{name}", category: CAT, description: desc, internal: true, &block)
22
+ end
23
+
24
+ self.class.instance_eval do
25
+ def normalize_adapter_name(job_class)
26
+ job_class.queue_adapter_name
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ register(:active_job_enqueue, "ActiveJob::Base", "active_job/base", ActiveJob::EnqueueProbe.new)
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ module Skylight
2
+ module Probes
3
+ module ActiveModelSerializers
4
+ module Instrumentation
5
+ def as_json(*)
6
+ payload = { serializer: self.class }
7
+ ActiveSupport::Notifications.instrument("render.active_model_serializers", payload) { super }
8
+ end
9
+ end
10
+
11
+ class Probe
12
+ def install
13
+ version = nil
14
+
15
+ # File moved location between version
16
+ %w[serializer serializers].each do |dir|
17
+ require "active_model/#{dir}/version"
18
+ rescue LoadError # rubocop:disable Lint/SuppressedException
19
+ end
20
+
21
+ version = Gem.loaded_specs["active_model_serializers"].version if Gem.loaded_specs["active_model_serializers"]
22
+
23
+ if !version || version < Gem::Version.new("0.5.0")
24
+ Skylight.error "Instrumention is only available for ActiveModelSerializers version 0.5.0 and greater."
25
+ return
26
+ end
27
+
28
+ # We don't actually support the RCs correctly, requires
29
+ # a release after 0.10.0.rc3
30
+ if version >= Gem::Version.new("0.10.0.rc1")
31
+ # AS::N is built in to newer versions
32
+ return
33
+ end
34
+
35
+ # End users could override as_json without calling super, but it's likely safer
36
+ # than overriding serializable_array/hash/object.
37
+
38
+ [::ActiveModel::Serializer, ::ActiveModel::ArraySerializer].each { |klass| klass.prepend(Instrumentation) }
39
+ end
40
+ end
41
+ end
42
+
43
+ register(
44
+ :active_model_serializers,
45
+ "ActiveModel::Serializer",
46
+ "active_model/serializer",
47
+ ActiveModelSerializers::Probe.new
48
+ )
49
+ end
50
+ end
@@ -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,45 @@
1
+ module Skylight
2
+ module Probes
3
+ module Elasticsearch
4
+ class Probe
5
+ def install
6
+ const =
7
+ if defined?(::Elasticsearch::Transport::Transport::Base)
8
+ ::Elasticsearch::Transport::Transport::Base
9
+ elsif defined?(::Elastic::Transport::Transport::Base)
10
+ ::Elastic::Transport::Transport::Base
11
+ else
12
+ return false
13
+ end
14
+
15
+ # Prepending doesn't work here since this a module that's already been included
16
+ const.class_eval do
17
+ alias_method :perform_request_without_sk, :perform_request
18
+ def perform_request(method, path, *args, &block)
19
+ ActiveSupport::Notifications.instrument(
20
+ "request.elasticsearch",
21
+ name: "Request",
22
+ method: method,
23
+ path: path
24
+ ) do
25
+ # Prevent HTTP-related probes from firing
26
+ Skylight::Normalizers::Faraday::Request.disable do
27
+ disable_skylight_probe(:NetHTTP) do
28
+ disable_skylight_probe(:HTTPClient) { perform_request_without_sk(method, path, *args, &block) }
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def disable_skylight_probe(class_name)
35
+ klass = ::ActiveSupport::Inflector.safe_constantize("Skylight::Probes::#{class_name}::Probe")
36
+ (klass ? klass.disable { yield } : yield).tap { Skylight.log(:debug, "re-enabling: #{klass}") }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ register(:elasticsearch, "Elasticsearch", "elasticsearch", Elasticsearch::Probe.new)
44
+ end
45
+ 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