sqreen 1.20.0 → 1.20.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/lib/sqreen/actions/block_user.rb +1 -1
  4. data/lib/sqreen/actions/redirect_ip.rb +1 -1
  5. data/lib/sqreen/actions/redirect_user.rb +1 -1
  6. data/lib/sqreen/agent_message.rb +20 -0
  7. data/lib/sqreen/attack_detected.html +1 -2
  8. data/lib/sqreen/ca.crt +24 -0
  9. data/lib/sqreen/condition_evaluator.rb +8 -2
  10. data/lib/sqreen/configuration.rb +5 -3
  11. data/lib/sqreen/deferred_logger.rb +50 -14
  12. data/lib/sqreen/deprecation.rb +38 -0
  13. data/lib/sqreen/endpoint_testing.rb +184 -0
  14. data/lib/sqreen/events/request_record.rb +0 -1
  15. data/lib/sqreen/frameworks/generic.rb +9 -0
  16. data/lib/sqreen/frameworks/rails.rb +0 -7
  17. data/lib/sqreen/frameworks/request_recorder.rb +2 -0
  18. data/lib/sqreen/graft/call.rb +76 -18
  19. data/lib/sqreen/graft/callback.rb +1 -1
  20. data/lib/sqreen/graft/hook.rb +187 -85
  21. data/lib/sqreen/graft/hook_point.rb +1 -1
  22. data/lib/sqreen/legacy/instrumentation.rb +22 -10
  23. data/lib/sqreen/legacy/old_event_submission_strategy.rb +2 -1
  24. data/lib/sqreen/log.rb +3 -2
  25. data/lib/sqreen/log/loggable.rb +2 -1
  26. data/lib/sqreen/logger.rb +24 -0
  27. data/lib/sqreen/metrics_store.rb +11 -0
  28. data/lib/sqreen/null_logger.rb +22 -0
  29. data/lib/sqreen/remote_command.rb +1 -0
  30. data/lib/sqreen/rules.rb +8 -4
  31. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  32. data/lib/sqreen/rules/custom_error_cb.rb +3 -3
  33. data/lib/sqreen/rules/rule_cb.rb +2 -0
  34. data/lib/sqreen/rules/waf_cb.rb +3 -3
  35. data/lib/sqreen/runner.rb +64 -9
  36. data/lib/sqreen/session.rb +17 -11
  37. data/lib/sqreen/version.rb +1 -1
  38. data/lib/sqreen/weave/budget.rb +46 -0
  39. data/lib/sqreen/weave/legacy/instrumentation.rb +194 -103
  40. data/lib/sqreen/worker.rb +6 -2
  41. metadata +9 -7
  42. data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -8,7 +8,6 @@
8
8
  require 'json'
9
9
  require 'sqreen/log'
10
10
  require 'sqreen/event'
11
- require 'sqreen/encoding_sanitizer'
12
11
  require 'sqreen/sensitive_data_redactor'
13
12
 
14
13
  module Sqreen
@@ -209,7 +209,16 @@ module Sqreen
209
209
 
210
210
  # Should the agent not be starting up?
211
211
  def prevent_startup
212
+ # SQREEN-880 - prevent Sqreen startup on Sidekiq workers
213
+ return :sidekiq_cli if defined?(Sidekiq::CLI)
214
+ return :delayed_job if defined?(Delayed::Command)
215
+
216
+ # Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
217
+ run_in_test = sqreen_configuration.get(:run_in_test)
218
+ return :rake if !run_in_test && $0.end_with?('rake')
219
+
212
220
  return :irb if $0 == 'irb'
221
+
213
222
  return if sqreen_configuration.nil?
214
223
  disable = sqreen_configuration.get(:disable)
215
224
  return :config_disable if disable == true || disable.to_s.to_i == 1
@@ -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
 
@@ -93,58 +93,116 @@ module Sqreen
93
93
  end
94
94
  end
95
95
 
96
+ class TimerError < StandardError; end
97
+
96
98
  class Timer
97
99
  def self.read
98
100
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
99
101
  end
100
102
 
101
- attr_reader :tag
103
+ attr_reader :tag, :size
102
104
 
103
105
  def initialize(tag, &block)
104
106
  @tag = tag
105
- @blips = []
106
107
  @block = block
108
+ @tally = 0
109
+ @size = 0
107
110
  end
108
111
 
109
- def duration
110
- @blips.each_with_index.reduce(0) { |a, (e, i)| i.even? ? a - e : a + e }
112
+ def elapsed
113
+ raise(TimerError, 'Timer#elapsed when paused') if @size.even?
114
+
115
+ @tally + Timer.read
111
116
  end
