skylight 3.1.4 → 5.3.4
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 +465 -294
- data/CLA.md +1 -1
- data/CONTRIBUTING.md +11 -3
- data/ERRORS.md +3 -0
- data/LICENSE.md +8 -18
- data/README.md +1 -2
- data/bin/skylight +1 -1
- data/ext/extconf.rb +118 -122
- data/ext/libskylight.yml +8 -6
- data/ext/skylight_native.c +56 -100
- data/lib/skylight/api.rb +41 -27
- data/lib/skylight/cli/doctor.rb +68 -70
- data/lib/skylight/cli/helpers.rb +3 -5
- data/lib/skylight/cli/merger.rb +99 -92
- data/lib/skylight/cli.rb +40 -43
- data/lib/skylight/config.rb +656 -201
- data/lib/skylight/data/cacert.pem +730 -1023
- data/lib/skylight/deprecation.rb +17 -0
- data/lib/skylight/errors.rb +34 -16
- 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 +82 -39
- data/lib/skylight/instrumenter.rb +339 -9
- data/lib/skylight/middleware.rb +147 -1
- data/lib/skylight/native.rb +71 -23
- data/lib/skylight/native_ext_fetcher.rb +39 -47
- 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 +45 -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 +169 -0
- data/lib/skylight/probes/mongoid.rb +6 -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 +68 -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 +172 -0
- data/lib/skylight/railtie.rb +172 -15
- data/lib/skylight/sidekiq.rb +47 -0
- data/lib/skylight/sinatra.rb +2 -2
- data/lib/skylight/subscriber.rb +130 -0
- data/lib/skylight/test.rb +147 -0
- data/lib/skylight/trace.rb +331 -15
- data/lib/skylight/user_config.rb +60 -0
- data/lib/skylight/util/allocation_free.rb +26 -0
- data/lib/skylight/util/clock.rb +57 -0
- data/lib/skylight/util/component.rb +47 -9
- data/lib/skylight/util/deploy.rb +24 -40
- data/lib/skylight/util/gzip.rb +20 -0
- data/lib/skylight/util/hostname.rb +4 -4
- data/lib/skylight/util/http.rb +62 -71
- 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 +74 -0
- data/lib/skylight/util/proxy.rb +13 -0
- data/lib/skylight/util/ssl.rb +4 -28
- 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 +213 -24
- metadata +171 -53
data/lib/skylight/helpers.rb
CHANGED
|
@@ -6,16 +6,15 @@ module Skylight
|
|
|
6
6
|
# into the class that you will be instrumenting. Then, annotate each method that
|
|
7
7
|
# you wish to instrument with {Skylight::Helpers::ClassMethods#instrument_method instrument_method}.
|
|
8
8
|
module Helpers
|
|
9
|
-
|
|
10
9
|
# @see Skylight::Helpers
|
|
11
10
|
module ClassMethods
|
|
12
11
|
# @api private
|
|
13
12
|
def method_added(name)
|
|
14
13
|
super
|
|
15
14
|
|
|
16
|
-
if opts = @__sk_instrument_next_method
|
|
15
|
+
if (opts = @__sk_instrument_next_method)
|
|
17
16
|
@__sk_instrument_next_method = nil
|
|
18
|
-
instrument_method(name, opts)
|
|
17
|
+
instrument_method(name, **opts)
|
|
19
18
|
end
|
|
20
19
|
end
|
|
21
20
|
|
|
@@ -23,9 +22,9 @@ module Skylight
|
|
|
23
22
|
def singleton_method_added(name)
|
|
24
23
|
super
|
|
25
24
|
|
|
26
|
-
if opts = @__sk_instrument_next_method
|
|
25
|
+
if (opts = @__sk_instrument_next_method)
|
|
27
26
|
@__sk_instrument_next_method = nil
|
|
28
|
-
instrument_class_method(name, opts)
|
|
27
|
+
instrument_class_method(name, **opts)
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
30
|
|
|
@@ -78,14 +77,12 @@ module Skylight
|
|
|
78
77
|
# do_expensive_stuff
|
|
79
78
|
# end
|
|
80
79
|
# end
|
|
81
|
-
def instrument_method(*args)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
title = "#{to_s}##{name}"
|
|
86
|
-
__sk_instrument_method_on(self, name, title, opts || {})
|
|
80
|
+
def instrument_method(*args, **opts)
|
|
81
|
+
if (name = args.pop)
|
|
82
|
+
title = "#{self}##{name}"
|
|
83
|
+
__sk_instrument_method_on(self, name, title, **opts)
|
|
87
84
|
else
|
|
88
|
-
@__sk_instrument_next_method = opts
|
|
85
|
+
@__sk_instrument_next_method = opts
|
|
89
86
|
end
|
|
90
87
|
end
|
|
91
88
|
|
|
@@ -124,45 +121,92 @@ module Skylight
|
|
|
124
121
|
#
|
|
125
122
|
# instrument_class_method :my_method, title: 'Expensive work'
|
|
126
123
|
# end
|
|
127
|
-
def instrument_class_method(name, opts
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
title = "#{self}.#{name}"
|
|
128
|
+
__sk_instrument_method_on(__sk_singleton_class, name, title, **opts)
|
|
130
129
|
end
|
|
131
130
|
|
|
132
|
-
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
HAS_ARGUMENT_FORWARDING = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
|
|
133
134
|
|
|
134
|
-
def __sk_instrument_method_on(klass, name, title, opts)
|
|
135
|
+
def __sk_instrument_method_on(klass, name, title, **opts)
|
|
135
136
|
category = (opts[:category] || "app.method").to_s
|
|
136
|
-
title
|
|
137
|
-
desc
|
|
137
|
+
title = (opts[:title] || title).to_s
|
|
138
|
+
desc = opts[:description].to_s if opts[:description]
|
|
138
139
|
|
|
139
|
-
|
|
140
|
-
|
|
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
|
|
147
|
+
|
|
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?("=")
|
|
141
158
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
159
|
+
arg_string =
|
|
160
|
+
if HAS_ARGUMENT_FORWARDING
|
|
161
|
+
is_setter_method ? "*args, **kwargs, &blk" : "..."
|
|
162
|
+
else
|
|
163
|
+
"*args, &blk"
|
|
164
|
+
end
|
|
147
165
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
raise e
|
|
154
|
-
ensure
|
|
155
|
-
Skylight.done(span, meta) if span
|
|
156
|
-
end
|
|
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})"
|
|
157
171
|
end
|
|
158
|
-
|
|
172
|
+
|
|
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
|
|
200
|
+
RUBY
|
|
159
201
|
end
|
|
160
202
|
|
|
161
203
|
if respond_to?(:singleton_class)
|
|
162
|
-
alias
|
|
204
|
+
alias __sk_singleton_class singleton_class
|
|
163
205
|
else
|
|
164
206
|
def __sk_singleton_class
|
|
165
|
-
class << self
|
|
207
|
+
class << self
|
|
208
|
+
self
|
|
209
|
+
end
|
|
166
210
|
end
|
|
167
211
|
end
|
|
168
212
|
end
|
|
@@ -174,6 +218,5 @@ module Skylight
|
|
|
174
218
|
extend ClassMethods
|
|
175
219
|
end
|
|
176
220
|
end
|
|
177
|
-
|
|
178
221
|
end
|
|
179
222
|
end
|
|
@@ -1,24 +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"
|
|
83
|
+
end
|
|
84
|
+
|
|
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
|
|
5
104
|
end
|
|
6
105
|
|
|
7
|
-
def
|
|
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)
|
|
113
|
+
return false
|
|
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?
|
|
125
|
+
end
|
|
126
|
+
|
|
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"
|
|
16
164
|
return
|
|
17
165
|
end
|
|
166
|
+
|
|
167
|
+
t { "starting native instrumenter" }
|
|
168
|
+
unless native_start
|
|
169
|
+
warn "failed to start instrumenter"
|
|
170
|
+
return
|
|
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") }
|
|
183
|
+
nil
|
|
184
|
+
end
|
|
185
|
+
|
|
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
|
|
18
230
|
end
|
|
19
231
|
|
|
20
|
-
def
|
|
21
|
-
|
|
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>"
|
|
22
352
|
end
|
|
23
353
|
end
|
|
24
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
|