sqreen 1.20.1-java → 1.22.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -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/attack_detected.html +1 -2
  7. data/lib/sqreen/condition_evaluator.rb +8 -2
  8. data/lib/sqreen/configuration.rb +1 -1
  9. data/lib/sqreen/deferred_logger.rb +50 -14
  10. data/lib/sqreen/deliveries/batch.rb +8 -1
  11. data/lib/sqreen/dependency/detector.rb +11 -3
  12. data/lib/sqreen/dependency/new_relic.rb +10 -1
  13. data/lib/sqreen/deprecation.rb +38 -0
  14. data/lib/sqreen/ecosystem.rb +123 -0
  15. data/lib/sqreen/ecosystem/databases/database_connection_data.rb +23 -0
  16. data/lib/sqreen/ecosystem/databases/mongo.rb +39 -0
  17. data/lib/sqreen/ecosystem/databases/mysql.rb +54 -0
  18. data/lib/sqreen/ecosystem/databases/postgres.rb +51 -0
  19. data/lib/sqreen/ecosystem/databases/redis.rb +36 -0
  20. data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
  21. data/lib/sqreen/ecosystem/exception_reporting.rb +28 -0
  22. data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
  23. data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
  24. data/lib/sqreen/ecosystem/loggable.rb +13 -0
  25. data/lib/sqreen/ecosystem/messaging/bunny.rb +61 -0
  26. data/lib/sqreen/ecosystem/messaging/kafka.rb +70 -0
  27. data/lib/sqreen/ecosystem/messaging/kinesis.rb +66 -0
  28. data/lib/sqreen/ecosystem/messaging/sqs.rb +68 -0
  29. data/lib/sqreen/ecosystem/module_api.rb +30 -0
  30. data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
  31. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
  32. data/lib/sqreen/ecosystem/module_api/message_producer.rb +57 -0
  33. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
  34. data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
  35. data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
  36. data/lib/sqreen/ecosystem/module_api/tracing/consumer_data.rb +13 -0
  37. data/lib/sqreen/ecosystem/module_api/tracing/messaging_data.rb +35 -0
  38. data/lib/sqreen/ecosystem/module_api/tracing/producer_data.rb +13 -0
  39. data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
  40. data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
  41. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
  42. data/lib/sqreen/ecosystem/module_registry.rb +48 -0
  43. data/lib/sqreen/ecosystem/tracing/modules/client.rb +35 -0
  44. data/lib/sqreen/ecosystem/tracing/modules/consumer.rb +35 -0
  45. data/lib/sqreen/ecosystem/tracing/modules/determine_ip.rb +28 -0
  46. data/lib/sqreen/ecosystem/tracing/modules/producer.rb +35 -0
  47. data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
  48. data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
  49. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
  50. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
  51. data/lib/sqreen/ecosystem/tracing/signals/tracing_consumer.rb +56 -0
  52. data/lib/sqreen/ecosystem/tracing/signals/tracing_producer.rb +56 -0
  53. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
  54. data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
  55. data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
  56. data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
  57. data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
  58. data/lib/sqreen/ecosystem_integration.rb +81 -0
  59. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +89 -0
  60. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +38 -0
  61. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
  62. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
  63. data/lib/sqreen/events/request_record.rb +0 -1
  64. data/lib/sqreen/frameworks/generic.rb +36 -1
  65. data/lib/sqreen/frameworks/rails.rb +0 -7
  66. data/lib/sqreen/frameworks/request_recorder.rb +2 -0
  67. data/lib/sqreen/graft/call.rb +85 -18
  68. data/lib/sqreen/graft/callback.rb +1 -1
  69. data/lib/sqreen/graft/hook.rb +192 -88
  70. data/lib/sqreen/graft/hook_point.rb +18 -11
  71. data/lib/sqreen/kit/signals/specialized/sqreen_exception.rb +2 -0
  72. data/lib/sqreen/legacy/instrumentation.rb +22 -10
  73. data/lib/sqreen/legacy/old_event_submission_strategy.rb +9 -2
  74. data/lib/sqreen/log.rb +3 -2
  75. data/lib/sqreen/log/loggable.rb +1 -0
  76. data/lib/sqreen/logger.rb +24 -0
  77. data/lib/sqreen/metrics_store.rb +11 -0
  78. data/lib/sqreen/null_logger.rb +22 -0
  79. data/lib/sqreen/remote_command.rb +4 -0
  80. data/lib/sqreen/rules.rb +8 -4
  81. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  82. data/lib/sqreen/rules/custom_error_cb.rb +3 -3
  83. data/lib/sqreen/rules/rule_cb.rb +2 -0
  84. data/lib/sqreen/rules/waf_cb.rb +3 -3
  85. data/lib/sqreen/runner.rb +47 -7
  86. data/lib/sqreen/session.rb +2 -0
  87. data/lib/sqreen/signals/conversions.rb +6 -1
  88. data/lib/sqreen/version.rb +1 -1
  89. data/lib/sqreen/weave/budget.rb +46 -0
  90. data/lib/sqreen/weave/legacy/instrumentation.rb +252 -109
  91. data/lib/sqreen/worker.rb +6 -2
  92. metadata +60 -11
  93. data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -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,125 @@ 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?