112
117
 
113
- def elapsed
114
- @blips.each_with_index.reduce(0) { |a, (e, i)| i.even? ? a - e : a + e } + Timer.read
118
+ def duration
119
+ raise(TimerError, 'Timer#duration when running') if @size.odd?
120
+
121
+ @tally
115
122
  end
116
123
 
117
124
  def ignore
118
- @blips << Timer.read
125
+ raise(TimerError, 'Timer#ignore when paused') if @size.even?
126
+
127
+ @size += 1
128
+ @tally += Timer.read
119
129
  yield(self)
120
130
  ensure
121
- @blips << Timer.read
131
+ @size += 1
132
+ @tally -= Timer.read
122
133
  end
123
134
 
124
- def measure
125
- @blips << Timer.read
135
+ def measure(opts = nil)
136
+ raise(TimerError, 'Timer#measure when running') if @size.odd?
137
+
138
+ now = Timer.read
139
+
140
+ ignore = opts[:ignore] if opts
141
+ if ignore
142
+ ignore.size += 1
143
+ ignore.tally += now
144
+ end
145
+
146
+ @size += 1
147
+ @tally -= now
148
+
126
149
  yield(self)
127
150
  ensure
128
- @blips << Timer.read
151
+ now = Timer.read
152
+
153
+ if ignore
154
+ ignore.size += 1
155
+ ignore.tally -= now
156
+ end
157
+
158
+ @size += 1
159
+ @tally += now
160
+
129
161
  @block.call(self) if @block
130
- Sqreen::Graft.logger.debug { "#{@tag}: time=%.03fus" % (duration * 1_000_000) }
131
162
  end
132
163
 
133
- def start
134
- @blips << Timer.read
164
+ def start(at = Timer.read)
165
+ raise(TimerError, 'Timer#start when started') unless @size.even?
166
+
167
+ @size += 1
168
+ @tally -= at
169
+
170
+ at
171
+ end
172
+
173
+ def stop(at = Timer.read)
174
+ raise(TimerError, 'Timer#stop when unstarted') unless @size.odd?
175
+
176
+ @size += 1
177
+ @tally += at
178
+
179
+ at
180
+ end
181
+
182
+ def started?
183
+ @size != 0 && @size.odd?
135
184
  end
136
185
 
137
- def stop
138
- @blips << Timer.read
186
+ def stopped?
187
+ @size != 0 && @size.even?
139
188
  end
140
189
 
141
- def size
142
- @blips.size
190
+ def running?
191
+ @size.odd?
192
+ end
193
+
194
+ def paused?
195
+ @size.even?
143
196
  end
144
197
 
145
198
  def to_s
146
199
  "#{@tag}: time=%.03fus" % (duration * 1_000_000)
147
200
  end
201
+
202
+ protected
203
+
204
+ attr_reader :tally
205
+ attr_writer :size, :tally
148
206
  end
149
207
  end
150
208
  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
@@ -21,6 +21,13 @@ module Sqreen
21
21
  self[hook_point, strategy].add(&block)
22
22
  end
23
23
 
24
+ def self.ignore
25
+ Thread.current[:sqreen_hook_entered] = true
26
+ yield
27
+ ensure
28
+ Thread.current[:sqreen_hook_entered] = false
29
+ end
30
+
24
31
  attr_reader :point
25
32
 
26
33
  def initialize(hook_point, dependency_test = nil, strategy = :chain)
@@ -46,27 +53,31 @@ module Sqreen
46
53
  end
47
54
 
48
55
  def before(tag = nil, opts = {}, &block)
49
- return @before.sort_by(&:rank) if block.nil?
56
+ return @before if block.nil?
50
57
 
51
58
  @before << Callback.new(callback_name(:before, tag), opts, &block)
59
+ @before.sort_by!(&:rank)
52
60
  end
53
61
 
54
62
  def after(tag = nil, opts = {}, &block)
55
- return @after.sort_by(&:rank) if block.nil?
63
+ return @after if block.nil?
56
64
 
57
65
  @after << Callback.new(callback_name(:after, tag), opts, &block)
66
+ @after.sort_by!(&:rank)
58
67
  end
59
68
 
60
69
  def raised(tag = nil, opts = {}, &block)
61
- return @raised.sort_by(&:rank) if block.nil?
70
+ return @raised if block.nil?
62
71
 
