sqreen 1.20.3 → 1.20.4.beta1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bdcf1837e4c2ea45300af52682c98574d0c007703b507a469aeced98088bbda
4
- data.tar.gz: df2416f673ff51cf73b4056da0148738eb0f162bc054fba7480ce2d8ca05ea49
3
+ metadata.gz: 18fc090589982cc7e09d74263a3bbb17ae9180f00afd8425c47498a0f8b75e36
4
+ data.tar.gz: 074c3c9cad84ef1a52e6fd7ce1a4db5431920f59298e5b930206ca9a996141e9
5
5
  SHA512:
6
- metadata.gz: bc5df95d6ddff6c1a45b469dc42a0d18b17a804d0fbeb3689361db8583ad421b3ed1a9b823ec24b88f3494f5192865bd1f4c4dfde28c61fdf15d1fa4d06603bc
7
- data.tar.gz: 16980a1f0934e7f29251d0c72d751108ddf033c15f7860bd9f63a66b1ff6a5bc367f99d64faef3955015248ee16df882b44db2040f8e4aa84054a82c1ea18397
6
+ metadata.gz: afd27c8a52c0c5e21685b9bf6b346a28ca43ff297c529c1976d7197bc132d51a92f2ac52dcb4de150cd8c752c4875b4fdd3baf8e59560a0c1952c4df12585572
7
+ data.tar.gz: 2e6d09aceaf1dcc351537b0a34e4459e7f1d4659a231cd515e6f01ec42f0515dc40ff89f14adb7094a6971f86fe3983e984bf35f8242b8f047405c164ef0874c
@@ -1,3 +1,11 @@
1
+ ## 1.20.4.beta1
2
+
3
+ * Add optional dynamic time budget
4
+ * Add advanced per request metrics
5
+ * Improve robustness against exception in instrumentation
6
+ * Improve metric engine thread safety
7
+ * Restrict deferred logger to final logger severity on agent boot
8
+
1
9
  ## 1.20.3
2
10
 
3
11
  * Fix signature check
@@ -22,7 +22,7 @@ module Sqreen
22
22
  end
23
23
 
24
24
  def do_run(identity_params)