196
+ end
197
+
198
+ def include_measurements(another_timer)
199
+ @blips += another_timer.instance_variable_get(:@blips)
200
+ end
201
+
202
+ def start_and_end
203
+ raise 'Not exactly two measurements recorded' unless size == 2
204
+ @blips
143
205
  end
144
206
 
145
207
  def to_s
146
208
  "#{@tag}: time=%.03fus" % (duration * 1_000_000)
147
209
  end
210
+
211
+ protected
212
+
213
+ attr_reader :tally
214
+ attr_writer :size, :tally
148
215
  end
149
216
  end
150
217
  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
@@ -11,19 +11,28 @@ require 'sqreen/graft/hook_point'
11
11
  module Sqreen
12
12
  module Graft
13
13
  class Hook
14
+ DEFAULT_STRATEGY = Sqreen::Graft::HookPoint::DEFAULT_STRATEGY
15
+
14
16
  @hooks = {}
15
17
 
16
- def self.[](hook_point, strategy = :chain)
18
+ def self.[](hook_point, strategy = DEFAULT_STRATEGY)
17
19
  @hooks[hook_point] ||= new(hook_point, nil, strategy)
18
20
  end
19
21
 
20
- def self.add(hook_point, strategy = :chain, &block)
22
+ def self.add(hook_point, strategy = DEFAULT_STRATEGY, &block)
21
23
  self[hook_point, strategy].add(&block)
22
24
  end
23
25
 
26
+ def self.ignore
27
+ Thread.current[:sqreen_hook_entered] = true
28
+ yield
29
+ ensure
30
+ Thread.current[:sqreen_hook_entered] = false
31
+ end
32
+
24
33
  attr_reader :point
25
34
 
26
- def initialize(hook_point, dependency_test = nil, strategy = :chain)
35
+ def initialize(hook_point, dependency_test = nil, strategy = DEFAULT_STRATEGY)
27
36
  @disabled = false
28
37
  @point = hook_point.is_a?(HookPoint) ? hook_point : HookPoint.new(hook_point, strategy)
29
38
  @before = []
@@ -46,27 +55,31 @@ module Sqreen
46
55
  end
47
56
 
48
57
  def before(tag = nil, opts = {}, &block)
49
- return @before.sort_by(&:rank) if block.nil?
58
+ return @before if block.nil?
50
59
 
51
60
  @before << Callback.new(callback_name(:before, tag), opts, &block)
61
+ @before.sort_by!(&:rank)
52
62
  end
53
63
 
54
64
  def after(tag = nil, opts = {}, &block)
55
- return @after.sort_by(&:rank) if block.nil?
65
+ return @after if block.nil?
56
66
 
57
67
  @after << Callback.new(callback_name(:after, tag), opts, &block)
