skylight 5.0.0.beta → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|