25
- Sqreen.log.info(
25
+ Sqreen.log.debug(
26
26
  "Will raise due to user being blocked by action #{id}. " \
27
27
  "Blocked user identity: #{identity_params}"
28
28
  )
@@ -25,7 +25,7 @@ module Sqreen
25
25
  end
26
26
 
27
27
  def do_run(client_ip)
28
- Sqreen.log.info "Will request redirect for client with IP #{client_ip} " \
28
+ Sqreen.log.debug "Will request redirect for client with IP #{client_ip} " \
29
29
  "(action: #{id})."
30
30
  {
31
31
  :status => :skip,
@@ -24,7 +24,7 @@ module Sqreen
24
24
  end
25
25
 
26
26
  def do_run(identity_params)
27
- Sqreen.log.info 'Will request redirect for user with identity ' \
27
+ Sqreen.log.debug 'Will request redirect for user with identity ' \
28
28
  "#{identity_params} (action: #{id})."
29
29
 
30
30
  e = Sqreen::AttackBlocked.new(
@@ -57,7 +57,7 @@ module Sqreen
57
57
  { :env => :SQREEN_RULES_SIGNATURE, :name => :rules_verify_signature,
58
58
  :default => true },
59
59
  { :env => :SQREEN_LOG_LEVEL, :name => :log_level,
60
- :default => 'WARN', :choice => %w[UNKNOWN FATAL ERROR WARN INFO DEBUG] },
60
+ :default => 'INFO', :choice => %w[UNKNOWN FATAL ERROR WARN INFO DEBUG] },
61
61
  { :env => :SQREEN_LOG_LOCATION, :name => :log_location,
62
62
  :default => 'log/sqreen.log' },
63
63
  { :env => :SQREEN_RUN_IN_TEST, :name => :run_in_test,
@@ -9,39 +9,70 @@ require 'sqreen/logger'
9
9
 
10
10
  module Sqreen
11
11
  class DeferredLogger
12
- include Singleton
12
+ MAX_ENTRIES = 1000
13
+
14
+ Entry = Struct.new(:severity, :message)
13
15
 
14
16
  def initialize
15
17
  @buffer = StringIO.new
16
18
  @logger = ::Logger.new(@buffer)
19
+ @entries = []
20
+ @mutex = Mutex.new
17
21
  end
18
22
 
19
23
  def debug?
20
24
  true
21
25
  end
22
26
 
27
+ def info?
28
+ true
29
+ end
30
+
31
+ def warn?
32
+ true
33
+ end
34
+
35
+ def error?
36
+ true
37
+ end
38
+
39
+ def fatal?
40
+ true
41
+ end
42
+
23
43
  def debug(msg = nil, &block)
24
- @logger.debug(msg, &block)
44
+ add(::Logger::DEBUG, msg, &block)
25
45
  end
26
46
 
27
47
  def info(msg = nil, &block)
28
- @logger.info(msg, &block)
48
+ add(::Logger::INFO, msg, &block)
29
49
  end
30
50
 
31
51
  def warn(msg = nil, &block)
32
- @logger.warn(msg, &block)
52
+ add(::Logger::WARN, msg, &block)
33
53
  end
34
54
 
35
55
  def error(msg = nil, &block)
36
- @logger.error(msg, &block)
56
+ add(::Logger::ERROR, msg, &block)
37
57
  end
38
58
 
39
59
  def fatal(msg = nil, &block)
40
- @logger.error(msg, &block)
60
+ add(::Logger::FATAL, msg, &block)
61
+ end
62
+
63
+ def unknown(msg = nil, &block)
64
+ add(::Logger::UNKNOWN, msg, &block)
41
65
  end
42
66
 
43
67
  def add(severity, msg = nil, &block)
44
- send(Sqreen::Logger::SEVERITY_TO_METHOD[severity], msg, &block)
68
+ @mutex.synchronize do
69
+ @entries.shift if @entries.count >= MAX_ENTRIES
70
+ mark = @buffer.pos
71
+ @logger.add(severity, msg, &block)
72
+ @buffer.seek(mark)
73
+ @entries << Entry.new(severity, @buffer.read)
74
+ @buffer.truncate(0)
75
+ end
45
76
  end
46
77
 
47
78
  def formatter=(value)
@@ -49,21 +80,22 @@ module Sqreen
49
80
  end
50
81
 
51
82
  def flush_to(logger)
52
- logger.instance_eval { @logdev }.write(read).tap { reset }
83
+ @mutex.synchronize do
84
+ @entries.each do |entry|
85
+ next if entry.severity < logger.level
86
+ logger.instance_eval { @logdev }.write(entry.message)
87
+ end
88
+ reset
89
+ end
53
90
  end
54
91
 
55
92
  private
56
93
 
57
- def read
58
- @buffer.rewind
59
- @buffer.read
60
- end
61
-
62
94
  def reset
63
95
  buffer = StringIO.new
64
96
  logger = ::Logger.new(buffer)
65
97
  logger.formatter = @logger.formatter
66
- @buffer, @logger = buffer, logger
98
+ @buffer, @logger, @entries = buffer, logger, []
67
99
  end
68
100
  end
69
101
  end
@@ -0,0 +1,38 @@
1
+ # typed: strong
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ require 'sqreen/log/loggable'
7
+
8
+ module Sqreen
9
+ module Deprecation
10
+ include Sqreen::Log::Loggable
11
+
12
+ module_function
13
+
14
+ def deprecate(method)
15
+ return unless ENV['SQREEN_DEBUG_DEPRECATION']
16
+
17
+ owner = method.owner
18
+ deprecated = :"_deprecated_#{method.name}"
19
+ klass = owner.is_a?(Module)
20
+ target = klass ? owner.to_s : owner.class.to_s
21
+
22
+ method.owner.instance_eval do
23
+ alias_method deprecated, method.name
24
+
25
+ define_method(method.name) do |*args, &block|
26
+ msg = [
27
+ "deprecation",
28
+ "target:#{target}",
29
+ "method:#{method.name}",
30
+ "caller:#{Kernel.caller_locations[0]}",
31
+ ].join(' ')
32
+ Sqreen::Deprecation.logger.info(msg)
33
+ send(deprecated, *args, &block)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -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)
@@ -93,62 +93,106 @@ 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, :size, :duration
103
+ attr_reader :tag, :size
102
104
 
103
105
  def initialize(tag, &block)
104
106
  @tag = tag
105
107
  @block = block
106
- @duration = 0
108
+ @tally = 0
107
109
  @size = 0
108
110
  end
109
111
 
110
112
  def elapsed
111
- @duration + Timer.read
113
+ raise(TimerError, 'Timer#elapsed when paused') if @size.even?
114
+
115
+ @tally + Timer.read
116
+ end
117
+
118
+ def duration
119
+ raise(TimerError, 'Timer#duration when running') if @size.odd?
120
+
121
+ @tally
112
122
  end
113
123
 
114
124
  def ignore
125
+ raise(TimerError, 'Timer#ignore when paused') if @size.even?
126
+
115
127
  @size += 1
116
- @duration += Timer.read
128
+ @tally += Timer.read
117
129
  yield(self)
118
130
  ensure
119
131
  @size += 1
120
- @duration -= Timer.read
132
+ @tally -= Timer.read
121
133
  end
122
134
 
123
135
  def measure(opts = nil)
136
+ raise(TimerError, 'Timer#measure when running') if @size.odd?
137
+
124
138
  now = Timer.read
139
+
125
140
  ignore = opts[:ignore] if opts
126
141
  if ignore
127
142
  ignore.size += 1
128
- ignore.duration += now
143
+ ignore.tally += now
129
144
  end
145
+
130
146
  @size += 1
131
- @duration -= now
147
+ @tally -= now
148
+
132
149
  yield(self)
133
150
  ensure
134
151
  now = Timer.read
152
+
135
153
  if ignore
136
154
  ignore.size += 1
137
- ignore.duration -= now
155
+ ignore.tally -= now
138
156
  end
157
+
139
158
  @size += 1
140
- @duration += now
159
+ @tally += now
160
+
141
161
  @block.call(self) if @block
142
162
  end
143
163
 
144
- def start
164
+ def start(at = Timer.read)
165
+ raise(TimerError, 'Timer#start when started') unless @size.even?
166
+
145
167
  @size += 1
146
- @duration -= Timer.read
168
+ @tally -= at
169
+
170
+ at
147
171
  end
148
172
 
149
- def stop
173
+ def stop(at = Timer.read)
174
+ raise(TimerError, 'Timer#stop when unstarted') unless @size.odd?
175
+
150
176
  @size += 1
151
- @duration += Timer.read
177
+ @tally += at
178
+
179
+ at
180
+ end
181
+
182
+ def started?
183
+ @size != 0 && @size.odd?
184
+ end
185
+
186
+ def stopped?
187
+ @size != 0 && @size.even?
188
+ end
189
+
190
+ def running?
191
+ @size.odd?
192
+ end
193
+
194
+ def paused?
195
+ @size.even?
152
196
  end
153
197
 
154
198
  def to_s
@@ -157,7 +201,8 @@ module Sqreen
157
201
 
158
202
  protected
159
203
 
160
- attr_writer :size, :duration
204
+ attr_reader :tally
205
+ attr_writer :size, :tally
161
206
  end
162
207
  end
163
208
  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)
@@ -113,18 +120,25 @@ module Sqreen
113
120
  @before = []
114
121
  @after = []
115
122
  @raised = []
123
+ @ensured = []
116
124
  end
117
125
 
118
126
  def self.wrapper(hook)
119
127
  timed_hooks_proc = proc do |t|
120
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks] << t
128
+ if (request = Thread.current[:sqreen_http_request])
129
+ request[:timed_hooks] << t if request[:timed_level] >= 1
130
+ end
121
131
  end
