skylight 5.0.0.beta → 5.0.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 +26 -6
- data/CONTRIBUTING.md +1 -1
- data/ext/extconf.rb +2 -2
- data/ext/libskylight.yml +7 -5
- data/lib/skylight.rb +9 -2
- data/lib/skylight/api.rb +3 -0
- data/lib/skylight/cli/doctor.rb +11 -13
- data/lib/skylight/config.rb +25 -32
- data/lib/skylight/deprecation.rb +3 -1
- data/lib/skylight/errors.rb +4 -4
- data/lib/skylight/extensions.rb +8 -0
- data/lib/skylight/extensions/source_location.rb +123 -81
- data/lib/skylight/formatters/http.rb +2 -1
- data/lib/skylight/helpers.rb +44 -35
- data/lib/skylight/instrumenter.rb +3 -2
- data/lib/skylight/middleware.rb +4 -4
- data/lib/skylight/native.rb +1 -1
- data/lib/skylight/native_ext_fetcher.rb +2 -2
- data/lib/skylight/normalizers.rb +6 -4
- data/lib/skylight/normalizers/action_controller/process_action.rb +1 -1
- data/lib/skylight/normalizers/action_dispatch/route_set.rb +1 -1
- data/lib/skylight/normalizers/active_job/perform.rb +5 -0
- data/lib/skylight/normalizers/graphql/base.rb +1 -0
- data/lib/skylight/normalizers/render.rb +1 -1
- data/lib/skylight/normalizers/shrine.rb +34 -0
- data/lib/skylight/normalizers/sql.rb +3 -2
- data/lib/skylight/probes.rb +38 -10
- data/lib/skylight/probes/active_job.rb +4 -6
- data/lib/skylight/probes/active_job_enqueue.rb +18 -14
- data/lib/skylight/probes/active_model_serializers.rb +2 -6
- data/lib/skylight/probes/delayed_job.rb +112 -25
- data/lib/skylight/probes/elasticsearch.rb +1 -1
- data/lib/skylight/probes/excon/middleware.rb +4 -4
- data/lib/skylight/probes/middleware.rb +2 -1
- data/lib/skylight/probes/mongo.rb +2 -1
- data/lib/skylight/probes/net_http.rb +0 -1
- data/lib/skylight/probes/redis.rb +6 -3
- data/lib/skylight/railtie.rb +1 -1
- data/lib/skylight/sidekiq.rb +12 -7
- data/lib/skylight/subscriber.rb +1 -1
- data/lib/skylight/trace.rb +10 -4
- data/lib/skylight/util/deploy.rb +3 -6
- data/lib/skylight/util/instrumenter_method.rb +11 -11
- data/lib/skylight/util/logging.rb +6 -6
- data/lib/skylight/util/lru_cache.rb +1 -3
- data/lib/skylight/util/platform.rb +1 -1
- data/lib/skylight/version.rb +1 -1
- metadata +27 -13
- data/lib/skylight/fanout.rb +0 -0
@@ -6,21 +6,25 @@ module Skylight
|
|
6
6
|
|
7
7
|
def install
|
8
8
|
::ActiveJob::Base.around_enqueue do |job, block|
|
9
|
-
|
10
|
-
|
11
|
-
adapter_name = EnqueueProbe.normalize_adapter_name(job_class)
|
9
|
+
job_class = job.class
|
10
|
+
adapter_name = EnqueueProbe.normalize_adapter_name(job_class)
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
19
|
+
block.call
|
20
|
+
else
|
21
|
+
Skylight.instrument(
|
22
|
+
title: "Enqueue #{name}",
|
23
|
+
category: CAT,
|
24
|
+
description: desc,
|
25
|
+
internal: true,
|
26
|
+
&block
|
27
|
+
)
|
24
28
|
end
|
25
29
|
|
26
30
|
self.class.instance_eval do
|
@@ -14,12 +14,8 @@ module Skylight
|
|
14
14
|
|
15
15
|
# File moved location between version
|
16
16
|
%w[serializer serializers].each do |dir|
|
17
|
-
|
18
|
-
|
19
|
-
require "active_model/#{dir}/version"
|
20
|
-
rescue LoadError
|
21
|
-
end
|
22
|
-
# rubocop:enable Lint/SuppressedException
|
17
|
+
require "active_model/#{dir}/version"
|
18
|
+
rescue LoadError # rubocop:disable Lint/SuppressedException
|
23
19
|
end
|
24
20
|
|
25
21
|
if Gem.loaded_specs["active_model_serializers"]
|
@@ -1,46 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
1
5
|
module Skylight
|
2
6
|
module Probes
|
3
7
|
module DelayedJob
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
begin
|
12
|
-
if defined?(::Delayed::PerformableMethod) && job.payload_object.is_a?(::Delayed::PerformableMethod)
|
13
|
-
job.name
|
14
|
-
else
|
15
|
-
job.payload_object.class.name
|
16
|
-
end
|
17
|
-
rescue
|
18
|
-
UNKNOWN
|
8
|
+
begin
|
9
|
+
require "delayed/plugin"
|
10
|
+
|
11
|
+
class Plugin < ::Delayed::Plugin
|
12
|
+
callbacks do |lifecycle|
|
13
|
+
lifecycle.around(:perform) do |worker, job, &block|
|
14
|
+
sk_instrument(worker, job, &block)
|
19
15
|
end
|
20
16
|
|
21
|
-
|
22
|
-
|
17
|
+
lifecycle.after(:error) do |_worker, _job|
|
18
|
+
Skylight.trace&.segment = "error"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
include Skylight::Util::Logging
|
24
|
+
|
25
|
+
def sk_instrument(_worker, job)
|
26
|
+
endpoint = Skylight::Probes::DelayedJob.handler_name(job)
|
27
|
+
|
28
|
+
Skylight.trace(endpoint,
|
29
|
+
"app.delayed_job.worker",
|
30
|
+
"Delayed::Worker#run",
|
31
|
+
component: :worker,
|
32
|
+
segment: job.queue,
|
33
|
+
meta: { source_location: "delayed_job" }) do
|
34
|
+
t { "Delayed::Job beginning trace" }
|
35
|
+
yield
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
23
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>"
|
24
45
|
|
25
|
-
|
26
|
-
|
27
|
-
|
46
|
+
def self.handler_name(job)
|
47
|
+
payload_object = if job.respond_to?(:payload_object_without_sk)
|
48
|
+
job.payload_object_without_sk
|
49
|
+
else
|
50
|
+
job.payload_object
|
51
|
+
end
|
52
|
+
|
53
|
+
payload_object_name(payload_object)
|
54
|
+
end
|
28
55
|
|
29
|
-
|
56
|
+
def self.payload_object_name(payload_object)
|
57
|
+
if payload_object.is_a?(::Delayed::PerformableMethod)
|
58
|
+
payload_object.display_name
|
59
|
+
else
|
60
|
+
# In the case of ActiveJob-wrapped jobs, there is quite a bit of job-specific metadata
|
61
|
+
# in `job.name`, which would break aggregation and potentially leak private data in job args.
|
62
|
+
# Use class name instead to avoid this.
|
63
|
+
payload_object.class.name
|
30
64
|
end
|
65
|
+
rescue
|
66
|
+
UNKNOWN
|
31
67
|
end
|
32
68
|
|
33
|
-
|
34
|
-
|
69
|
+
def self.payload_object_source_meta(payload_object)
|
70
|
+
if payload_object.is_a?(::Delayed::PerformableMethod)
|
71
|
+
if payload_object.object.is_a?(Module)
|
72
|
+
[:class_method, payload_object.object.name, payload_object.method_name.to_s]
|
73
|
+
else
|
74
|
+
[:instance_method, payload_object.object.class.name, payload_object.method_name.to_s]
|
75
|
+
end
|
76
|
+
else
|
77
|
+
[:instance_method, payload_object.class.name, "perform"]
|
78
|
+
end
|
79
|
+
end
|
35
80
|
|
81
|
+
class InstrumentationProxy < SimpleDelegator
|
82
|
+
def perform
|
83
|
+
source_meta = Skylight::Probes::DelayedJob.payload_object_source_meta(__getobj__)
|
84
|
+
|
85
|
+
opts = {
|
86
|
+
category: "app.delayed_job.job",
|
87
|
+
title: format_source(*source_meta),
|
88
|
+
meta: { source_location_hint: source_meta },
|
89
|
+
internal: true
|
90
|
+
}
|
91
|
+
|
92
|
+
Skylight.instrument(opts) { __getobj__.perform }
|
93
|
+
end
|
94
|
+
|
95
|
+
# Used by Delayed::Backend::Base to determine Job#name
|
96
|
+
def display_name
|
97
|
+
__getobj__.respond_to?(:display_name) ? __getobj__.display_name : __getobj__.class.name
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def format_source(method_type, constant_name, method_name)
|
103
|
+
if method_type == :instance_method
|
104
|
+
"#{constant_name}##{method_name}"
|
105
|
+
else
|
106
|
+
"#{constant_name}.#{method_name}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class Probe
|
36
112
|
def install
|
37
|
-
return unless validate_version
|
113
|
+
return unless validate_version && plugin_defined?
|
114
|
+
|
115
|
+
::Delayed::Worker.plugins = [Skylight::Probes::DelayedJob::Plugin] | ::Delayed::Worker.plugins
|
116
|
+
::Delayed::Backend::Base.class_eval do
|
117
|
+
alias_method :payload_object_without_sk, :payload_object
|
38
118
|
|
39
|
-
|
119
|
+
def payload_object
|
120
|
+
Skylight::Probes::DelayedJob::InstrumentationProxy.new(payload_object_without_sk)
|
121
|
+
end
|
122
|
+
end
|
40
123
|
end
|
41
124
|
|
42
125
|
private
|
43
126
|
|
127
|
+
def plugin_defined?
|
128
|
+
defined?(::Skylight::Probes::DelayedJob::Plugin)
|
129
|
+
end
|
130
|
+
|
44
131
|
def validate_version
|
45
132
|
spec = Gem.loaded_specs["delayed_job"]
|
46
133
|
version = spec&.version
|
@@ -26,7 +26,7 @@ module Skylight
|
|
26
26
|
|
27
27
|
def disable_skylight_probe(class_name)
|
28
28
|
klass = ::ActiveSupport::Inflector.safe_constantize("Skylight::Probes::#{class_name}::Probe")
|
29
|
-
(klass ? klass.disable { yield } : yield).tap {
|
29
|
+
(klass ? klass.disable { yield } : yield).tap { Skylight.log(:debug, "re-enabling: #{klass}") }
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -6,7 +6,7 @@ module Skylight
|
|
6
6
|
# Middleware for Excon that instruments requests
|
7
7
|
class Middleware < ::Excon::Middleware::Base
|
8
8
|
def initialize(*)
|
9
|
-
@requests = {}
|
9
|
+
@requests = {}.compare_by_identity
|
10
10
|
super
|
11
11
|
end
|
12
12
|
|
@@ -38,19 +38,19 @@ module Skylight
|
|
38
38
|
scheme = datum[:scheme]
|
39
39
|
host = datum[:host]
|
40
40
|
# TODO: Maybe don't show other default ports like 443
|
41
|
-
port = datum[:port]
|
41
|
+
port = datum[:port] == 80 ? nil : datum[:port]
|
42
42
|
path = datum[:path]
|
43
43
|
query = datum[:query]
|
44
44
|
|
45
45
|
opts = Formatters::HTTP.build_opts(method, scheme, host, port, path, query)
|
46
46
|
|
47
|
-
@requests[datum
|
47
|
+
@requests[datum] = Skylight.instrument(opts)
|
48
48
|
rescue Exception => e
|
49
49
|
Skylight.error "failed to begin instrumentation for Excon; msg=%s", e.message
|
50
50
|
end
|
51
51
|
|
52
52
|
def end_instrumentation(datum)
|
53
|
-
if (request = @requests.delete(datum
|
53
|
+
if (request = @requests.delete(datum))
|
54
54
|
meta = {}
|
55
55
|
if datum[:error].is_a?(Exception)
|
56
56
|
meta[:exception_object] = datum[:error]
|
@@ -72,7 +72,8 @@ module Skylight
|
|
72
72
|
|
73
73
|
source_file, source_line = method(__method__).super_method.source_location
|
74
74
|
|
75
|
-
spans = Skylight.instrument(title: name, category: __sk_category,
|
75
|
+
spans = Skylight.instrument(title: name, category: __sk_category,
|
76
|
+
source_file: source_file, source_line: source_line)
|
76
77
|
|
77
78
|
proxied_response =
|
78
79
|
Skylight::Middleware.with_after_close(super(*args), debug_identifier: "Middleware: #{name}") do
|
@@ -113,7 +113,8 @@ module Skylight
|
|
113
113
|
category: CAT,
|
114
114
|
title: title,
|
115
115
|
description: payload.empty? ? nil : payload.to_json,
|
116
|
-
meta: { database: event.database_name }
|
116
|
+
meta: { database: event.database_name },
|
117
|
+
internal: true
|
117
118
|
}
|
118
119
|
|
119
120
|
@events[event.operation_id] = Skylight.instrument(opts)
|
@@ -6,12 +6,14 @@ module Skylight
|
|
6
6
|
|
7
7
|
PIPELINED_OPTS = {
|
8
8
|
category: "db.redis.pipelined".freeze,
|
9
|
-
title: "PIPELINE".freeze
|
9
|
+
title: "PIPELINE".freeze,
|
10
|
+
internal: true
|
10
11
|
}.freeze
|
11
12
|
|
12
13
|
MULTI_OPTS = {
|
13
14
|
category: "db.redis.multi".freeze,
|
14
|
-
title: "MULTI".freeze
|
15
|
+
title: "MULTI".freeze,
|
16
|
+
internal: true
|
15
17
|
}.freeze
|
16
18
|
|
17
19
|
module ClientInstrumentation
|
@@ -22,7 +24,8 @@ module Skylight
|
|
22
24
|
|
23
25
|
opts = {
|
24
26
|
category: "db.redis.command",
|
25
|
-
title: command_name.upcase.to_s
|
27
|
+
title: command_name.upcase.to_s,
|
28
|
+
internal: true
|
26
29
|
}
|
27
30
|
|
28
31
|
Skylight.instrument(opts) { super }
|
data/lib/skylight/railtie.rb
CHANGED
data/lib/skylight/sidekiq.rb
CHANGED
@@ -19,16 +19,21 @@ module Skylight
|
|
19
19
|
class ServerMiddleware
|
20
20
|
include Util::Logging
|
21
21
|
|
22
|
-
def call(
|
22
|
+
def call(worker, job, queue)
|
23
23
|
t { "Sidekiq middleware beginning trace" }
|
24
24
|
title = job["wrapped"] || job["class"]
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
raise
|
25
|
+
|
26
|
+
# TODO: Using hints here would be ideal but requires further refactoring
|
27
|
+
meta =
|
28
|
+
if (source_location = worker.method(:perform).source_location)
|
29
|
+
{ source_file: source_location[0], source_line: source_location[1] }
|
31
30
|
end
|
31
|
+
|
32
|
+
Skylight.trace(title, "app.sidekiq.worker", title, meta: meta, segment: queue, component: :worker) do |trace|
|
33
|
+
yield
|
34
|
+
rescue Exception # includes Sidekiq::Shutdown
|
35
|
+
trace.segment = "error" if trace
|
36
|
+
raise
|
32
37
|
end
|
33
38
|
end
|
34
39
|
end
|
data/lib/skylight/subscriber.rb
CHANGED
data/lib/skylight/trace.rb
CHANGED
@@ -4,8 +4,9 @@ require "skylight/util/logging"
|
|
4
4
|
module Skylight
|
5
5
|
class Trace
|
6
6
|
GC_CAT = "noise.gc".freeze
|
7
|
+
SYNTHETIC = "<synthetic>".freeze
|
7
8
|
|
8
|
-
META_KEYS = %i[mute_children].freeze
|
9
|
+
META_KEYS = %i[mute_children database].freeze
|
9
10
|
|
10
11
|
include Util::Logging
|
11
12
|
|
@@ -39,6 +40,8 @@ module Skylight
|
|
39
40
|
|
40
41
|
@spans = []
|
41
42
|
|
43
|
+
preprocess_meta(meta) if meta
|
44
|
+
|
42
45
|
# create the root node
|
43
46
|
@root = start(native_get_started_at, cat, title, desc, meta, normalize: false)
|
44
47
|
|
@@ -217,7 +220,8 @@ module Skylight
|
|
217
220
|
|
218
221
|
if time > 0
|
219
222
|
t { fmt "tracking GC time; duration=%d", time }
|
220
|
-
|
223
|
+
meta = { source_location: SYNTHETIC }
|
224
|
+
stop(start(now - time, GC_CAT, nil, nil, meta), now)
|
221
225
|
end
|
222
226
|
end
|
223
227
|
|
@@ -336,8 +340,10 @@ module Skylight
|
|
336
340
|
def validate_meta(meta)
|
337
341
|
unknown_keys = meta.keys - allowed_meta_keys
|
338
342
|
if unknown_keys.any?
|
339
|
-
|
340
|
-
|
343
|
+
unknown_keys.each do |key|
|
344
|
+
maybe_warn("unknown_meta:#{key}", "Unknown meta key will be ignored; key=#{key.inspect}")
|
345
|
+
meta.delete(key)
|
346
|
+
end
|
341
347
|
end
|
342
348
|
end
|
343
349
|
|
data/lib/skylight/util/deploy.rb
CHANGED
@@ -13,8 +13,7 @@ module Skylight
|
|
13
13
|
end
|
14
14
|
|
15
15
|
class EmptyDeploy
|
16
|
-
attr_reader :config
|
17
|
-
attr_reader :timestamp
|
16
|
+
attr_reader :config, :timestamp
|
18
17
|
|
19
18
|
def initialize(config)
|
20
19
|
@config = config
|
@@ -90,10 +89,8 @@ module Skylight
|
|
90
89
|
def get_info
|
91
90
|
info_path = config[:'heroku.dyno_info_path']
|
92
91
|
|
93
|
-
if File.exist?(info_path)
|
94
|
-
|
95
|
-
info["release"]
|
96
|
-
end
|
92
|
+
if File.exist?(info_path) && (info = JSON.parse(File.read(info_path)))
|
93
|
+
info["release"]
|
97
94
|
end
|
98
95
|
end
|
99
96
|
end
|
@@ -4,20 +4,20 @@ module Skylight
|
|
4
4
|
def instrumenter_method(name, block: false)
|
5
5
|
if block
|
6
6
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
7
|
-
def #{name}(*args)
|
8
|
-
unless instrumenter
|
9
|
-
return yield if block_given?
|
10
|
-
return
|
11
|
-
end
|
12
|
-
|
13
|
-
instrumenter.#{name}(*args) { yield }
|
14
|
-
end
|
7
|
+
def #{name}(*args) # def mute(*args)
|
8
|
+
unless instrumenter # unless instrumenter
|
9
|
+
return yield if block_given? # return yield if block_given?
|
10
|
+
return # return
|
11
|
+
end # end
|
12
|
+
#
|
13
|
+
instrumenter.#{name}(*args) { yield } # instrumenter.mute(*args) { yield }
|
14
|
+
end # end
|
15
15
|
RUBY
|
16
16
|
else
|
17
17
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
18
|
-
def #{name}(*args)
|
19
|
-
instrumenter&.#{name}(*args)
|
20
|
-
end
|
18
|
+
def #{name}(*args) # def config(*args)
|
19
|
+
instrumenter&.#{name}(*args) # instrumenter&.config(*args)
|
20
|
+
end # end
|
21
21
|
RUBY
|
22
22
|
end
|
23
23
|
end
|