68
+ @after.sort_by!(&:rank)
58
69
  end
59
70
 
60
71
  def raised(tag = nil, opts = {}, &block)
61
- return @raised.sort_by(&:rank) if block.nil?
72
+ return @raised if block.nil?
62
73
 
63
74
  @raised << Callback.new(callback_name(:raised, tag), opts, &block)
75
+ @raised.sort_by!(&:rank)
64
76
  end
65
77
 
66
78
  def ensured(tag = nil, opts = {}, &block)
67
- return @ensured.sort_by(&:rank) if block.nil?
79
+ return @ensured if block.nil?
68
80
 
69
81
  @ensured << Callback.new(callback_name(:ensured, tag), opts, &block)
82
+ @ensured.sort_by!(&:rank)
70
83
  end
71
84
 
72
85
  def depends_on(&block)
@@ -109,11 +122,25 @@ module Sqreen
109
122
  @before = []
110
123
  @after = []
111
124
  @raised = []
125
+ @ensured = []
112
126
  end
113
127
 
114
128
  def self.wrapper(hook)
129
+ timed_hooks_proc = proc do |t|
130
+ if (request = Thread.current[:sqreen_http_request])
131
+ request[:timed_hooks] << t if request[:timed_level] >= 1
132
+ end
133
+ end
134
+ timed_callbacks_proc = proc do |t|
135
+ if (request = Thread.current[:sqreen_http_request])
136
+ request[:timed_callbacks] << t if request[:timed_level] >= 1
137
+ end
138
+ end
139
+
115
140
  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]
141
+ request = Thread.current[:sqreen_http_request]
142
+
143
+ if Thread.current[:sqreen_hook_entered]
117
144
  if hook.point.super?
118
145
  return super(*args, &block)
119
146
  else
@@ -121,49 +148,88 @@ module Sqreen
121
148
  end
122
149
  end
123
150
 
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}" }
151
+ if request && request[:time_budget_expended] && (hook.before + hook.after + hook.raised + hook.ensured).none?(&:mandatory)
152
+ if request[:timed_level] >= 2
153
+ begin
154
+ request[:skipped_callbacks].concat(hook.before)
155
+
156
+ if hook.point.super?
157
+ return super(*args, &block)
158
+ else
159
+ return hook.point.apply(self, 'sqreen_hook', *args, &block)
160
+ end
161
+ rescue ::Exception # rubocop:disable Lint/RescueException
162
+ request[:skipped_callbacks].concat(hook.raised)
163
+ raise
164
+ else
165
+ request[:skipped_callbacks].concat(hook.after)
166
+ ensure
167
+ request[:skipped_callbacks].concat(hook.ensured)
168
+ end
169
+ else
170
+ if hook.point.super? # rubocop:disable Style/IfInsideElse
171
+ return super(*args, &block)
172
+ else
173
+ return hook.point.apply(self, 'sqreen_hook', *args, &block)
174
+ end
175
+ end
176
+ end
177
+
178
+ hook_point_super = hook.point.super?
179
+ logger = Sqreen::Graft.logger
180
+ logger_debug = Sqreen::Graft.logger.debug?
181
+
182
+ Timer.new(hook.point, &timed_hooks_proc).measure do |chrono|
183
+ # budget implies request
184
+ # TODO: make budget depend on a generic context (currently "request")
185
+ budget = request[:time_budget] if request
186
+ if budget
187
+ budget_threshold = request[:time_budget_threshold]
188
+ budget_ratio = request[:time_budget_ratio]
189
+ sqreen_timer = request[:sqreen_timer]
190
+ request_timer = request[:request_timer]
191
+ end
128
192
 
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
193
  hooked_call = HookedCall.new(self, args)
132
194
 
133
195
  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|