122
132
  timed_callbacks_proc = proc do |t|
123
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_callbacks] << t
133
+ if (request = Thread.current[:sqreen_http_request])
134
+ request[:timed_callbacks] << t if request[:timed_level] >= 1
135
+ end
124
136
  end
125
137
 
126
138
  Proc.new do |*args, &block|
127
- 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]
128
142
  if hook.point.super?
129
143
  return super(*args, &block)
130
144
  else
@@ -132,60 +146,108 @@ module Sqreen
132
146
  end
133
147
  end
134
148
 
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
+
135
176
  hook_point_super = hook.point.super?
136
177
  logger = Sqreen::Graft.logger
137
178
  logger_debug = Sqreen::Graft.logger.debug?
138
179
 
139
180
  Timer.new(hook.point, &timed_hooks_proc).measure do |chrono|
140
- logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} caller:#{Kernel.caller[2].inspect}" } if logger_debug
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
141
190
 
142
- budget = Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:time_budget]
143
- timer = Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timer] if budget
144
191
  hooked_call = HookedCall.new(self, args)
145
192
 
146
193
  begin
147
- timer.start if timer
148
- Thread.current[:sqreen_hook_entered] = true
149
-
150
- # TODO: make Call have #ball to throw by cb
151
- # TODO: can Call be the ball? r = catch(Call.new, &c)
152
- # TODO: is catch return value a Call? a #dispatch?
153
- # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
154
- # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
155
- # TODO: HookCall x CallbackCollection#each_with_call x Flow
156
- # TODO: TimedHookCall TimedCallbackCall
157
- # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
158
-
159
- hook.before.each do |c|
160
- next if c.ignore && c.ignore.call
161
-
162
- if timer && !c.mandatory
163
- remaining = budget - timer.elapsed
164
- unless remaining > 0
165
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
166
- next
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
+
209
+ hook.before.each do |c|
210
+ next if c.ignore && c.ignore.call
211
+
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
221
+ unless remaining > 0
222
+ request[:skipped_callbacks] << c
223
+ request[:time_budget_expended] = true
224
+ next
225
+ end
167
226
  end
