sqreen 1.20.1-java → 1.21.0.beta3-java
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 +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
|