63
72
  @raised << Callback.new(callback_name(:raised, tag), opts, &block)
73
+ @raised.sort_by!(&:rank)
64
74
  end
65
75
 
66
76
  def ensured(tag = nil, opts = {}, &block)
67
- return @ensured.sort_by(&:rank) if block.nil?
77
+ return @ensured if block.nil?
68
78
 
69
79
  @ensured << Callback.new(callback_name(:ensured, tag), opts, &block)
80
+ @ensured.sort_by!(&:rank)
70
81
  end
71
82
 
72
83
  def depends_on(&block)
@@ -109,11 +120,25 @@ module Sqreen
109
120
  @before = []
110
121
  @after = []
111
122
  @raised = []
123
+ @ensured = []
112
124
  end
113
125
 
114
126
  def self.wrapper(hook)
127
+ timed_hooks_proc = proc do |t|
128
+ if (request = Thread.current[:sqreen_http_request])
129
+ request[:timed_hooks] << t if request[:timed_level] >= 1
130
+ end
131
+ end
132
+ timed_callbacks_proc = proc do |t|
133
+ if (request = Thread.current[:sqreen_http_request])
134
+ request[:timed_callbacks] << t if request[:timed_level] >= 1
135
+ end
136
+ end
137
+
115
138
  Proc.new do |*args, &block|
116
- if Thread.current[:sqreen_hook_entered] || Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:time_budget_expended]
139
+ request = Thread.current[:sqreen_http_request]
140
+
141
+ if Thread.current[:sqreen_hook_entered]
117
142
  if hook.point.super?
118
143
  return super(*args, &block)
119
144
  else
@@ -121,49 +146,88 @@ module Sqreen
121
146
  end
122
147
  end
123
148
 
124
- Timer.new(hook.point) do |t|
125
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks] << t
126
- end.measure do |chrono|
127
- Sqreen::Graft.logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} caller:#{Kernel.caller[2].inspect}" }
149
+ if request && request[:time_budget_expended] && (hook.before + hook.after + hook.raised + hook.ensured).none?(&:mandatory)
150
+ if request[:timed_level] >= 2
151
+ begin
152
+ request[:skipped_callbacks].concat(hook.before)
153
+
154
+ if hook.point.super?
155
+ return super(*args, &block)
156
+ else
157
+ return hook.point.apply(self, 'sqreen_hook', *args, &block)
158
+ end
159
+ rescue ::Exception # rubocop:disable Lint/RescueException
160
+ request[:skipped_callbacks].concat(hook.raised)
161
+ raise
162
+ else
163
+ request[:skipped_callbacks].concat(hook.after)
164
+ ensure
165
+ request[:skipped_callbacks].concat(hook.ensured)
166
+ end
167
+ else
168
+ if hook.point.super? # rubocop:disable Style/IfInsideElse
169
+ return super(*args, &block)
170
+ else
171
+ return hook.point.apply(self, 'sqreen_hook', *args, &block)
172
+ end
173
+ end
174
+ end
175
+
176
+ hook_point_super = hook.point.super?
177
+ logger = Sqreen::Graft.logger
178
+ logger_debug = Sqreen::Graft.logger.debug?
179
+
180
+ Timer.new(hook.point, &timed_hooks_proc).measure do |chrono|
181
+ # budget implies request
182
+ # TODO: make budget depend on a generic context (currently "request")
183
+ budget = request[:time_budget] if request
184
+ if budget
185
+ budget_threshold = request[:time_budget_threshold]
186
+ budget_ratio = request[:time_budget_ratio]
187
+ sqreen_timer = request[:sqreen_timer]
188
+ request_timer = request[:request_timer]
189
+ end
128
190
 
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
191
  hooked_call = HookedCall.new(self, args)
132
192
 
133
193
  begin
134
- timer.start if timer
135
- Thread.current[:sqreen_hook_entered] = true
136
-
137
- # TODO: make Call have #ball to throw by cb
138
- # TODO: can Call be the ball? r = catch(Call.new, &c)
139
- # TODO: is catch return value a Call? a #dispatch?
140
- # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
141
- # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
142
- # TODO: HookCall x CallbackCollection#each_with_call x Flow
143
- # TODO: TimedHookCall TimedCallbackCall
144
- # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
145
-
146
- Timer.new("#{hook.point}@before") do |t|
147
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_before] << t
148
- end.measure do |before_chrono|
194
+ begin
195
+ sqreen_timer.start if budget
196
+ Thread.current[:sqreen_hook_entered] = true
197
+
198
+ # TODO: make Call have #ball to throw by cb
199
+ # TODO: can Call be the ball? r = catch(Call.new, &c)
200
+ # TODO: is catch return value a Call? a #dispatch?
201
+ # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
202
+ # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
203
+ # TODO: HookCall x CallbackCollection#each_with_call x Flow
204
+ # TODO: TimedHookCall TimedCallbackCall
205
+ # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
206
+
207
+ request_elapsed = request_timer.elapsed if budget
208
+
149
209
  hook.before.each do |c|