168
- end
169
227
 
170
- flow = catch(Ball.new) do |ball|
171
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
172
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
228
+ flow = catch(Ball.new) do |ball|
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)
231
+ end
173
232
  end
174
- end
175
233
 
176
- next unless c.flow && flow.is_a?(Flow)
177
- hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
178
- hooked_call.args_pass = flow.args and hooked_call.args_passing = true if flow.args?
179
- hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
180
- break if flow.break?
181
- end unless hook.disabled?
234
+ next unless c.flow && flow.is_a?(Flow)
235
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
236
+ hooked_call.args_pass = flow.args and hooked_call.args_passing = true if flow.args?
237
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
238
+ break if flow.break?
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
243
+ end
182
244
 
183
245
  raise hooked_call.raise if hooked_call.raising
184
246
  return hooked_call.return if hooked_call.returning
185
247
  ensure
186
248
  Thread.current[:sqreen_hook_entered] = false
187
- timer.stop if timer
188
- end
249
+ sqreen_timer.stop if budget
250
+ end unless hook.before.empty?
189
251
 
190
252
  begin
191
253
  chrono.ignore do
@@ -196,98 +258,157 @@ module Sqreen
196
258
  end
197
259
  end
198
260
  rescue ::Exception => e # rubocop:disable Lint/RescueException
199
- timer.start if timer
200
- Thread.current[:sqreen_hook_entered] = true
201
- hooked_call.raised = e
202
-
203
- logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" } if logger_debug
204
- raise if hook.raised.empty?
205
-
206
- hook.raised.each do |c|
207
- next if c.ignore && c.ignore.call
208
-
209
- if timer && !c.mandatory
210
- remaining = budget - timer.elapsed
211
- unless remaining > 0
212
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
213
- next
261
+ begin
262
+ sqreen_timer.start if budget
263
+ Thread.current[:sqreen_hook_entered] = true
264
+ hooked_call.raised = e
265
+
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
270
+
271
+ hook.raised.each do |c|
272
+ next if c.ignore && c.ignore.call
273
+
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
283
+ unless remaining > 0
284
+ request[:skipped_callbacks] << c
285
+ request[:time_budget_expended] = true
286
+ next
287
+ end
214
288
  end