196
+ begin
197
+ sqreen_timer.start if budget
198
+ Thread.current[:sqreen_hook_entered] = true
199
+
200
+ # TODO: make Call have #ball to throw by cb
201
+ # TODO: can Call be the ball? r = catch(Call.new, &c)
202
+ # TODO: is catch return value a Call? a #dispatch?
203
+ # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
204
+ # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
205
+ # TODO: HookCall x CallbackCollection#each_with_call x Flow
206
+ # TODO: TimedHookCall TimedCallbackCall
207
+ # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
208
+
209
+ request_elapsed = request_timer.elapsed if budget
210
+
149
211
  hook.before.each do |c|
150
212
  next if c.ignore && c.ignore.call
151
213
 
152
- if timer && !c.mandatory
153
- remaining = budget - timer.elapsed
214
+ if budget && !c.mandatory
215
+ sqreen_elapsed = sqreen_timer.elapsed
216
+ if budget_ratio && !request[:time_budget_expended]
217
+ fixed_budget = budget_threshold
218
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
219
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
220
+ else
221
+ remaining = budget_threshold - sqreen_elapsed
222
+ end
154
223
  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
224
+ request[:skipped_callbacks] << c
225
+ request[:time_budget_expended] = true
156
226
  next
157
227
  end
158
228
  end
159
229
 
160
230
  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
231
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
232
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
167
233
  end
168
234
  end
169
235
 
@@ -173,52 +239,59 @@ module Sqreen
173
239
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
174
240
  break if flow.break?
175
241
  end unless hook.disabled?
242
+ rescue StandardError => e
243
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
244
+ Sqreen::RemoteException.record(e) if Sqreen.queue
176
245
  end
177
246
 
178
247
  raise hooked_call.raise if hooked_call.raising
179
248
  return hooked_call.return if hooked_call.returning
180
249
  ensure
181
250
  Thread.current[:sqreen_hook_entered] = false
182
- timer.stop if timer
183
- end
251
+ sqreen_timer.stop if budget
252
+ end unless hook.before.empty?
184
253
 
185
254
  begin
186
255
  chrono.ignore do
