sqreen 1.20.1-java → 1.21.0.beta3-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +16 -0
- data/lib/sqreen/actions/block_user.rb +1 -1
- data/lib/sqreen/actions/redirect_ip.rb +1 -1
- data/lib/sqreen/actions/redirect_user.rb +1 -1
- data/lib/sqreen/attack_detected.html +1 -2
- data/lib/sqreen/condition_evaluator.rb +9 -2
- data/lib/sqreen/conditionable.rb +24 -6
- data/lib/sqreen/configuration.rb +1 -1
- data/lib/sqreen/deferred_logger.rb +50 -14
- data/lib/sqreen/deliveries/batch.rb +8 -1
- data/lib/sqreen/deprecation.rb +38 -0
- data/lib/sqreen/ecosystem.rb +96 -0
- data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
- data/lib/sqreen/ecosystem/exception_reporting.rb +26 -0
- data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
- data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
- data/lib/sqreen/ecosystem/loggable.rb +13 -0
- data/lib/sqreen/ecosystem/module_api.rb +30 -0
- data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
- data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
- data/lib/sqreen/ecosystem/module_api/message_producer.rb +51 -0
- data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
- data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
- data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
- data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
- data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
- data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
- data/lib/sqreen/ecosystem/module_registry.rb +44 -0
- data/lib/sqreen/ecosystem/redis/redis_connection.rb +43 -0
- data/lib/sqreen/ecosystem/tracing/modules/client.rb +31 -0
- data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
- data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
- data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
- data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
- data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
- data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
- data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
- data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
- data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
- data/lib/sqreen/ecosystem_integration.rb +87 -0
- data/lib/sqreen/ecosystem_integration/around_callbacks.rb +99 -0
- data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +42 -0
- data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
- data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
- data/lib/sqreen/events/request_record.rb +0 -1
- data/lib/sqreen/frameworks/generic.rb +24 -1
- data/lib/sqreen/frameworks/rails.rb +0 -7
- data/lib/sqreen/frameworks/request_recorder.rb +2 -0
- data/lib/sqreen/graft/call.rb +106 -19
- data/lib/sqreen/graft/callback.rb +1 -1
- data/lib/sqreen/graft/hook.rb +212 -100
- data/lib/sqreen/graft/hook_point.rb +18 -11
- data/lib/sqreen/legacy/instrumentation.rb +22 -10
- data/lib/sqreen/legacy/old_event_submission_strategy.rb +9 -2
- data/lib/sqreen/log.rb +3 -2
- data/lib/sqreen/log/loggable.rb +1 -0
- data/lib/sqreen/logger.rb +24 -0
- data/lib/sqreen/metrics.rb +1 -0
- data/lib/sqreen/metrics/req_detailed.rb +41 -0
- data/lib/sqreen/metrics_store.rb +11 -0
- data/lib/sqreen/null_logger.rb +22 -0
- data/lib/sqreen/remote_command.rb +4 -0
- data/lib/sqreen/rules.rb +8 -4
- data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
- data/lib/sqreen/rules/custom_error_cb.rb +3 -3
- data/lib/sqreen/rules/rule_cb.rb +4 -2
- data/lib/sqreen/rules/waf_cb.rb +3 -3
- data/lib/sqreen/runner.rb +63 -8
- data/lib/sqreen/session.rb +2 -0
- data/lib/sqreen/signals/conversions.rb +6 -1
- data/lib/sqreen/version.rb +1 -1
- data/lib/sqreen/weave/budget.rb +35 -0
- data/lib/sqreen/weave/legacy/instrumentation.rb +274 -132
- data/lib/sqreen/worker.rb +6 -2
- metadata +46 -9
- data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'sqreen/graft/hook'
|
2
|
+
require 'sqreen/ecosystem_integration/around_callbacks'
|
3
|
+
|
4
|
+
module Sqreen
|
5
|
+
class EcosystemIntegration
|
6
|
+
module InstrumentationService
|
7
|
+
class << self
|
8
|
+
# @param [String] module_name
|
9
|
+
# @param [String] method in form A::B#c or A::B.c
|
10
|
+
# @param [Hash{Symbol=>Proc}] spec
|
11
|
+
def instrument(module_name, method, spec)
|
12
|
+
hook = Sqreen::Graft::Hook[method].add do
|
13
|
+
if spec[:before]
|
14
|
+
cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'pre', spec[:before])
|
15
|
+
tag = "weave,rule=ecosystem_#{module_name}"
|
16
|
+
before(tag, flow: true, &cb)
|
17
|
+
end
|
18
|
+
|
19
|
+
if spec[:after]
|
20
|
+
cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'post', spec[:after])
|
21
|
+
tag = "weave,rule=ecosystem_#{module_name}"
|
22
|
+
after(tag, flow: true, &cb)
|
23
|
+
end
|
24
|
+
|
25
|
+
if spec[:raised]
|
26
|
+
cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'failing', spec[:raised])
|
27
|
+
tag = "weave,rule=ecosystem_#{module_name}"
|
28
|
+
raised(tag, flow: true, &cb)
|
29
|
+
end
|
30
|
+
|
31
|
+
if spec[:ensured]
|
32
|
+
cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'finally', spec[:ensured])
|
33
|
+
tag = "weave,rule=ecosystem_#{module_name}"
|
34
|
+
ensured(tag, flow: true, &cb)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
hook.install
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'sqreen/events/remote_exception'
|
2
|
+
require 'sqreen/log/loggable'
|
3
|
+
|
4
|
+
module Sqreen
|
5
|
+
class EcosystemIntegration
|
6
|
+
# This class gets notified of request start/end and
|
7
|
+
# 1) distributes such events to listeners (typically ecosystem modules;
|
8
|
+
# the method add_start_observer is exposed to ecosystem modules through
|
9
|
+
# +Sqreen::Ecosystem::ModuleApi::EventListener+ and the dispatch table).
|
10
|
+
# 2) keeps track of whether a request is active on this thread. This is
|
11
|
+
# so that users of this class can have this information without needing
|
12
|
+
# to subscribe to request start/events and keeping thread local state
|
13
|
+
# themselves.
|
14
|
+
# XXX: Since the Ecosystem is also notified of request start/end, it could
|
15
|
+
# notify its modules of request start without going through the dispatch
|
16
|
+
# table and call add_start_observer. We need to think if we want to keep
|
17
|
+
# the transaction / request distinction or if they should just be
|
18
|
+
# assumed to be the same, though.
|
19
|
+
class RequestLifecycleTracking
|
20
|
+
include Sqreen::Log::Loggable
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@start_observers = []
|
24
|
+
@tl_key = "#{object_id}_req_in_flight"
|
25
|
+
end
|
26
|
+
|
27
|
+
# API for classes needing to know the request state
|
28
|
+
|
29
|
+
# @param cb A callback taking a Rack::Request
|
30
|
+
def add_start_observer(cb)
|
31
|
+
@start_observers << cb
|
32
|
+
end
|
33
|
+
|
34
|
+
def in_request?
|
35
|
+
Thread.current[@tl_key] ? true : false
|
36
|
+
end
|
37
|
+
|
38
|
+
# API for classes notifying this one of request events
|
39
|
+
|
40
|
+
def notify_request_start(rack_req)
|
41
|
+
Thread.current[@tl_key] = true
|
42
|
+
return if @start_observers.empty?
|
43
|
+
@start_observers.each do |cb|
|
44
|
+
begin
|
45
|
+
cb.call(rack_req)
|
46
|
+
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
47
|
+
logger.warn { "Error calling #{cb} on request start: #{e.message}" }
|
48
|
+
Sqreen::RemoteException.record(e)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def notify_request_end
|
54
|
+
Thread.current[@tl_key] = false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'sqreen/log/loggable'
|
2
|
+
|
3
|
+
module Sqreen
|
4
|
+
class EcosystemIntegration
|
5
|
+
class SignalConsumption
|
6
|
+
include Sqreen::Log::Loggable
|
7
|
+
|
8
|
+
PAYLOAD_CREATOR_SECTIONS = %w[request response params headers].freeze
|
9
|
+
|
10
|
+
# @param [Sqreen::Frameworks::GenericFramework] framework
|
11
|
+
# @param [Sqreen::EcosystemIntegration::RequestLifecycleTracking]
|
12
|
+
# @param [Sqreen::CappedQueue]
|
13
|
+
def initialize(framework, req_lifecycle, queue)
|
14
|
+
@framework = framework
|
15
|
+
@req_lifecycle = req_lifecycle
|
16
|
+
@queue = queue
|
17
|
+
end
|
18
|
+
|
19
|
+
def consume_signal(signal)
|
20
|
+
# transitional
|
21
|
+
unless Sqreen.features.fetch('use_signals', DEFAULT_USE_SIGNALS)
|
22
|
+
logger.debug { "Discarding signal #{signal} (signals disabled)" }
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
if @req_lifecycle.in_request?
|
27
|
+
# add it to the request record
|
28
|
+
@framework.observe(:signals, signal, PAYLOAD_CREATOR_SECTIONS, true)
|
29
|
+
else
|
30
|
+
@queue.push signal
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -22,8 +22,17 @@ module Sqreen
|
|
22
22
|
include RequestRecorder
|
23
23
|
attr_accessor :sqreen_configuration
|
24
24
|
|
25
|
+
attr_writer :req_start_cb, :req_end_cb
|
26
|
+
|
25
27
|
def initialize
|
26
28
|
clean_request_record
|
29
|
+
|
30
|
+
# for notifying the ecosystem of request boundaries
|
31
|
+
# XXX: this should be refactored. It shouldn't be
|
32
|
+
# the framework doing these notifications to the ecosystem
|
33
|
+
# Probably the rule callback should do it itself
|
34
|
+
@req_start_cb = Proc.new {}
|
35
|
+
@req_end_cb = Proc.new {}
|
27
36
|
end
|
28
37
|
|
29
38
|
# What kind of database is this
|
@@ -209,7 +218,16 @@ module Sqreen
|
|
209
218
|
|
210
219
|
# Should the agent not be starting up?
|
211
220
|
def prevent_startup
|
221
|
+
# SQREEN-880 - prevent Sqreen startup on Sidekiq workers
|
222
|
+
return :sidekiq_cli if defined?(Sidekiq::CLI)
|
223
|
+
return :delayed_job if defined?(Delayed::Command)
|
224
|
+
|
225
|
+
# Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
|
226
|
+
run_in_test = sqreen_configuration.get(:run_in_test)
|
227
|
+
return :rake if !run_in_test && $0.end_with?('rake')
|
228
|
+
|
212
229
|
return :irb if $0 == 'irb'
|
230
|
+
|
213
231
|
return if sqreen_configuration.nil?
|
214
232
|
disable = sqreen_configuration.get(:disable)
|
215
233
|
return :config_disable if disable == true || disable.to_s.to_i == 1
|
@@ -251,8 +269,12 @@ module Sqreen
|
|
251
269
|
# Nota: cleanup should be performed at end of request (see clean_request)
|
252
270
|
def store_request(object)
|
253
271
|
return unless ensure_rack_loaded
|
272
|
+
|
273
|
+
rack_req = Rack::Request.new(object)
|
274
|
+
@req_start_cb.call(rack_req)
|
275
|
+
|
254
276
|
self.remaining_perf_budget = Sqreen.performance_budget
|
255
|
-
SharedStorage.set(:request,
|
277
|
+
SharedStorage.set(:request, rack_req)
|
256
278
|
SharedStorage.set(:xss_params, nil)
|
257
279
|
SharedStorage.set(:whitelisted, nil)
|
258
280
|
SharedStorage.set(:request_overtime, nil)
|
@@ -281,6 +303,7 @@ module Sqreen
|
|
281
303
|
SharedStorage.set(:xss_params, nil)
|
282
304
|
SharedStorage.set(:whitelisted, nil)
|
283
305
|
SharedStorage.set(:request_overtime, nil)
|
306
|
+
@req_end_cb.call
|
284
307
|
end
|
285
308
|
|
286
309
|
def remaining_perf_budget
|
@@ -103,13 +103,6 @@ module Sqreen
|
|
103
103
|
run_in_test = sqreen_configuration.get(:run_in_test)
|
104
104
|
return :rails_test if !run_in_test && (Rails.env.test? || Rails.env.cucumber?)
|
105
105
|
|
106
|
-
# SQREEN-880 - prevent Sqreen startup on Sidekiq workers
|
107
|
-
return :sidekiq_cli if defined?(Sidekiq::CLI)
|
108
|
-
return :delayed_job if defined?(Delayed::Command)
|
109
|
-
|
110
|
-
# Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
|
111
|
-
return :rake if !run_in_test && $0.end_with?('rake')
|
112
|
-
|
113
106
|
return nil unless defined?(Rails::CommandsTasks)
|
114
107
|
return nil if defined?(Rails::Server)
|
115
108
|
return :rails_console if defined?(Rails::Console)
|
@@ -69,6 +69,8 @@ module Sqreen
|
|
69
69
|
|
70
70
|
# signals require request section to be present
|
71
71
|
payload_requests << 'request'
|
72
|
+
# for signals, response is optional, but the backend team wants them
|
73
|
+
payload_requests << 'response'
|
72
74
|
payload = payload_creator.payload(payload_requests)
|
73
75
|
payload[:observed] = observed_items
|
74
76
|
|
data/lib/sqreen/graft/call.rb
CHANGED
@@ -27,6 +27,10 @@ module Sqreen
|
|
27
27
|
def raise(value)
|
28
28
|
Flow.raise(value)
|
29
29
|
end
|
30
|
+
|
31
|
+
def noop
|
32
|
+
Flow.noop
|
33
|
+
end
|
30
34
|
end
|
31
35
|
|
32
36
|
class Flow
|
@@ -46,12 +50,17 @@ module Sqreen
|
|
46
50
|
def raise(value)
|
47
51
|
new(:raise, value)
|
48
52
|
end
|
53
|
+
|
54
|
+
def noop
|
55
|
+
new(:noop, nil)
|
56
|
+
end
|
49
57
|
end
|
50
58
|
|
51
59
|
def initialize(action, value, brk = false)
|
52
60
|
@action = action
|
53
61
|
@value = value
|
54
62
|
@break = brk
|
63
|
+
@passed_conditions = false
|
55
64
|
end
|
56
65
|
|
57
66
|
def return?
|
@@ -91,60 +100,138 @@ module Sqreen
|
|
91
100
|
def break?
|
92
101
|
@break ? true : false
|
93
102
|
end
|
103
|
+
|
104
|
+
def passed_conditions!
|
105
|
+
@passed_conditions = true
|
106
|
+
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def passed_conditions?
|
111
|
+
@passed_conditions
|
112
|
+
end
|
94
113
|
end
|
95
114
|
|
115
|
+
class TimerError < StandardError; end
|
116
|
+
|
96
117
|
class Timer
|
97
118
|
def self.read
|
98
119
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
99
120
|
end
|
100
121
|
|
101
|
-
attr_reader :tag
|
122
|
+
attr_reader :tag, :size
|
123
|
+
attr_accessor :conditions_passed
|
102
124
|
|
103
125
|
def initialize(tag, &block)
|
104
126
|
@tag = tag
|
105
|
-
@blips = []
|
106
127
|
@block = block
|
128
|
+
@tally = 0
|
129
|
+
@size = 0
|
107
130
|
end
|
108
131
|
|
109
|
-
def
|
110
|
-
|
132
|
+
def elapsed
|
133
|
+
raise(TimerError, 'Timer#elapsed when paused') if @size.even?
|
134
|
+
|
135
|
+
@tally + Timer.read
|
111
136
|
end
|
112
137
|
|
113
|
-
def
|
114
|
-
|
138
|
+
def duration
|
139
|
+
raise(TimerError, 'Timer#duration when running') if @size.odd?
|
140
|
+
|
141
|
+
@tally
|
115
142
|
end
|
116
143
|
|
117
144
|
def ignore
|
118
|
-
|
145
|
+
raise(TimerError, 'Timer#ignore when paused') if @size.even?
|
146
|
+
|
147
|
+
@size += 1
|
148
|
+
@tally += Timer.read
|
119
149
|
yield(self)
|
120
150
|
ensure
|
121
|
-
@
|
151
|
+
@size += 1
|
152
|
+
@tally -= Timer.read
|
122
153
|
end
|
123
154
|
|
124
|
-
def measure
|
125
|
-
|
155
|
+
def measure(opts = nil)
|
156
|
+
raise(TimerError, 'Timer#measure when running') if @size.odd?
|
157
|
+
|
158
|
+
now = Timer.read
|
159
|
+
|
160
|
+
ignore = opts[:ignore] if opts
|
161
|
+
if ignore
|
162
|
+
ignore.size += 1
|
163
|
+
ignore.tally += now
|
164
|
+
end
|
165
|
+
|
166
|
+
@size += 1
|
167
|
+
@tally -= now
|
168
|
+
|
126
169
|
yield(self)
|
127
170
|
ensure
|
128
|
-
|
171
|
+
now = Timer.read
|
172
|
+
|
173
|
+
if ignore
|
174
|
+
ignore.size += 1
|
175
|
+
ignore.tally -= now
|
176
|
+
end
|
177
|
+
|
178
|
+
@size += 1
|
179
|
+
@tally += now
|
180
|
+
|
129
181
|
@block.call(self) if @block
|
130
|
-
Sqreen::Graft.logger.debug { "#{@tag}: time=%.03fus" % (duration * 1_000_000) }
|
131
182
|
end
|
132
183
|
|
133
|
-
def start
|
134
|
-
|
184
|
+
def start(at = Timer.read)
|
185
|
+
raise(TimerError, 'Timer#start when started') unless @size.even?
|
186
|
+
|
187
|
+
@size += 1
|
188
|
+
@tally -= at
|
189
|
+
|
190
|
+
at
|
191
|
+
end
|
192
|
+
|
193
|
+
def stop(at = Timer.read)
|
194
|
+
raise(TimerError, 'Timer#stop when unstarted') unless @size.odd?
|
195
|
+
|
196
|
+
@size += 1
|
197
|
+
@tally += at
|
198
|
+
|
199
|
+
at
|
135
200
|
end
|
136
201
|
|
137
|
-
def
|
138
|
-
@
|
202
|
+
def started?
|
203
|
+
@size != 0 && @size.odd?
|
139
204
|
end
|
140
205
|
|
141
|
-
def
|
142
|
-
@
|
206
|
+
def stopped?
|
207
|
+
@size != 0 && @size.even?
|
208
|
+
end
|
209
|
+
|
210
|
+
def running?
|
211
|
+
@size.odd?
|
212
|
+
end
|
213
|
+
|
214
|
+
def paused?
|
215
|
+
@size.even?
|
216
|
+
end
|
217
|
+
|
218
|
+
def include_measurements(another_timer)
|
219
|
+
@blips += another_timer.instance_variable_get(:@blips)
|
220
|
+
end
|
221
|
+
|
222
|
+
def start_and_end
|
223
|
+
raise 'Not exactly two measurements recorded' unless size == 2
|
224
|
+
@blips
|
143
225
|
end
|
144
226
|
|
145
227
|
def to_s
|
146
|
-
"#{@tag}: time=%.03fus" % (
|
228
|
+
"#{@tag}: time=%.03fus" % (@tally * 1_000_000)
|
147
229
|
end
|
230
|
+
|
231
|
+
protected
|
232
|
+
|
233
|
+
attr_reader :tally
|
234
|
+
attr_writer :size, :tally
|
148
235
|
end
|
149
236
|
end
|
150
237
|
end
|
@@ -21,7 +21,7 @@ module Sqreen
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def call(*args, &block)
|
24
|
-
Sqreen::Graft.logger.debug { "[#{Process.pid}] Callback #{@name} disabled:#{disabled?}" }
|
24
|
+
# Sqreen::Graft.logger.debug { "[#{Process.pid}] Callback #{@name} disabled:#{disabled?}" } if Sqreen::Graft.logger.debug?
|
25
25
|
return if @disabled
|
26
26
|
@block.call(*args, &block)
|
27
27
|
end
|
data/lib/sqreen/graft/hook.rb
CHANGED
@@ -7,23 +7,34 @@ require 'sqreen/graft'
|
|
7
7
|
require 'sqreen/graft/call'
|
8
8
|
require 'sqreen/graft/callback'
|
9
9
|
require 'sqreen/graft/hook_point'
|
10
|
+
require 'sqreen/weave'
|
11
|
+
require 'sqreen/runner' # Sqreen.queue
|
10
12
|
|
11
13
|
module Sqreen
|
12
14
|
module Graft
|
13
15
|
class Hook
|
16
|
+
DEFAULT_STRATEGY = Sqreen::Graft::HookPoint::DEFAULT_STRATEGY
|
17
|
+
|
14
18
|
@hooks = {}
|
15
19
|
|
16
|
-
def self.[](hook_point, strategy =
|
20
|
+
def self.[](hook_point, strategy = DEFAULT_STRATEGY)
|
17
21
|
@hooks[hook_point] ||= new(hook_point, nil, strategy)
|
18
22
|
end
|
19
23
|
|
20
|
-
def self.add(hook_point, strategy =
|
24
|
+
def self.add(hook_point, strategy = DEFAULT_STRATEGY, &block)
|
21
25
|
self[hook_point, strategy].add(&block)
|
22
26
|
end
|
23
27
|
|
28
|
+
def self.ignore
|
29
|
+
Thread.current[:sqreen_hook_entered] = true
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
Thread.current[:sqreen_hook_entered] = false
|
33
|
+
end
|
34
|
+
|
24
35
|
attr_reader :point
|
25
36
|
|
26
|
-
def initialize(hook_point, dependency_test = nil, strategy =
|
37
|
+
def initialize(hook_point, dependency_test = nil, strategy = DEFAULT_STRATEGY)
|
27
38
|
@disabled = false
|
28
39
|
@point = hook_point.is_a?(HookPoint) ? hook_point : HookPoint.new(hook_point, strategy)
|
29
40
|
@before = []
|
@@ -46,27 +57,31 @@ module Sqreen
|
|
46
57
|
end
|
47
58
|
|
48
59
|
def before(tag = nil, opts = {}, &block)
|
49
|
-
return @before
|
60
|
+
return @before if block.nil?
|
50
61
|
|
51
62
|
@before << Callback.new(callback_name(:before, tag), opts, &block)
|
63
|
+
@before.sort_by!(&:rank)
|
52
64
|
end
|
53
65
|
|
54
66
|
def after(tag = nil, opts = {}, &block)
|
55
|
-
return @after
|
67
|
+
return @after if block.nil?
|
56
68
|
|
57
69
|
@after << Callback.new(callback_name(:after, tag), opts, &block)
|
70
|
+
@after.sort_by!(&:rank)
|
58
71
|
end
|
59
72
|
|
60
73
|
def raised(tag = nil, opts = {}, &block)
|
61
|
-
return @raised
|
74
|
+
return @raised if block.nil?
|
62
75
|
|
63
76
|
@raised << Callback.new(callback_name(:raised, tag), opts, &block)
|
77
|
+
@raised.sort_by!(&:rank)
|
64
78
|
end
|
65
79
|
|
66
80
|
def ensured(tag = nil, opts = {}, &block)
|
67
|
-
return @ensured
|
81
|
+
return @ensured if block.nil?
|
68
82
|
|
69
83
|
@ensured << Callback.new(callback_name(:ensured, tag), opts, &block)
|
84
|
+
@ensured.sort_by!(&:rank)
|
70
85
|
end
|
71
86
|
|
72
87
|
def depends_on(&block)
|
@@ -109,11 +124,32 @@ module Sqreen
|
|
109
124
|
@before = []
|
110
125
|
@after = []
|
111
126
|
@raised = []
|
127
|
+
@ensured = []
|
112
128
|
end
|
113
129
|
|
114
130
|
def self.wrapper(hook)
|
131
|
+
timed_hooks_proc = proc do |t|
|
132
|
+
if (request = Thread.current[:sqreen_http_request])
|
133
|
+
request[:timed_hooks] << t if request[:timed_level] >= 1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
timed_callbacks_proc = proc do |t|
|
137
|
+
if (request = Thread.current[:sqreen_http_request])
|
138
|
+
request[:timed_callbacks] << t if request[:timed_level] >= 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
# very hacky, but the non-local control flow with throw-catch
|
142
|
+
# makes the solution non-obvious. needs to be revisited
|
143
|
+
conditions_passed_proc = proc do
|
144
|
+
if (request = Thread.current[:sqreen_http_request])
|
145
|
+
request[:timed_callbacks].last.conditions_passed = true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
115
149
|
Proc.new do |*args, &block|
|
116
|
-
|
150
|
+
request = Thread.current[:sqreen_http_request]
|
151
|
+
|
152
|
+
if Thread.current[:sqreen_hook_entered]
|
117
153
|
if hook.point.super?
|
118
154
|
return super(*args, &block)
|
119
155
|
else
|
@@ -121,113 +157,159 @@ module Sqreen
|
|
121
157
|
end
|
122
158
|
end
|
123
159
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
160
|
+
if request && request[:time_budget_expended] && (hook.before + hook.after + hook.raised + hook.ensured).none?(&:mandatory)
|
161
|
+
if request[:timed_level] >= 2
|
162
|
+
begin
|
163
|
+
request[:skipped_callbacks].concat(hook.before)
|
164
|
+
|
165
|
+
if hook.point.super?
|
166
|
+
return super(*args, &block)
|
167
|
+
else
|
168
|
+
return hook.point.apply(self, 'sqreen_hook', *args, &block)
|
169
|
+
end
|
170
|
+
rescue ::Exception # rubocop:disable Lint/RescueException
|
171
|
+
request[:skipped_callbacks].concat(hook.raised)
|
172
|
+
raise
|
173
|
+
else
|
174
|
+
request[:skipped_callbacks].concat(hook.after)
|
175
|
+
ensure
|
176
|
+
request[:skipped_callbacks].concat(hook.ensured)
|
177
|
+
end
|
178
|
+
else
|
179
|
+
if hook.point.super? # rubocop:disable Style/IfInsideElse
|
180
|
+
return super(*args, &block)
|
181
|
+
else
|
182
|
+
return hook.point.apply(self, 'sqreen_hook', *args, &block)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
hook_point_super = hook.point.super?
|
188
|
+
logger = Sqreen::Graft.logger
|
189
|
+
logger_debug = Sqreen::Graft.logger.debug?
|
190
|
+
|
191
|
+
Timer.new(hook.point, &timed_hooks_proc).measure do |chrono|
|
192
|
+
# budget implies request
|
193
|
+
# TODO: make budget depend on a generic context (currently "request")
|
194
|
+
budget = request[:time_budget] if request
|
195
|
+
if request && (budget || request[:timed_level] >= 1)
|
196
|
+
sqreen_timer = request[:sqreen_timer]
|
197
|
+
end
|
128
198
|
|
129
|
-
budget = Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:time_budget]
|
130
|
-
timer = Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timer] if budget
|
131
199
|
hooked_call = HookedCall.new(self, args)
|
132
200
|
|
133
201
|
begin
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_before] << t
|
148
|
-
end.measure do |before_chrono|
|
202
|
+
begin
|
203
|
+
sqreen_timer.start if sqreen_timer
|
204
|
+
Thread.current[:sqreen_hook_entered] = true
|
205
|
+
|
206
|
+
# TODO: make Call have #ball to throw by cb
|
207
|
+
# TODO: can Call be the ball? r = catch(Call.new, &c)
|
208
|
+
# TODO: is catch return value a Call? a #dispatch?
|
209
|
+
# TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
|
210
|
+
# TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
|
211
|
+
# TODO: HookCall x CallbackCollection#each_with_call x Flow
|
212
|
+
# TODO: TimedHookCall TimedCallbackCall
|
213
|
+
# TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
|
214
|
+
|
149
215
|
hook.before.each do |c|
|
150
216
|
next if c.ignore && c.ignore.call
|
151
217
|
|
152
|
-
if
|
153
|
-
|
154
|
-
|
155
|
-
Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
|
156
|
-
next
|
157
|
-
end
|
218
|
+
if budget && !c.mandatory && request[:time_budget_expended]
|
219
|
+
request[:skipped_callbacks] << c
|
220
|
+
next
|
158
221
|
end
|
159
222
|
|
223
|
+
remaining = budget - sqreen_timer.elapsed if budget
|
224
|
+
|
225
|
+
timer = nil
|
160
226
|
flow = catch(Ball.new) do |ball|
|
161
|
-
Timer.new(c.name)
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
227
|
+
timer = Timer.new(c.name, &timed_callbacks_proc)
|
228
|
+
timer.measure(ignore: chrono) do
|
229
|
+
c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
if budget && timer
|
234
|
+
remaining -= timer.duration
|
235
|
+
if remaining < 0.0
|
236
|
+
request[:time_budget_expended]
|
237
|
+
request[:overtime_cb] = c.name
|
167
238
|
end
|
168
239
|
end
|
169
240
|
|
170
241
|
next unless c.flow && flow.is_a?(Flow)
|
242
|
+
conditions_passed_proc[] if flow.passed_conditions?
|
171
243
|
hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
|
172
244
|
hooked_call.args_pass = flow.args and hooked_call.args_passing = true if flow.args?
|
173
245
|
hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
|
174
246
|
break if flow.break?
|
175
247
|
end unless hook.disabled?
|
248
|
+
rescue StandardError => e
|
249
|
+
Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
|
250
|
+
Sqreen::RemoteException.record(e) if Sqreen.queue
|
176
251
|
end
|
177
252
|
|
178
253
|
raise hooked_call.raise if hooked_call.raising
|
179
254
|
return hooked_call.return if hooked_call.returning
|
180
255
|
ensure
|
181
256
|
Thread.current[:sqreen_hook_entered] = false
|
182
|
-
|
183
|
-
end
|
257
|
+
sqreen_timer.stop if sqreen_timer
|
258
|
+
end unless hook.before.empty?
|
184
259
|
|
185
260
|
begin
|
186
261
|
chrono.ignore do
|
187
|
-
if
|
262
|
+
if hook_point_super
|
188
263
|
hooked_call.returned = super(*(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
|
189
264
|
else
|
190
265
|
hooked_call.returned = hook.point.apply(hooked_call.instance, 'sqreen_hook', *(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
|
191
266
|
end
|
192
267
|
end
|
193
268
|
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
194
|
-
|
195
|
-
|
196
|
-
|
269
|
+
begin
|
270
|
+
sqreen_timer.start if sqreen_timer
|
271
|
+
Thread.current[:sqreen_hook_entered] = true
|
272
|
+
hooked_call.raised = e
|
197
273
|
|
198
|
-
|
199
|
-
|
274
|
+
logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" } if logger_debug
|
275
|
+
raise if hook.raised.empty?
|
200
276
|
|
201
|
-
Timer.new("#{hook.point}@raised") do |t|
|
202
|
-
Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_raised] << t
|
203
|
-
end.measure do |raised_chrono|
|
204
277
|
hook.raised.each do |c|
|
205
278
|
next if c.ignore && c.ignore.call
|
206
279
|
|
207
|
-
if
|
208
|
-
|
209
|
-
|
210
|
-
Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
|
211
|
-
next
|
212
|
-
end
|
280
|
+
if budget && !c.mandatory && request[:time_budget_expended]
|
281
|
+
request[:skipped_callbacks] << c
|
282
|
+
next
|
213
283
|
end
|
214
284
|
|
285
|
+
remaining = budget - sqreen_timer.elapsed if budget
|
286
|
+
|
287
|
+
timer = nil
|
215
288
|
flow = catch(Ball.new) do |ball|
|
216
|
-
Timer.new(c.name)
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
289
|
+
timer = Timer.new(c.name, &timed_callbacks_proc)
|
290
|
+
timer.measure(ignore: chrono) do
|
291
|
+
c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, hooked_call.raised), ball)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
if budget && timer
|
296
|
+
remaining -= timer.duration
|
297
|
+
if remaining < 0.0
|
298
|
+
request[:time_budget_expended]
|
299
|
+
request[:overtime_cb] = c.name
|
222
300
|
end
|
223
301
|
end
|
224
302
|
|
225
303
|
next unless c.flow && flow.is_a?(Flow)
|
304
|
+
conditions_passed_proc[] if flow.passed_conditions?
|
226
305
|
hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
|
227
306
|
hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
|
228
307
|
hooked_call.retrying = true if flow.retry?
|
229
308
|
break if flow.break?
|
230
309
|
end unless hook.disabled?
|
310
|
+
rescue StandardError => e
|
311
|
+
Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
|
312
|
+
Sqreen::RemoteException.record(e) if Sqreen.queue
|
231
313
|
end
|
232
314
|
|
233
315
|
retry if hooked_call.retrying
|
@@ -235,78 +317,108 @@ module Sqreen
|
|
235
317
|
return hooked_call.return if hooked_call.returning
|
236
318
|
raise
|
237
319
|
else
|
238
|
-
|
239
|
-
|
320
|
+
begin
|
321
|
+
sqreen_timer.start if sqreen_timer
|
322
|
+
Thread.current[:sqreen_hook_entered] = true
|
323
|
+
|
324
|
+
# TODO: hooked_call.returning should be always false here?
|
325
|
+
return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.after.empty?
|
240
326
|
|
241
|
-
Timer.new("#{hook.point}@after") do |t|
|
242
|
-
Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_after] << t
|
243
|
-
end.measure do |after_chrono|
|
244
327
|
hook.after.each do |c|
|
245
328
|
next if c.ignore && c.ignore.call
|
246
329
|
|
247
|
-
if
|
248
|
-
|
249
|
-
|
250
|
-
Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
|
251
|
-
next
|
252
|
-
end
|
330
|
+
if budget && !c.mandatory && request[:time_budget_expended]
|
331
|
+
request[:skipped_callbacks] << c
|
332
|
+
next
|
253
333
|
end
|
254
334
|
|
335
|
+
remaining = budget - sqreen_timer.elapsed if budget
|
336
|
+
|
337
|
+
timer = nil
|
255
338
|
flow = catch(Ball.new) do |ball|
|
256
|
-
Timer.new(c.name)
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
339
|
+
timer = Timer.new(c.name, &timed_callbacks_proc)
|
340
|
+
timer.measure(ignore: chrono) do
|
341
|
+
c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
if budget && timer
|
346
|
+
remaining -= timer.duration
|
347
|
+
if remaining < 0.0
|
348
|
+
request[:time_budget_expended]
|
349
|
+
request[:overtime_cb] = c.name
|
262
350
|
end
|
263
351
|
end
|
264
352
|
|
265
353
|
next unless c.flow && flow.is_a?(Flow)
|
354
|
+
conditions_passed_proc[] if flow.passed_conditions?
|
266
355
|
hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
|
267
356
|
hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
|
268
357
|
break if flow.break?
|
269
358
|
end unless hook.disabled?
|
359
|
+
rescue StandardError => e
|
360
|
+
Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
|
361
|
+
Sqreen::RemoteException.record(e) if Sqreen.queue
|
270
362
|
end
|
271
363
|
|
272
364
|
raise hooked_call.raise if hooked_call.raising
|
273
365
|
return hooked_call.returning ? hooked_call.return : hooked_call.returned
|
274
366
|
ensure
|
275
|
-
|
367
|
+
begin
|
368
|
+
# TODO: sqreen_timer.start if someone has thrown?
|
369
|
+
# TODO: sqreen_timer.stop at end of rescue+else?
|
370
|
+
# TODO: Thread.current[:sqreen_hook_entered] = true if neither rescue nor else ie thrown?
|
371
|
+
# TODO: Thread.current[:sqreen_hook_entered] = false at end of rescue+else? (risky?)
|
372
|
+
|
373
|
+
# TODO: uniform early bail out? (but don't forget sqreen_hook_entered and sqreen_timer)
|
374
|
+
# return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.ensured.empty?
|
375
|
+
|
376
|
+
# done at either rescue or else
|
377
|
+
# request_elapsed = request_timer.elapsed if budget # && !hook.ensured.empty?
|
276
378
|
|
277
|
-
Timer.new("#{hook.point}@ensured") do |t|
|
278
|
-
Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_ensured] << t
|
279
|
-
end.measure do |ensured_chrono|
|
280
379
|
hook.ensured.each do |c|
|
281
380
|
next if c.ignore && c.ignore.call
|
282
381
|
|
283
|
-
if
|
284
|
-
|
285
|
-
|
286
|
-
Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
|
287
|
-
next
|
288
|
-
end
|
382
|
+
if budget && !c.mandatory && request[:time_budget_expended]
|
383
|
+
request[:skipped_callbacks] << c
|
384
|
+
next
|
289
385
|
end
|
290
386
|
|
387
|
+
remaining = budget - sqreen_timer.elapsed if budget
|
388
|
+
|
389
|
+
timer = nil
|
291
390
|
flow = catch(Ball.new) do |ball|
|
292
|
-
Timer.new(c.name)
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
391
|
+
timer = Timer.new(c.name, &timed_callbacks_proc)
|
392
|
+
timer.measure(ignore: chrono) do
|
393
|
+
c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
if budget && timer
|
398
|
+
remaining -= timer.duration
|
399
|
+
if remaining < 0.0
|
400
|
+
request[:time_budget_expended]
|
401
|
+
request[:overtime_cb] = c.name
|
298
402
|
end
|
299
403
|
end
|
300
404
|
|
301
405
|
next unless c.flow && flow.is_a?(Flow)
|
406
|
+
conditions_passed_proc[] if flow.passed_conditions?
|
302
407
|
hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
|
303
408
|
hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
|
304
409
|
break if flow.break?
|
305
|
-
end unless hook.disabled?
|
410
|
+
end unless hook.ensured.empty? || hook.disabled?
|
411
|
+
|
412
|
+
Thread.current[:sqreen_hook_entered] = false
|
413
|
+
sqreen_timer.stop if sqreen_timer
|
414
|
+
rescue StandardError => e
|
415
|
+
Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
|
416
|
+
Sqreen::RemoteException.record(e) if Sqreen.queue
|
306
417
|
end
|
307
418
|
|
308
|
-
|
309
|
-
|
419
|
+
# TODO: should we run the following?
|
420
|
+
# raise hooked_call.raise if hooked_call.raising
|
421
|
+
# return hooked_call.returning ? hooked_call.return : hooked_call.returned
|
310
422
|
end
|
311
423
|
end # chrono
|
312
424
|
end
|