150
210
  next if c.ignore && c.ignore.call
151
211
 
152
- if timer && !c.mandatory
153
- remaining = budget - timer.elapsed
212
+ if budget && !c.mandatory
213
+ sqreen_elapsed = sqreen_timer.elapsed
214
+ if budget_ratio && !request[:time_budget_expended]
215
+ fixed_budget = budget_threshold
216
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
217
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
218
+ else
219
+ remaining = budget_threshold - sqreen_elapsed
220
+ end
154
221
  unless remaining > 0
155
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
222
+ request[:skipped_callbacks] << c
223
+ request[:time_budget_expended] = true
156
224
  next
157
225
  end
158
226
  end
159
227
 
160
228
  flow = catch(Ball.new) do |ball|
161
- Timer.new(c.name) do |t|
162
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_callbacks] << t
163
- end.measure do
164
- before_chrono.ignore do
165
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
166
- end
229
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
230
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
167
231
  end
168
232
  end
169
233
 
@@ -173,52 +237,59 @@ module Sqreen
173
237
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
174
238
  break if flow.break?
175
239
  end unless hook.disabled?
240
+ rescue StandardError => e
241
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
242
+ Sqreen::RemoteException.record(e) if Sqreen.queue
176
243
  end
177
244
 
178
245
  raise hooked_call.raise if hooked_call.raising
179
246
  return hooked_call.return if hooked_call.returning
180
247
  ensure
181
248
  Thread.current[:sqreen_hook_entered] = false
182
- timer.stop if timer
183
- end
249
+ sqreen_timer.stop if budget
250
+ end unless hook.before.empty?
184
251
 
185
252
  begin
186
253
  chrono.ignore do
187
- if hook.point.super?
254
+ if hook_point_super
188
255
  hooked_call.returned = super(*(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
189
256
  else
190
257
  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
258
  end
192
259
  end
193
260
  rescue ::Exception => e # rubocop:disable Lint/RescueException
194
- timer.start if timer
195
- Thread.current[:sqreen_hook_entered] = true
196
- hooked_call.raised = e
261
+ begin
262
+ sqreen_timer.start if budget
263
+ Thread.current[:sqreen_hook_entered] = true
264
+ hooked_call.raised = e
197
265
 
198
- Sqreen::Graft.logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" }
199
- raise if hook.raised.empty?
266
+ logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" } if logger_debug
267
+ raise if hook.raised.empty?
268
+
269
+ request_elapsed = request_timer.elapsed if budget
200
270
 
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
271
  hook.raised.each do |c|
205
272
  next if c.ignore && c.ignore.call
206
273
 
207
- if timer && !c.mandatory
208
- remaining = budget - timer.elapsed
274
+ if budget && !c.mandatory
275
+ sqreen_elapsed = sqreen_timer.elapsed
276
+ if budget_ratio && !request[:time_budget_expended]
277
+ fixed_budget = budget_threshold
278
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
279
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
280
+ else
281
+ remaining = budget_threshold - sqreen_elapsed
282
+ end
209
283
  unless remaining > 0
210
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
284
+ request[:skipped_callbacks] << c
285
+ request[:time_budget_expended] = true
211
286
  next
212
287
  end
213
288
  end
214
289
 
215
290
  flow = catch(Ball.new) do |ball|
216
- Timer.new(c.name) do |t|
217
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_callbacks] << t
218
- end.measure do
219
- raised_chrono.ignore do
220
- 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)
221
- end
291
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
292
+ 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)
222
293
  end
223
294
  end
224
295
 
@@ -228,6 +299,9 @@ module Sqreen
228
299
  hooked_call.retrying = true if flow.retry?
229
300
  break if flow.break?
230
301
  end unless hook.disabled?
302
+ rescue StandardError => e
303
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
304
+ Sqreen::RemoteException.record(e) if Sqreen.queue
231
305
  end
232
306
 
233
307
  retry if hooked_call.retrying
@@ -235,30 +309,37 @@ module Sqreen
235
309
  return hooked_call.return if hooked_call.returning