215
- end
216
289
 
217
- flow = catch(Ball.new) do |ball|
218
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
219
- 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)
290
+ flow = catch(Ball.new) do |ball|
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)
293
+ end
220
294
  end
221
- end
222
295
 
223
- next unless c.flow && flow.is_a?(Flow)
224
- hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
225
- hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
226
- hooked_call.retrying = true if flow.retry?
227
- break if flow.break?
228
- end unless hook.disabled?
296
+ next unless c.flow && flow.is_a?(Flow)
297
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
298
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
299
+ hooked_call.retrying = true if flow.retry?
300
+ break if flow.break?
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
305
+ end
229
306
 
230
307
  retry if hooked_call.retrying
231
308
  raise hooked_call.raise if hooked_call.raising
232
309
  return hooked_call.return if hooked_call.returning
233
310
  raise
234
311
  else
235
- timer.start if timer
236
- Thread.current[:sqreen_hook_entered] = true
237
-
238
- hook.after.each do |c|
239
- next if c.ignore && c.ignore.call
240
-
241
- if timer && !c.mandatory
242
- remaining = budget - timer.elapsed
243
- unless remaining > 0
244
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
245
- next
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
320
+
321
+ hook.after.each do |c|
322
+ next if c.ignore && c.ignore.call
323
+
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
333
+ unless remaining > 0
334
+ request[:skipped_callbacks] << c
335
+ request[:time_budget_expended] = true
336
+ next
337
+ end
246
338
  end
247
- end
248
339
 
249
- flow = catch(Ball.new) do |ball|
250
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
251
- 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)
340
+ flow = catch(Ball.new) do |ball|
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)
343
+ end
252
344
  end
253
- end
254
345
 
255
- next unless c.flow && flow.is_a?(Flow)
256
- hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
257
- hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
258
- break if flow.break?
259
- end unless hook.disabled?
346
+ next unless c.flow && flow.is_a?(Flow)
347
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
348
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
349
+ break if flow.break?
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
354
+ end
260
355
 
261
356
  raise hooked_call.raise if hooked_call.raising
262
357
  return hooked_call.returning ? hooked_call.return : hooked_call.returned
263
358
  ensure
264
- # TODO: timer.start if someone has thrown?
265
-
266
- hook.ensured.each do |c|
267
- next if c.ignore && c.ignore.call
268
-
269
- if timer && !c.mandatory
270
- remaining = budget - timer.elapsed
271
- unless remaining > 0
272
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:skipped_callbacks] << c && Thread.current[:sqreen_http_request][:time_budget_expended] = true
273
- next
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?
370
+
371
+ hook.ensured.each do |c|
372
+ next if c.ignore && c.ignore.call
373
+
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
383
+ unless remaining > 0
384
+ request[:skipped_callbacks] << c
385
+ request[:time_budget_expended] = true
386
+ next
387
+ end
274
388
  end
275
- end
276
389
 
277
- flow = catch(Ball.new) do |ball|
278
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
279
- 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)
390
+ flow = catch(Ball.new) do |ball|
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)
393
+ end
280
394
  end
281
- end
282
395
 
283
- next unless c.flow && flow.is_a?(Flow)
284
- hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
285
- hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
286
- break if flow.break?
287
- end unless hook.disabled?
396
+ next unless c.flow && flow.is_a?(Flow)
397
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
398
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
399
+ break if flow.break?
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
407
+ end
288
408
 
289
- Thread.current[:sqreen_hook_entered] = false
290
- 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
291
412
  end
292
413
  end # chrono
293
414
  end