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
data/lib/skylight/helpers.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Skylight
|
|
|
14
14
|
|
|
15
15
|
if (opts = @__sk_instrument_next_method)
|
|
16
16
|
@__sk_instrument_next_method = nil
|
|
17
|
-
instrument_method(name, opts)
|
|
17
|
+
instrument_method(name, **opts)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -24,7 +24,7 @@ module Skylight
|
|
|
24
24
|
|
|
25
25
|
if (opts = @__sk_instrument_next_method)
|
|
26
26
|
@__sk_instrument_next_method = nil
|
|
27
|
-
instrument_class_method(name, opts)
|
|
27
|
+
instrument_class_method(name, **opts)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -77,14 +77,12 @@ module Skylight
|
|
|
77
77
|
# do_expensive_stuff
|
|
78
78
|
# end
|
|
79
79
|
# end
|
|
80
|
-
def instrument_method(*args)
|
|
81
|
-
opts = args.pop if args.last.is_a?(Hash)
|
|
82
|
-
|
|
80
|
+
def instrument_method(*args, **opts)
|
|
83
81
|
if (name = args.pop)
|
|
84
82
|
title = "#{self}##{name}"
|
|
85
|
-
__sk_instrument_method_on(self, name, title, opts
|
|
83
|
+
__sk_instrument_method_on(self, name, title, **opts)
|
|
86
84
|
else
|
|
87
|
-
@__sk_instrument_next_method = opts
|
|
85
|
+
@__sk_instrument_next_method = opts
|
|
88
86
|
end
|
|
89
87
|
end
|
|
90
88
|
|
|
@@ -123,47 +121,94 @@ module Skylight
|
|
|
123
121
|
#
|
|
124
122
|
# instrument_class_method :my_method, title: 'Expensive work'
|
|
125
123
|
# end
|
|
126
|
-
def instrument_class_method(name, opts
|
|
124
|
+
def instrument_class_method(name, **opts)
|
|
125
|
+
# NOTE: If the class is defined anonymously and then assigned to a variable this code
|
|
126
|
+
# will not be aware of the updated name.
|
|
127
127
|
title = "#{self}.#{name}"
|
|
128
|
-
__sk_instrument_method_on(__sk_singleton_class, name, title, opts
|
|
128
|
+
__sk_instrument_method_on(__sk_singleton_class, name, title, **opts)
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
private
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
HAS_ARGUMENT_FORWARDING = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
|
|
134
|
+
|
|
135
|
+
def __sk_instrument_method_on(klass, name, title, **opts)
|
|
136
|
+
category = (opts[:category] || "app.method").to_s
|
|
137
|
+
title = (opts[:title] || title).to_s
|
|
138
|
+
desc = opts[:description].to_s if opts[:description]
|
|
139
|
+
|
|
140
|
+
# NOTE: The source location logic happens before we have have a config so we can'
|
|
141
|
+
# check if source locations are enabled. However, it only happens once so the potential impact
|
|
142
|
+
# should be minimal. This would more appropriately belong to Extensions::SourceLocation,
|
|
143
|
+
# but as that is a runtime concern, and this happens at compile time, there isn't currently
|
|
144
|
+
# a clean way to turn this on and off. The absence of the extension will cause the
|
|
145
|
+
# source_file and source_line to be removed from the trace span before it is submitted.
|
|
146
|
+
source_file, source_line = klass.instance_method(name).source_location
|
|
137
147
|
|
|
138
|
-
|
|
139
|
-
|
|
148
|
+
# We should strongly prefer using the new argument-forwarding syntax (...) where available.
|
|
149
|
+
# In Ruby 2.7, the following are known to be syntax errors:
|
|
150
|
+
#
|
|
151
|
+
# - mixing positional arguments with argument forwarding (e.g., send(:method_name, ...))
|
|
152
|
+
# - calling a setter method with multiple arguments, unless dispatched via send or public_send.
|
|
153
|
+
#
|
|
154
|
+
# So it is possible, though not recommended, to define setter methods that take multiple arguments,
|
|
155
|
+
# keywords, and/or blocks. Unfortunately, this means that for setters, we still need to explicitly
|
|
156
|
+
# forward the different argument types.
|
|
157
|
+
is_setter_method = name.to_s.end_with?("=")
|
|
158
|
+
|
|
159
|
+
arg_string =
|
|
160
|
+
if HAS_ARGUMENT_FORWARDING
|
|
161
|
+
is_setter_method ? "*args, **kwargs, &blk" : "..."
|
|
162
|
+
else
|
|
163
|
+
"*args, &blk"
|
|
164
|
+
end
|
|
140
165
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
original_method_dispatch =
|
|
167
|
+
if is_setter_method
|
|
168
|
+
"self.send(:before_instrument_#{name}, #{arg_string})"
|
|
169
|
+
else
|
|
170
|
+
"before_instrument_#{name}(#{arg_string})"
|
|
171
|
+
end
|
|
146
172
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
173
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
174
|
+
alias_method :"before_instrument_#{name}", :"#{name}" # alias_method :"before_instrument_process", :"process"
|
|
175
|
+
def #{name}(#{arg_string}) # def process(*args, **kwargs, &blk)
|
|
176
|
+
span = Skylight.instrument( # span = Skylight.instrument(
|
|
177
|
+
category: :"#{category}", # category: :"app.method",
|
|
178
|
+
title: #{title.inspect}, # title: "process",
|
|
179
|
+
description: #{desc.inspect}, # description: "Process data",
|
|
180
|
+
source_file: #{source_file.inspect}, # source_file: "myapp/lib/processor.rb",
|
|
181
|
+
source_line: #{source_line.inspect}) # source_line: 123)
|
|
182
|
+
#
|
|
183
|
+
meta = {} # meta = {}
|
|
184
|
+
#
|
|
185
|
+
begin # begin
|
|
186
|
+
#{original_method_dispatch} # self.before_instrument_process(...)
|
|
187
|
+
rescue Exception => e # rescue Exception => e
|
|
188
|
+
meta[:exception_object] = e # meta[:exception_object] = e
|
|
189
|
+
raise e # raise e
|
|
190
|
+
ensure # ensure
|
|
191
|
+
Skylight.done(span, meta) if span # Skylight.done(span, meta) if span
|
|
192
|
+
end # end
|
|
193
|
+
end # end
|
|
194
|
+
#
|
|
195
|
+
if protected_method_defined?(:"before_instrument_#{name}") # if protected_method_defined?(:"before_instrument_process")
|
|
196
|
+
protected :"#{name}" # protected :"process"
|
|
197
|
+
elsif private_method_defined?(:"before_instrument_#{name}") # elsif private_method_defined?(:"before_instrument_process")
|
|
198
|
+
private :"#{name}" # private :"process"
|
|
199
|
+
end # end
|
|
157
200
|
RUBY
|
|
158
|
-
|
|
201
|
+
end
|
|
159
202
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
203
|
+
if respond_to?(:singleton_class)
|
|
204
|
+
alias __sk_singleton_class singleton_class
|
|
205
|
+
else
|
|
206
|
+
def __sk_singleton_class
|
|
207
|
+
class << self
|
|
208
|
+
self
|
|
165
209
|
end
|
|
166
210
|
end
|
|
211
|
+
end
|
|
167
212
|
end
|
|
168
213
|
|
|
169
214
|
# @api private
|
|
@@ -1,36 +1,354 @@
|
|
|
1
|
+
require "strscan"
|
|
2
|
+
require "securerandom"
|
|
3
|
+
require "skylight/util/logging"
|
|
4
|
+
require "skylight/extensions"
|
|
5
|
+
|
|
1
6
|
module Skylight
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
# @api private
|
|
8
|
+
class Instrumenter
|
|
9
|
+
KEY = :__skylight_current_trace
|
|
10
|
+
|
|
11
|
+
include Util::Logging
|
|
12
|
+
|
|
13
|
+
class TraceInfo
|
|
14
|
+
def initialize(key = KEY)
|
|
15
|
+
@key = key
|
|
16
|
+
@muted_key = "#{key}_muted"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def current
|
|
20
|
+
Thread.current[@key]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def current=(trace)
|
|
24
|
+
Thread.current[@key] = trace
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# NOTE: This should only be set by the instrumenter, and only
|
|
28
|
+
# in the context of a `mute` block. Do not try to turn this
|
|
29
|
+
# flag on and off directly.
|
|
30
|
+
def muted=(val)
|
|
31
|
+
Thread.current[@muted_key] = val
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def muted?
|
|
35
|
+
!!Thread.current[@muted_key]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :uuid, :config, :gc, :extensions, :subscriber
|
|
40
|
+
|
|
41
|
+
def self.native_new(_uuid, _config_env)
|
|
42
|
+
raise "not implemented"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.new(config)
|
|
46
|
+
config.validate!
|
|
47
|
+
|
|
48
|
+
uuid = SecureRandom.uuid
|
|
49
|
+
inst = native_new(uuid, config.to_native_env)
|
|
50
|
+
inst.send(:initialize, uuid, config)
|
|
51
|
+
inst
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def initialize(uuid, config)
|
|
55
|
+
@uuid = uuid
|
|
56
|
+
@gc = config.gc
|
|
57
|
+
@config = config
|
|
58
|
+
@subscriber = Skylight::Subscriber.new(config, self)
|
|
59
|
+
|
|
60
|
+
@trace_info = @config[:trace_info] || TraceInfo.new(KEY)
|
|
61
|
+
@mutex = Mutex.new
|
|
62
|
+
@extensions = Skylight::Extensions::Collection.new(@config)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def enable_extension!(name)
|
|
66
|
+
@mutex.synchronize { extensions.enable!(name) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def disable_extension!(name)
|
|
70
|
+
@mutex.synchronize { extensions.disable!(name) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def extension_enabled?(name)
|
|
74
|
+
extensions.enabled?(name)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def log_context
|
|
78
|
+
@log_context ||= { inst: uuid }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def native_start
|
|
82
|
+
raise "not implemented"
|
|
5
83
|
end
|
|
6
84
|
|
|
7
|
-
def
|
|
85
|
+
def native_stop
|
|
86
|
+
raise "not implemented"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def native_track_desc(_endpoint, _description)
|
|
90
|
+
raise "not implemented"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def native_submit_trace(_trace)
|
|
94
|
+
raise "not implemented"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def current_trace
|
|
98
|
+
@trace_info.current
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def current_trace=(trace)
|
|
102
|
+
t { "setting current_trace=#{trace ? trace.uuid : "nil"}; thread=#{Thread.current.object_id}" }
|
|
103
|
+
@trace_info.current = trace
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def validate_installation
|
|
8
107
|
# Warn if there was an error installing Skylight.
|
|
9
108
|
|
|
10
|
-
if defined?(Skylight.check_install_errors)
|
|
11
|
-
Skylight.check_install_errors(config)
|
|
12
|
-
end
|
|
109
|
+
Skylight.check_install_errors(config) if defined?(Skylight.check_install_errors)
|
|
13
110
|
|
|
14
111
|
if !Skylight.native? && defined?(Skylight.warn_skylight_native_missing)
|
|
15
112
|
Skylight.warn_skylight_native_missing(config)
|
|
16
|
-
return
|
|
113
|
+
return false
|
|
17
114
|
end
|
|
115
|
+
|
|
116
|
+
true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def muted=(val)
|
|
120
|
+
@trace_info.muted = val
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def muted?
|
|
124
|
+
@trace_info.muted?
|
|
18
125
|
end
|
|
19
126
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
127
|
+
def mute
|
|
128
|
+
old_muted = muted?
|
|
129
|
+
self.muted = true
|
|
130
|
+
yield if block_given?
|
|
131
|
+
ensure
|
|
132
|
+
self.muted = old_muted
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def unmute
|
|
136
|
+
old_muted = muted?
|
|
137
|
+
self.muted = false
|
|
138
|
+
yield if block_given?
|
|
139
|
+
ensure
|
|
140
|
+
self.muted = old_muted
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def silence_warnings(context)
|
|
144
|
+
@warnings_silenced || @mutex.synchronize { @warnings_silenced ||= {} }
|
|
145
|
+
|
|
146
|
+
@warnings_silenced[context] = true
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def warnings_silenced?(context)
|
|
150
|
+
@warnings_silenced && @warnings_silenced[context]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
alias disable mute
|
|
154
|
+
alias disabled? muted?
|
|
155
|
+
|
|
156
|
+
def start!
|
|
157
|
+
# We do this here since we can't report these issues via Gem install without stopping install entirely.
|
|
158
|
+
return unless validate_installation
|
|
159
|
+
|
|
160
|
+
t { "starting instrumenter" }
|
|
161
|
+
|
|
162
|
+
unless config.validate_with_server
|
|
163
|
+
log_error "invalid config"
|
|
164
|
+
return
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
t { "starting native instrumenter" }
|
|
168
|
+
unless native_start
|
|
169
|
+
warn "failed to start instrumenter"
|
|
170
|
+
return
|
|
27
171
|
end
|
|
172
|
+
|
|
173
|
+
enable_extension!(:source_location) if @config.enable_source_locations?
|
|
174
|
+
config.gc.enable
|
|
175
|
+
@subscriber.register!
|
|
176
|
+
|
|
177
|
+
ActiveSupport::Notifications.instrument("started_instrumenter.skylight", instrumenter: self)
|
|
178
|
+
|
|
179
|
+
self
|
|
180
|
+
rescue Exception => e
|
|
181
|
+
log_error "failed to start instrumenter; msg=%s; config=%s", e.message, config.inspect
|
|
182
|
+
t { e.backtrace.join("\n") }
|
|
28
183
|
nil
|
|
29
184
|
end
|
|
30
185
|
|
|
31
|
-
def
|
|
32
|
-
|
|
33
|
-
|
|
186
|
+
def shutdown
|
|
187
|
+
@subscriber.unregister!
|
|
188
|
+
native_stop
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def trace(endpoint, cat, title = nil, desc = nil, meta: nil, segment: nil, component: nil)
|
|
192
|
+
# If a trace is already in progress, continue with that one
|
|
193
|
+
if (trace = @trace_info.current)
|
|
194
|
+
return yield(trace) if block_given?
|
|
195
|
+
|
|
196
|
+
return trace
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
begin
|
|
200
|
+
meta ||= {}
|
|
201
|
+
extensions.process_trace_meta(meta)
|
|
202
|
+
trace =
|
|
203
|
+
Trace.new(
|
|
204
|
+
self,
|
|
205
|
+
endpoint,
|
|
206
|
+
Skylight::Util::Clock.nanos,
|
|
207
|
+
cat,
|
|
208
|
+
title,
|
|
209
|
+
desc,
|
|
210
|
+
meta: meta,
|
|
211
|
+
segment: segment,
|
|
212
|
+
component: component
|
|
213
|
+
)
|
|
214
|
+
rescue Exception => e
|
|
215
|
+
log_error e.message
|
|
216
|
+
t { e.backtrace.join("\n") }
|
|
217
|
+
return
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
@trace_info.current = trace
|
|
221
|
+
return trace unless block_given?
|
|
222
|
+
|
|
223
|
+
begin
|
|
224
|
+
yield trace
|
|
225
|
+
ensure
|
|
226
|
+
@trace_info.current = nil
|
|
227
|
+
t { "instrumenter submitting trace; trace=#{trace.uuid}" }
|
|
228
|
+
trace.submit
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def instrument(cat, title = nil, desc = nil, meta = nil)
|
|
233
|
+
raise ArgumentError, "cat is required" unless cat
|
|
234
|
+
|
|
235
|
+
if muted?
|
|
236
|
+
return yield if block_given?
|
|
237
|
+
|
|
238
|
+
return
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
unless (trace = @trace_info.current)
|
|
242
|
+
return yield if block_given?
|
|
243
|
+
|
|
244
|
+
return
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
cat = cat.to_s
|
|
248
|
+
|
|
249
|
+
unless Skylight::CATEGORY_REGEX.match?(cat)
|
|
250
|
+
warn "invalid skylight instrumentation category; trace=%s; value=%s", trace.uuid, cat
|
|
251
|
+
return yield if block_given?
|
|
252
|
+
|
|
253
|
+
return
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
cat = "other.#{cat}" unless Skylight::TIER_REGEX.match?(cat)
|
|
257
|
+
|
|
258
|
+
unless (sp = trace.instrument(cat, title, desc, meta))
|
|
259
|
+
return yield if block_given?
|
|
260
|
+
|
|
261
|
+
return
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
return sp unless block_given?
|
|
265
|
+
|
|
266
|
+
begin
|
|
267
|
+
yield sp
|
|
268
|
+
rescue Exception => e
|
|
269
|
+
meta ||= {}
|
|
270
|
+
meta[:exception] = [e.class.name, e.message]
|
|
271
|
+
meta[:exception_object] = e
|
|
272
|
+
raise e
|
|
273
|
+
ensure
|
|
274
|
+
trace.done(sp, meta)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def broken!
|
|
279
|
+
return unless (trace = @trace_info.current)
|
|
280
|
+
|
|
281
|
+
trace.broken!
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def poison!
|
|
285
|
+
@poisoned = true
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def poisoned?
|
|
289
|
+
@poisoned
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def done(span, meta = nil)
|
|
293
|
+
return unless (trace = @trace_info.current)
|
|
294
|
+
|
|
295
|
+
trace.done(span, meta)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def process(trace)
|
|
299
|
+
t { fmt "processing trace=#{trace.uuid}" }
|
|
300
|
+
|
|
301
|
+
if ignore?(trace)
|
|
302
|
+
t { fmt "ignoring trace=#{trace.uuid}" }
|
|
303
|
+
return false
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
begin
|
|
307
|
+
finalize_endpoint_segment(trace)
|
|
308
|
+
native_submit_trace(trace)
|
|
309
|
+
true
|
|
310
|
+
rescue StandardError => e
|
|
311
|
+
handle_instrumenter_error(trace, e)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def handle_instrumenter_error(trace, err)
|
|
316
|
+
poison! if err.is_a?(Skylight::InstrumenterUnrecoverableError)
|
|
317
|
+
|
|
318
|
+
warn "failed to submit trace to worker; trace=%s, err=%s", trace.uuid, err
|
|
319
|
+
t { "BACKTRACE:\n#{err.backtrace.join("\n")}" }
|
|
320
|
+
|
|
321
|
+
false
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def ignore?(trace)
|
|
325
|
+
config.ignored_endpoints.include?(trace.endpoint)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Because GraphQL can return multiple results, each of which
|
|
329
|
+
# may have their own success/error states, we need to set the
|
|
330
|
+
# skylight segment as follows:
|
|
331
|
+
#
|
|
332
|
+
# - when all queries have errors: "error"
|
|
333
|
+
# - when some queries have errors: "<rendered format>+error"
|
|
334
|
+
# - when no queries have errors: "<rendered format>"
|
|
335
|
+
#
|
|
336
|
+
# <rendered format> will be determined by the Rails controller as usual.
|
|
337
|
+
# See Instrumenter#finalize_endpoint_segment for the actual segment/error assignment.
|
|
338
|
+
def finalize_endpoint_segment(trace)
|
|
339
|
+
return unless (segment = trace.segment)
|
|
340
|
+
|
|
341
|
+
segment =
|
|
342
|
+
case trace.compound_response_error_status
|
|
343
|
+
when :all
|
|
344
|
+
"error"
|
|
345
|
+
when :partial
|
|
346
|
+
"#{segment}+error"
|
|
347
|
+
else
|
|
348
|
+
segment
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
trace.endpoint += "<sk-segment>#{segment}</sk-segment>"
|
|
34
352
|
end
|
|
35
353
|
end
|
|
36
354
|
end
|
data/lib/skylight/middleware.rb
CHANGED
|
@@ -1,4 +1,150 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
|
|
1
3
|
module Skylight
|
|
2
|
-
|
|
4
|
+
# @api private
|
|
5
|
+
class Middleware
|
|
6
|
+
SKYLIGHT_REQUEST_ID = "skylight.request_id".freeze
|
|
7
|
+
|
|
8
|
+
class BodyProxy
|
|
9
|
+
def initialize(body, &block)
|
|
10
|
+
@body = body
|
|
11
|
+
@block = block
|
|
12
|
+
@closed = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def respond_to_missing?(name, include_all = false)
|
|
16
|
+
return false if name.to_s !~ /^to_ary$/
|
|
17
|
+
|
|
18
|
+
@body.respond_to?(name, include_all)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def close
|
|
22
|
+
return if @closed
|
|
23
|
+
|
|
24
|
+
@closed = true
|
|
25
|
+
begin
|
|
26
|
+
@body.close if @body.respond_to? :close
|
|
27
|
+
ensure
|
|
28
|
+
@block.call
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def closed?
|
|
33
|
+
@closed
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# N.B. This method is a special case to address the bug described by
|
|
37
|
+
# https://github.com/rack/rack/issues/434.
|
|
38
|
+
# We are applying this special case for #each only. Future bugs of this
|
|
39
|
+
# class will be handled by requesting users to patch their ruby
|
|
40
|
+
# implementation, to save adding too many methods in this class.
|
|
41
|
+
def each(*args, &block)
|
|
42
|
+
@body.each(*args, &block)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def method_missing(*args, &block)
|
|
46
|
+
super if args.first.to_s =~ /^to_ary$/
|
|
47
|
+
@body.__send__(*args, &block)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.with_after_close(resp, debug_identifier: "unknown", &block)
|
|
52
|
+
unless resp.respond_to?(:to_ary)
|
|
53
|
+
if resp.respond_to?(:to_a)
|
|
54
|
+
Skylight.warn(
|
|
55
|
+
"Rack response from \"#{debug_identifier}\" cannot be implicitly converted to an array. " \
|
|
56
|
+
"This is in violation of the Rack SPEC and will raise an error in future versions."
|
|
57
|
+
)
|
|
58
|
+
resp = resp.to_a
|
|
59
|
+
else
|
|
60
|
+
Skylight.error(
|
|
61
|
+
"Rack response from \"#{debug_identifier}\" cannot be converted to an array. This is in " \
|
|
62
|
+
"violation of the Rack SPEC and may cause problems with Skylight operation."
|
|
63
|
+
)
|
|
64
|
+
return resp
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
status, headers, body = resp
|
|
69
|
+
[status, headers, BodyProxy.new(body, &block)]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
include Skylight::Util::Logging
|
|
73
|
+
|
|
74
|
+
# For Util::Logging
|
|
75
|
+
attr_reader :config
|
|
76
|
+
|
|
77
|
+
def initialize(app, opts = {})
|
|
78
|
+
@app = app
|
|
79
|
+
@config = opts[:config]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def call(env)
|
|
83
|
+
set_request_id(env)
|
|
84
|
+
|
|
85
|
+
if Skylight.tracing?
|
|
86
|
+
debug "Already instrumenting. Make sure the Skylight Rack Middleware hasn't been added more than once."
|
|
87
|
+
return @app.call(env)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if env["REQUEST_METHOD"] == "HEAD"
|
|
91
|
+
t { "middleware skipping HEAD" }
|
|
92
|
+
@app.call(env)
|
|
93
|
+
else
|
|
94
|
+
begin
|
|
95
|
+
t { "middleware beginning trace" }
|
|
96
|
+
trace = Skylight.trace(endpoint_name(env), "app.rack.request", nil, meta: endpoint_meta(env), component: :web)
|
|
97
|
+
t { "middleware began trace=#{trace ? trace.uuid : nil}" }
|
|
98
|
+
|
|
99
|
+
resp = @app.call(env)
|
|
100
|
+
|
|
101
|
+
trace ? Middleware.with_after_close(resp, debug_identifier: "Rack App: #{@app.class}") { trace.submit } : resp
|
|
102
|
+
rescue Exception => e
|
|
103
|
+
t { "middleware exception: #{e}\n#{e.backtrace.join("\n")}" }
|
|
104
|
+
trace&.submit
|
|
105
|
+
raise
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def log_context
|
|
113
|
+
# Don't cache this, it will change
|
|
114
|
+
{ request_id: current_request_id, inst: Skylight.instrumenter&.uuid }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Allow for overwriting
|
|
118
|
+
def endpoint_name(_env)
|
|
119
|
+
"Rack"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def endpoint_meta(_env)
|
|
123
|
+
{ source_location: Trace::SYNTHETIC }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Request ID code based on ActionDispatch::RequestId
|
|
127
|
+
def set_request_id(env)
|
|
128
|
+
return if env[SKYLIGHT_REQUEST_ID]
|
|
129
|
+
|
|
130
|
+
existing_request_id = env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
|
|
131
|
+
self.current_request_id = env[SKYLIGHT_REQUEST_ID] = make_request_id(existing_request_id)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def make_request_id(request_id)
|
|
135
|
+
request_id && !request_id.empty? ? request_id.gsub(/[^\w\-]/, "".freeze)[0...255] : internal_request_id
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def internal_request_id
|
|
139
|
+
SecureRandom.uuid
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def current_request_id
|
|
143
|
+
Thread.current[SKYLIGHT_REQUEST_ID]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def current_request_id=(request_id)
|
|
147
|
+
Thread.current[SKYLIGHT_REQUEST_ID] = request_id
|
|
148
|
+
end
|
|
3
149
|
end
|
|
4
150
|
end
|