236
310
  raise
237
311
  else
238
- timer.start if timer
239
- Thread.current[:sqreen_hook_entered] = true
312
+ begin
313
+ sqreen_timer.start if budget
314
+ Thread.current[:sqreen_hook_entered] = true
315
+
316
+ # TODO: hooked_call.returning should be always false here?
317
+ return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.after.empty?
318
+
319
+ request_elapsed = request_timer.elapsed if budget
240
320
 
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
321
  hook.after.each do |c|
245
322
  next if c.ignore && c.ignore.call
246
323
 
247
- if timer && !c.mandatory
248
- remaining = budget - timer.elapsed
324
+ if budget && !c.mandatory
325
+ sqreen_elapsed = sqreen_timer.elapsed
326
+ if budget_ratio && !request[:time_budget_expended]
327
+ fixed_budget = budget_threshold
328
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
329
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
330
+ else
331
+ remaining = budget_threshold - sqreen_elapsed
332
+ end
249
333
  unless remaining > 0
250
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
334
+ request[:skipped_callbacks] << c
335
+ request[:time_budget_expended] = true
251
336
  next
252
337
  end
253
338
  end
254
339
 
255
340
  flow = catch(Ball.new) do |ball|
256
- Timer.new(c.name) do |t|
257
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_callbacks] << t
258
- end.measure do
259
- after_chrono.ignore do
260
- 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)
261
- end
341
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
342
+ 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)
262
343
  end
263
344
  end
264
345
 
@@ -267,34 +348,48 @@ module Sqreen
267
348
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
268
349
  break if flow.break?
269
350
  end unless hook.disabled?
351
+ rescue StandardError => e
352
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
353
+ Sqreen::RemoteException.record(e) if Sqreen.queue
270
354
  end
271
355
 
272
356
  raise hooked_call.raise if hooked_call.raising
273
357
  return hooked_call.returning ? hooked_call.return : hooked_call.returned
274
358
  ensure
275
- # TODO: timer.start if someone has thrown?
359
+ begin
360
+ # TODO: sqreen_timer.start if someone has thrown?
361
+ # TODO: sqreen_timer.stop at end of rescue+else?
362
+ # TODO: Thread.current[:sqreen_hook_entered] = true if neither rescue nor else ie thrown?
363
+ # TODO: Thread.current[:sqreen_hook_entered] = false at end of rescue+else? (risky?)
364
+
365
+ # TODO: uniform early bail out? (but don't forget sqreen_hook_entered and sqreen_timer)
366
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.ensured.empty?
367
+
368
+ # done at either rescue or else
369
+ # request_elapsed = request_timer.elapsed if budget # && !hook.ensured.empty?
276
370
 
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
371
  hook.ensured.each do |c|
281
372
  next if c.ignore && c.ignore.call
282
373
 
283
- if timer && !c.mandatory
284
- remaining = budget - timer.elapsed
374
+ if budget && !c.mandatory
375
+ sqreen_elapsed = sqreen_timer.elapsed
376
+ if budget_ratio && !request[:time_budget_expended]
377
+ fixed_budget = budget_threshold
378
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
379
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
380
+ else
381
+ remaining = budget_threshold - sqreen_elapsed
382
+ end
285
383
  unless remaining > 0
286
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
384
+ request[:skipped_callbacks] << c
385
+ request[:time_budget_expended] = true
287
386
  next
288
387
  end
289
388
  end
290
389
 
291
390
  flow = catch(Ball.new) do |ball|
292
- Timer.new(c.name) do |t|
293
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_callbacks] << t
294
- end.measure do
295
- ensured_chrono.ignore do
296
- 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)
297
- end
391
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
392
+ 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)
298
393
  end
299
394
  end
300
395
 
@@ -302,11 +397,18 @@ module Sqreen
302
397
  hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
303
398
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
304
399
  break if flow.break?
305
- end unless hook.disabled?
400
+ end unless hook.ensured.empty? || hook.disabled?
401
+
402
+ Thread.current[:sqreen_hook_entered] = false
403
+ sqreen_timer.stop if budget
404
+ rescue StandardError => e
405
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
406
+ Sqreen::RemoteException.record(e) if Sqreen.queue
306
407
  end
307
408
 
308
- Thread.current[:sqreen_hook_entered] = false
309
- timer.stop if timer
409
+ # TODO: should we run the following?
410
+ # raise hooked_call.raise if hooked_call.raising
411
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned
310
412
  end
311
413
  end # chrono
312
414
  end