187
- if hook.point.super?
256
+ if hook_point_super
188
257
  hooked_call.returned = super(*(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
189
258
  else
190
259
  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
260
  end
192
261
  end
193
262
  rescue ::Exception => e # rubocop:disable Lint/RescueException
194
- timer.start if timer
195
- Thread.current[:sqreen_hook_entered] = true
196
- hooked_call.raised = e
263
+ begin
264
+ sqreen_timer.start if budget
265
+ Thread.current[:sqreen_hook_entered] = true
266
+ hooked_call.raised = e
197
267
 
198
- Sqreen::Graft.logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" }
199
- raise if hook.raised.empty?
268
+ logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" } if logger_debug
269
+ raise if hook.raised.empty?
270
+
271
+ request_elapsed = request_timer.elapsed if budget
200
272
 
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
273
  hook.raised.each do |c|
205
274
  next if c.ignore && c.ignore.call
206
275
 
207
- if timer && !c.mandatory
208
- remaining = budget - timer.elapsed
276
+ if budget && !c.mandatory
277
+ sqreen_elapsed = sqreen_timer.elapsed
278
+ if budget_ratio && !request[:time_budget_expended]
279
+ fixed_budget = budget_threshold
280
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
281
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
282
+ else
283
+ remaining = budget_threshold - sqreen_elapsed
284
+ end
209
285
  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
286
+ request[:skipped_callbacks] << c
287
+ request[:time_budget_expended] = true
211
288
  next
212
289
  end
213
290
  end
214
291
 
215
292
  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
293
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
294
+ 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
295
  end
223
296
  end
224
297
 
@@ -228,6 +301,9 @@ module Sqreen
228
301
  hooked_call.retrying = true if flow.retry?
229
302
  break if flow.break?
230
303
  end unless hook.disabled?
304
+ rescue StandardError => e
305
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
306
+ Sqreen::RemoteException.record(e) if Sqreen.queue
231
307
  end
232
308
 
233
309
  retry if hooked_call.retrying
@@ -235,30 +311,37 @@ module Sqreen
235
311
  return hooked_call.return if hooked_call.returning
236
312
  raise
237
313
  else
238
- timer.start if timer
239
- Thread.current[:sqreen_hook_entered] = true
314
+ begin
315
+ sqreen_timer.start if budget
316
+ Thread.current[:sqreen_hook_entered] = true
317
+
318
+ # TODO: hooked_call.returning should be always false here?
319
+ return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.after.empty?
320
+
321
+ request_elapsed = request_timer.elapsed if budget
240
322
 
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
323
  hook.after.each do |c|
245
324
  next if c.ignore && c.ignore.call
246
325
 
247
- if timer && !c.mandatory
248
- remaining = budget - timer.elapsed
326
+ if budget && !c.mandatory
327
+ sqreen_elapsed = sqreen_timer.elapsed
328
+ if budget_ratio && !request[:time_budget_expended]
329
+ fixed_budget = budget_threshold
330
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
331
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
332
+ else
333
+ remaining = budget_threshold - sqreen_elapsed
334
+ end
249
335
  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
336
+ request[:skipped_callbacks] << c
337
+ request[:time_budget_expended] = true
251
338
  next
252
339
  end
253
340
  end
254
341
 
255
342
  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
343
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
344
+ 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
345
  end
263
346
  end
264
347
 
@@ -267,34 +350,48 @@ module Sqreen
267
350
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
268
351
  break if flow.break?
269
352
  end unless hook.disabled?
353
+ rescue StandardError => e
354
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
355
+ Sqreen::RemoteException.record(e) if Sqreen.queue
270
356
  end
271
357
 
272
358
  raise hooked_call.raise if hooked_call.raising
273
359
  return hooked_call.returning ? hooked_call.return : hooked_call.returned
274
360
  ensure
275
- # TODO: timer.start if someone has thrown?
361
+ begin
362
+ # TODO: sqreen_timer.start if someone has thrown?
363
+ # TODO: sqreen_timer.stop at end of rescue+else?
364
+ # TODO: Thread.current[:sqreen_hook_entered] = true if neither rescue nor else ie thrown?
365
+ # TODO: Thread.current[:sqreen_hook_entered] = false at end of rescue+else? (risky?)
366
+
367
+ # TODO: uniform early bail out? (but don't forget sqreen_hook_entered and sqreen_timer)
368
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.ensured.empty?
369
+
370
+ # done at either rescue or else
371
+ # request_elapsed = request_timer.elapsed if budget # && !hook.ensured.empty?
276
372
 
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
373
  hook.ensured.each do |c|
281
374
  next if c.ignore && c.ignore.call
282
375
 
283
- if timer && !c.mandatory
284
- remaining = budget - timer.elapsed
376
+ if budget && !c.mandatory
377
+ sqreen_elapsed = sqreen_timer.elapsed
378
+ if budget_ratio && !request[:time_budget_expended]
379
+ fixed_budget = budget_threshold
380
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
381
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
382
+ else
383
+ remaining = budget_threshold - sqreen_elapsed
384
+ end
285
385
  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
386
+ request[:skipped_callbacks] << c
387
+ request[:time_budget_expended] = true
287
388
  next
288
389
  end
289
390
  end
290
391
 
291
392
  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
393
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
394
+ 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
395
  end
299
396
  end
300
397
 
@@ -302,11 +399,18 @@ module Sqreen
302
399
  hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
303
400
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
304
401
  break if flow.break?
305
- end unless hook.disabled?
402
+ end unless hook.ensured.empty? || hook.disabled?
403
+
404
+ Thread.current[:sqreen_hook_entered] = false
405
+ sqreen_timer.stop if budget
406
+ rescue StandardError => e
407
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
408
+ Sqreen::RemoteException.record(e) if Sqreen.queue
306
409
  end
307
410
 
308
- Thread.current[:sqreen_hook_entered] = false
309
- timer.stop if timer
411
+ # TODO: should we run the following?
412
+ # raise hooked_call.raise if hooked_call.raising
413
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned
310
414
  end
311
415
  end # chrono
312
416
  end