sqreen 1.20.3 → 1.20.4.beta1

Sign up to get free protection for your applications and to get access to all the features.
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