sqreen 1.20.1-java → 1.21.0.beta3-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +16 -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 +9 -2
  8. data/lib/sqreen/conditionable.rb +24 -6
  9. data/lib/sqreen/configuration.rb +1 -1
  10. data/lib/sqreen/deferred_logger.rb +50 -14
  11. data/lib/sqreen/deliveries/batch.rb +8 -1
  12. data/lib/sqreen/deprecation.rb +38 -0
  13. data/lib/sqreen/ecosystem.rb +96 -0
  14. data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
  15. data/lib/sqreen/ecosystem/exception_reporting.rb +26 -0
  16. data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
  17. data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
  18. data/lib/sqreen/ecosystem/loggable.rb +13 -0
  19. data/lib/sqreen/ecosystem/module_api.rb +30 -0
  20. data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
  21. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
  22. data/lib/sqreen/ecosystem/module_api/message_producer.rb +51 -0
  23. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
  24. data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
  25. data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
  26. data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
  27. data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
  28. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
  29. data/lib/sqreen/ecosystem/module_registry.rb +44 -0
  30. data/lib/sqreen/ecosystem/redis/redis_connection.rb +43 -0
  31. data/lib/sqreen/ecosystem/tracing/modules/client.rb +31 -0
  32. data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
  33. data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
  34. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
  35. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
  36. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
  37. data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
  38. data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
  39. data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
  40. data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
  41. data/lib/sqreen/ecosystem_integration.rb +87 -0
  42. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +99 -0
  43. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +42 -0
  44. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
  45. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
  46. data/lib/sqreen/events/request_record.rb +0 -1
  47. data/lib/sqreen/frameworks/generic.rb +24 -1
  48. data/lib/sqreen/frameworks/rails.rb +0 -7
  49. data/lib/sqreen/frameworks/request_recorder.rb +2 -0
  50. data/lib/sqreen/graft/call.rb +106 -19
  51. data/lib/sqreen/graft/callback.rb +1 -1
  52. data/lib/sqreen/graft/hook.rb +212 -100
  53. data/lib/sqreen/graft/hook_point.rb +18 -11
  54. data/lib/sqreen/legacy/instrumentation.rb +22 -10
  55. data/lib/sqreen/legacy/old_event_submission_strategy.rb +9 -2
  56. data/lib/sqreen/log.rb +3 -2
  57. data/lib/sqreen/log/loggable.rb +1 -0
  58. data/lib/sqreen/logger.rb +24 -0
  59. data/lib/sqreen/metrics.rb +1 -0
  60. data/lib/sqreen/metrics/req_detailed.rb +41 -0
  61. data/lib/sqreen/metrics_store.rb +11 -0
  62. data/lib/sqreen/null_logger.rb +22 -0
  63. data/lib/sqreen/remote_command.rb +4 -0
  64. data/lib/sqreen/rules.rb +8 -4
  65. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  66. data/lib/sqreen/rules/custom_error_cb.rb +3 -3
  67. data/lib/sqreen/rules/rule_cb.rb +4 -2
  68. data/lib/sqreen/rules/waf_cb.rb +3 -3
  69. data/lib/sqreen/runner.rb +63 -8
  70. data/lib/sqreen/session.rb +2 -0
  71. data/lib/sqreen/signals/conversions.rb +6 -1
  72. data/lib/sqreen/version.rb +1 -1
  73. data/lib/sqreen/weave/budget.rb +35 -0
  74. data/lib/sqreen/weave/legacy/instrumentation.rb +274 -132
  75. data/lib/sqreen/worker.rb +6 -2
  76. metadata +46 -9
  77. data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -0,0 +1,42 @@
1
+ require 'sqreen/graft/hook'
2
+ require 'sqreen/ecosystem_integration/around_callbacks'
3
+
4
+ module Sqreen
5
+ class EcosystemIntegration
6
+ module InstrumentationService
7
+ class << self
8
+ # @param [String] module_name
9
+ # @param [String] method in form A::B#c or A::B.c
10
+ # @param [Hash{Symbol=>Proc}] spec
11
+ def instrument(module_name, method, spec)
12
+ hook = Sqreen::Graft::Hook[method].add do
13
+ if spec[:before]
14
+ cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'pre', spec[:before])
15
+ tag = "weave,rule=ecosystem_#{module_name}"
16
+ before(tag, flow: true, &cb)
17
+ end
18
+
19
+ if spec[:after]
20
+ cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'post', spec[:after])
21
+ tag = "weave,rule=ecosystem_#{module_name}"
22
+ after(tag, flow: true, &cb)
23
+ end
24
+
25
+ if spec[:raised]
26
+ cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'failing', spec[:raised])
27
+ tag = "weave,rule=ecosystem_#{module_name}"
28
+ raised(tag, flow: true, &cb)
29
+ end
30
+
31
+ if spec[:ensured]
32
+ cb = AroundCallbacks.wrap_instrumentation_hook(module_name, 'finally', spec[:ensured])
33
+ tag = "weave,rule=ecosystem_#{module_name}"
34
+ ensured(tag, flow: true, &cb)
35
+ end
36
+ end
37
+ hook.install
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ require 'sqreen/events/remote_exception'
2
+ require 'sqreen/log/loggable'
3
+
4
+ module Sqreen
5
+ class EcosystemIntegration
6
+ # This class gets notified of request start/end and
7
+ # 1) distributes such events to listeners (typically ecosystem modules;
8
+ # the method add_start_observer is exposed to ecosystem modules through
9
+ # +Sqreen::Ecosystem::ModuleApi::EventListener+ and the dispatch table).
10
+ # 2) keeps track of whether a request is active on this thread. This is
11
+ # so that users of this class can have this information without needing
12
+ # to subscribe to request start/events and keeping thread local state
13
+ # themselves.
14
+ # XXX: Since the Ecosystem is also notified of request start/end, it could
15
+ # notify its modules of request start without going through the dispatch
16
+ # table and call add_start_observer. We need to think if we want to keep
17
+ # the transaction / request distinction or if they should just be
18
+ # assumed to be the same, though.
19
+ class RequestLifecycleTracking
20
+ include Sqreen::Log::Loggable
21
+
22
+ def initialize
23
+ @start_observers = []
24
+ @tl_key = "#{object_id}_req_in_flight"
25
+ end
26
+
27
+ # API for classes needing to know the request state
28
+
29
+ # @param cb A callback taking a Rack::Request
30
+ def add_start_observer(cb)
31
+ @start_observers << cb
32
+ end
33
+
34
+ def in_request?
35
+ Thread.current[@tl_key] ? true : false
36
+ end
37
+
38
+ # API for classes notifying this one of request events
39
+
40
+ def notify_request_start(rack_req)
41
+ Thread.current[@tl_key] = true
42
+ return if @start_observers.empty?
43
+ @start_observers.each do |cb|
44
+ begin
45
+ cb.call(rack_req)
46
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
47
+ logger.warn { "Error calling #{cb} on request start: #{e.message}" }
48
+ Sqreen::RemoteException.record(e)
49
+ end
50
+ end
51
+ end
52
+
53
+ def notify_request_end
54
+ Thread.current[@tl_key] = false
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,35 @@
1
+ require 'sqreen/log/loggable'
2
+
3
+ module Sqreen
4
+ class EcosystemIntegration
5
+ class SignalConsumption
6
+ include Sqreen::Log::Loggable
7
+
8
+ PAYLOAD_CREATOR_SECTIONS = %w[request response params headers].freeze
9
+
10
+ # @param [Sqreen::Frameworks::GenericFramework] framework
11
+ # @param [Sqreen::EcosystemIntegration::RequestLifecycleTracking]
12
+ # @param [Sqreen::CappedQueue]
13
+ def initialize(framework, req_lifecycle, queue)
14
+ @framework = framework
15
+ @req_lifecycle = req_lifecycle
16
+ @queue = queue
17
+ end
18
+
19
+ def consume_signal(signal)
20
+ # transitional
21
+ unless Sqreen.features.fetch('use_signals', DEFAULT_USE_SIGNALS)
22
+ logger.debug { "Discarding signal #{signal} (signals disabled)" }
23
+ return
24
+ end
25
+
26
+ if @req_lifecycle.in_request?
27
+ # add it to the request record
28
+ @framework.observe(:signals, signal, PAYLOAD_CREATOR_SECTIONS, true)
29
+ else
30
+ @queue.push signal
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -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
@@ -22,8 +22,17 @@ module Sqreen
22
22
  include RequestRecorder
23
23
  attr_accessor :sqreen_configuration
24
24
 
25
+ attr_writer :req_start_cb, :req_end_cb
26
+
25
27
  def initialize
26
28
  clean_request_record
29
+
30
+ # for notifying the ecosystem of request boundaries
31
+ # XXX: this should be refactored. It shouldn't be
32
+ # the framework doing these notifications to the ecosystem
33
+ # Probably the rule callback should do it itself
34
+ @req_start_cb = Proc.new {}
35
+ @req_end_cb = Proc.new {}
27
36
  end
28
37
 
29
38
  # What kind of database is this
@@ -209,7 +218,16 @@ module Sqreen
209
218
 
210
219
  # Should the agent not be starting up?
211
220
  def prevent_startup
221
+ # SQREEN-880 - prevent Sqreen startup on Sidekiq workers
222
+ return :sidekiq_cli if defined?(Sidekiq::CLI)
223
+ return :delayed_job if defined?(Delayed::Command)
224
+
225
+ # Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
226
+ run_in_test = sqreen_configuration.get(:run_in_test)
227
+ return :rake if !run_in_test && $0.end_with?('rake')
228
+
212
229
  return :irb if $0 == 'irb'
230
+
213
231
  return if sqreen_configuration.nil?
214
232
  disable = sqreen_configuration.get(:disable)
215
233
  return :config_disable if disable == true || disable.to_s.to_i == 1
@@ -251,8 +269,12 @@ module Sqreen
251
269
  # Nota: cleanup should be performed at end of request (see clean_request)
252
270
  def store_request(object)
253
271
  return unless ensure_rack_loaded
272
+
273
+ rack_req = Rack::Request.new(object)
274
+ @req_start_cb.call(rack_req)
275
+
254
276
  self.remaining_perf_budget = Sqreen.performance_budget
255
- SharedStorage.set(:request, Rack::Request.new(object))
277
+ SharedStorage.set(:request, rack_req)
256
278
  SharedStorage.set(:xss_params, nil)
257
279
  SharedStorage.set(:whitelisted, nil)
258
280
  SharedStorage.set(:request_overtime, nil)
@@ -281,6 +303,7 @@ module Sqreen
281
303
  SharedStorage.set(:xss_params, nil)
282
304
  SharedStorage.set(:whitelisted, nil)
283
305
  SharedStorage.set(:request_overtime, nil)
306
+ @req_end_cb.call
284
307
  end
285
308
 
286
309
  def remaining_perf_budget
@@ -103,13 +103,6 @@ module Sqreen
103
103
  run_in_test = sqreen_configuration.get(:run_in_test)
104
104
  return :rails_test if !run_in_test && (Rails.env.test? || Rails.env.cucumber?)
105
105
 
106
- # SQREEN-880 - prevent Sqreen startup on Sidekiq workers
107
- return :sidekiq_cli if defined?(Sidekiq::CLI)
108
- return :delayed_job if defined?(Delayed::Command)
109
-
110
- # Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
111
- return :rake if !run_in_test && $0.end_with?('rake')
112
-
113
106
  return nil unless defined?(Rails::CommandsTasks)
114
107
  return nil if defined?(Rails::Server)
115
108
  return :rails_console if defined?(Rails::Console)
@@ -69,6 +69,8 @@ module Sqreen
69
69
 
70
70
  # signals require request section to be present
71
71
  payload_requests << 'request'
72
+ # for signals, response is optional, but the backend team wants them
73
+ payload_requests << 'response'
72
74
  payload = payload_creator.payload(payload_requests)
73
75
  payload[:observed] = observed_items
74
76
 
@@ -27,6 +27,10 @@ module Sqreen
27
27
  def raise(value)
28
28
  Flow.raise(value)
29
29
  end
30
+
31
+ def noop
32
+ Flow.noop
33
+ end
30
34
  end
31
35
 
32
36
  class Flow
@@ -46,12 +50,17 @@ module Sqreen
46
50
  def raise(value)
47
51
  new(:raise, value)
48
52
  end
53
+
54
+ def noop
55
+ new(:noop, nil)
56
+ end
49
57
  end
50
58
 
51
59
  def initialize(action, value, brk = false)
52
60
  @action = action
53
61
  @value = value
54
62
  @break = brk
63
+ @passed_conditions = false
55
64
  end
56
65
 
57
66
  def return?
@@ -91,60 +100,138 @@ module Sqreen
91
100
  def break?
92
101
  @break ? true : false
93
102
  end
103
+
104
+ def passed_conditions!
105
+ @passed_conditions = true
106
+
107
+ self
108
+ end
109
+
110
+ def passed_conditions?
111
+ @passed_conditions
112
+ end
94
113
  end
95
114
 
115
+ class TimerError < StandardError; end
116
+
96
117
  class Timer
97
118
  def self.read
98
119
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
99
120
  end
100
121
 
101
- attr_reader :tag
122
+ attr_reader :tag, :size
123
+ attr_accessor :conditions_passed
102
124
 
103
125
  def initialize(tag, &block)
104
126
  @tag = tag
105
- @blips = []
106
127
  @block = block
128
+ @tally = 0
129
+ @size = 0
107
130
  end
108
131
 
109
- def duration
110
- @blips.each_with_index.reduce(0) { |a, (e, i)| i.even? ? a - e : a + e }
132
+ def elapsed
133
+ raise(TimerError, 'Timer#elapsed when paused') if @size.even?
134
+
135
+ @tally + Timer.read
111
136
  end
112
137
 
113
- def elapsed
114
- @blips.each_with_index.reduce(0) { |a, (e, i)| i.even? ? a - e : a + e } + Timer.read
138
+ def duration
139
+ raise(TimerError, 'Timer#duration when running') if @size.odd?
140
+
141
+ @tally
115
142
  end
116
143
 
117
144
  def ignore
118
- @blips << Timer.read
145
+ raise(TimerError, 'Timer#ignore when paused') if @size.even?
146
+
147
+ @size += 1
148
+ @tally += Timer.read
119
149
  yield(self)
120
150
  ensure
121
- @blips << Timer.read
151
+ @size += 1
152
+ @tally -= Timer.read
122
153
  end
123
154
 
124
- def measure
125
- @blips << Timer.read
155
+ def measure(opts = nil)
156
+ raise(TimerError, 'Timer#measure when running') if @size.odd?
157
+
158
+ now = Timer.read
159
+
160
+ ignore = opts[:ignore] if opts
161
+ if ignore
162
+ ignore.size += 1
163
+ ignore.tally += now
164
+ end
165
+
166
+ @size += 1
167
+ @tally -= now
168
+
126
169
  yield(self)
127
170
  ensure
128
- @blips << Timer.read
171
+ now = Timer.read
172
+
173
+ if ignore
174
+ ignore.size += 1
175
+ ignore.tally -= now
176
+ end
177
+
178
+ @size += 1
179
+ @tally += now
180
+
129
181
  @block.call(self) if @block
130
- Sqreen::Graft.logger.debug { "#{@tag}: time=%.03fus" % (duration * 1_000_000) }
131
182
  end
132
183
 
133
- def start
134
- @blips << Timer.read
184
+ def start(at = Timer.read)
185
+ raise(TimerError, 'Timer#start when started') unless @size.even?
186
+
187
+ @size += 1
188
+ @tally -= at
189
+
190
+ at
191
+ end
192
+
193
+ def stop(at = Timer.read)
194
+ raise(TimerError, 'Timer#stop when unstarted') unless @size.odd?
195
+
196
+ @size += 1
197
+ @tally += at
198
+
199
+ at
135
200
  end
136
201
 
137
- def stop
138
- @blips << Timer.read
202
+ def started?
203
+ @size != 0 && @size.odd?
139
204
  end
140
205
 
141
- def size
142
- @blips.size
206
+ def stopped?
207
+ @size != 0 && @size.even?
208
+ end
209
+
210
+ def running?
211
+ @size.odd?
212
+ end
213
+
214
+ def paused?
215
+ @size.even?
216
+ end
217
+
218
+ def include_measurements(another_timer)
219
+ @blips += another_timer.instance_variable_get(:@blips)
220
+ end
221
+
222
+ def start_and_end
223
+ raise 'Not exactly two measurements recorded' unless size == 2
224
+ @blips
143
225
  end
144
226
 
145
227
  def to_s
146
- "#{@tag}: time=%.03fus" % (duration * 1_000_000)
228
+ "#{@tag}: time=%.03fus" % (@tally * 1_000_000)
147
229
  end
230
+
231
+ protected
232
+
233
+ attr_reader :tally
234
+ attr_writer :size, :tally
148
235
  end
149
236
  end
150
237
  end
@@ -21,7 +21,7 @@ module Sqreen
21
21
  end
22
22
 
23
23
  def call(*args, &block)
24
- Sqreen::Graft.logger.debug { "[#{Process.pid}] Callback #{@name} disabled:#{disabled?}" }
24
+ # Sqreen::Graft.logger.debug { "[#{Process.pid}] Callback #{@name} disabled:#{disabled?}" } if Sqreen::Graft.logger.debug?
25
25
  return if @disabled
26
26
  @block.call(*args, &block)
27
27
  end
@@ -7,23 +7,34 @@ require 'sqreen/graft'
7
7
  require 'sqreen/graft/call'
8
8
  require 'sqreen/graft/callback'
9
9
  require 'sqreen/graft/hook_point'
10
+ require 'sqreen/weave'
11
+ require 'sqreen/runner' # Sqreen.queue
10
12
 
11
13
  module Sqreen
12
14
  module Graft
13
15
  class Hook
16
+ DEFAULT_STRATEGY = Sqreen::Graft::HookPoint::DEFAULT_STRATEGY
17
+
14
18
  @hooks = {}
15
19
 
16
- def self.[](hook_point, strategy = :chain)
20
+ def self.[](hook_point, strategy = DEFAULT_STRATEGY)
17
21
  @hooks[hook_point] ||= new(hook_point, nil, strategy)
18
22
  end
19
23
 
20
- def self.add(hook_point, strategy = :chain, &block)
24
+ def self.add(hook_point, strategy = DEFAULT_STRATEGY, &block)
21
25
  self[hook_point, strategy].add(&block)
22
26
  end
23
27
 
28
+ def self.ignore
29
+ Thread.current[:sqreen_hook_entered] = true
30
+ yield
31
+ ensure
32
+ Thread.current[:sqreen_hook_entered] = false
33
+ end
34
+
24
35
  attr_reader :point
25
36
 
26
- def initialize(hook_point, dependency_test = nil, strategy = :chain)
37
+ def initialize(hook_point, dependency_test = nil, strategy = DEFAULT_STRATEGY)
27
38
  @disabled = false
28
39
  @point = hook_point.is_a?(HookPoint) ? hook_point : HookPoint.new(hook_point, strategy)
29
40
  @before = []
@@ -46,27 +57,31 @@ module Sqreen
46
57
  end
47
58
 
48
59
  def before(tag = nil, opts = {}, &block)
49
- return @before.sort_by(&:rank) if block.nil?
60
+ return @before if block.nil?
50
61
 
51
62
  @before << Callback.new(callback_name(:before, tag), opts, &block)
63
+ @before.sort_by!(&:rank)
52
64
  end
53
65
 
54
66
  def after(tag = nil, opts = {}, &block)
55
- return @after.sort_by(&:rank) if block.nil?
67
+ return @after if block.nil?
56
68
 
57
69
  @after << Callback.new(callback_name(:after, tag), opts, &block)
70
+ @after.sort_by!(&:rank)
58
71
  end
59
72
 
60
73
  def raised(tag = nil, opts = {}, &block)
61
- return @raised.sort_by(&:rank) if block.nil?
74
+ return @raised if block.nil?
62
75
 
63
76
  @raised << Callback.new(callback_name(:raised, tag), opts, &block)
77
+ @raised.sort_by!(&:rank)
64
78
  end
65
79
 
66
80
  def ensured(tag = nil, opts = {}, &block)
67
- return @ensured.sort_by(&:rank) if block.nil?
81
+ return @ensured if block.nil?
68
82
 
69
83
  @ensured << Callback.new(callback_name(:ensured, tag), opts, &block)
84
+ @ensured.sort_by!(&:rank)
70
85
  end
71
86
 
72
87
  def depends_on(&block)
@@ -109,11 +124,32 @@ module Sqreen
109
124
  @before = []
110
125
  @after = []
111
126
  @raised = []
127
+ @ensured = []
112
128
  end
113
129
 
114
130
  def self.wrapper(hook)
131
+ timed_hooks_proc = proc do |t|
132
+ if (request = Thread.current[:sqreen_http_request])
133
+ request[:timed_hooks] << t if request[:timed_level] >= 1
134
+ end
135
+ end
136
+ timed_callbacks_proc = proc do |t|
137
+ if (request = Thread.current[:sqreen_http_request])
138
+ request[:timed_callbacks] << t if request[:timed_level] >= 1
139
+ end
140
+ end
141
+ # very hacky, but the non-local control flow with throw-catch
142
+ # makes the solution non-obvious. needs to be revisited
143
+ conditions_passed_proc = proc do
144
+ if (request = Thread.current[:sqreen_http_request])
145
+ request[:timed_callbacks].last.conditions_passed = true
146
+ end
147
+ end
148
+
115
149
  Proc.new do |*args, &block|
116
- if Thread.current[:sqreen_hook_entered] || Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:time_budget_expended]
150
+ request = Thread.current[:sqreen_http_request]
151
+
152
+ if Thread.current[:sqreen_hook_entered]
117
153
  if hook.point.super?
118
154
  return super(*args, &block)
119
155
  else
@@ -121,113 +157,159 @@ module Sqreen
121
157
  end
122
158
  end
123
159
 
124
- 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}" }
160
+ if request && request[:time_budget_expended] && (hook.before + hook.after + hook.raised + hook.ensured).none?(&:mandatory)
161
+ if request[:timed_level] >= 2
162
+ begin
163
+ request[:skipped_callbacks].concat(hook.before)
164
+
165
+ if hook.point.super?
166
+ return super(*args, &block)
167
+ else
168
+ return hook.point.apply(self, 'sqreen_hook', *args, &block)
169
+ end
170
+ rescue ::Exception # rubocop:disable Lint/RescueException
171
+ request[:skipped_callbacks].concat(hook.raised)
172
+ raise
173
+ else
174
+ request[:skipped_callbacks].concat(hook.after)
175
+ ensure
176
+ request[:skipped_callbacks].concat(hook.ensured)
177
+ end
178
+ else
179
+ if hook.point.super? # rubocop:disable Style/IfInsideElse
180
+ return super(*args, &block)
181
+ else
182
+ return hook.point.apply(self, 'sqreen_hook', *args, &block)
183
+ end
184
+ end
185
+ end
186
+
187
+ hook_point_super = hook.point.super?
188
+ logger = Sqreen::Graft.logger
189
+ logger_debug = Sqreen::Graft.logger.debug?
190
+
191
+ Timer.new(hook.point, &timed_hooks_proc).measure do |chrono|
192
+ # budget implies request
193
+ # TODO: make budget depend on a generic context (currently "request")
194
+ budget = request[:time_budget] if request
195
+ if request && (budget || request[:timed_level] >= 1)
196
+ sqreen_timer = request[:sqreen_timer]
197
+ end
128
198
 
129
- budget = Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:time_budget]
130
- timer = Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timer] if budget
131
199
  hooked_call = HookedCall.new(self, args)
132
200
 
133
201
  begin
134
- 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|
202
+ begin
203
+ sqreen_timer.start if sqreen_timer
204
+ Thread.current[:sqreen_hook_entered] = true
205
+
206
+ # TODO: make Call have #ball to throw by cb
207
+ # TODO: can Call be the ball? r = catch(Call.new, &c)
208
+ # TODO: is catch return value a Call? a #dispatch?
209
+ # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
210
+ # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
211
+ # TODO: HookCall x CallbackCollection#each_with_call x Flow
212
+ # TODO: TimedHookCall TimedCallbackCall
213
+ # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
214
+
149
215
  hook.before.each do |c|
150
216
  next if c.ignore && c.ignore.call
151
217
 
152
- if timer && !c.mandatory
153
- remaining = budget - timer.elapsed
154
- 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
156
- next
157
- end
218
+ if budget && !c.mandatory && request[:time_budget_expended]
219
+ request[:skipped_callbacks] << c
220
+ next
158
221
  end
159
222
 
223
+ remaining = budget - sqreen_timer.elapsed if budget
224
+
225
+ timer = nil
160
226
  flow = catch(Ball.new) do |ball|
161
- Timer.new(c.name) 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
227
+ timer = Timer.new(c.name, &timed_callbacks_proc)
228
+ timer.measure(ignore: chrono) do
229
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
230
+ end
231
+ end
232
+
233
+ if budget && timer
234
+ remaining -= timer.duration
235
+ if remaining < 0.0
236
+ request[:time_budget_expended]
237
+ request[:overtime_cb] = c.name
167
238
  end
168
239
  end
169
240
 
170
241
  next unless c.flow && flow.is_a?(Flow)
242
+ conditions_passed_proc[] if flow.passed_conditions?
171
243
  hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
172
244
  hooked_call.args_pass = flow.args and hooked_call.args_passing = true if flow.args?
173
245
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
174
246
  break if flow.break?
175
247
  end unless hook.disabled?
248
+ rescue StandardError => e
249
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
250
+ Sqreen::RemoteException.record(e) if Sqreen.queue
176
251
  end
177
252
 
178
253
  raise hooked_call.raise if hooked_call.raising
179
254
  return hooked_call.return if hooked_call.returning
180
255
  ensure
181
256
  Thread.current[:sqreen_hook_entered] = false
182
- timer.stop if timer
183
- end
257
+ sqreen_timer.stop if sqreen_timer
258
+ end unless hook.before.empty?
184
259
 
185
260
  begin
186
261
  chrono.ignore do
187
- if hook.point.super?
262
+ if hook_point_super
188
263
  hooked_call.returned = super(*(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
189
264
  else
190
265
  hooked_call.returned = hook.point.apply(hooked_call.instance, 'sqreen_hook', *(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
191
266
  end
192
267
  end
193
268
  rescue ::Exception => e # rubocop:disable Lint/RescueException
194
- timer.start if timer
195
- Thread.current[:sqreen_hook_entered] = true
196
- hooked_call.raised = e
269
+ begin
270
+ sqreen_timer.start if sqreen_timer
271
+ Thread.current[:sqreen_hook_entered] = true
272
+ hooked_call.raised = e
197
273
 
198
- Sqreen::Graft.logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" }
199
- raise if hook.raised.empty?
274
+ logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" } if logger_debug
275
+ raise if hook.raised.empty?
200
276
 
201
- Timer.new("#{hook.point}@raised") do |t|
202
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_raised] << t
203
- end.measure do |raised_chrono|
204
277
  hook.raised.each do |c|
205
278
  next if c.ignore && c.ignore.call
206
279
 
207
- if timer && !c.mandatory
208
- remaining = budget - timer.elapsed
209
- 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
211
- next
212
- end
280
+ if budget && !c.mandatory && request[:time_budget_expended]
281
+ request[:skipped_callbacks] << c
282
+ next
213
283
  end
214
284
 
285
+ remaining = budget - sqreen_timer.elapsed if budget
286
+
287
+ timer = nil
215
288
  flow = catch(Ball.new) do |ball|
216
- Timer.new(c.name) 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
289
+ timer = Timer.new(c.name, &timed_callbacks_proc)
290
+ timer.measure(ignore: chrono) do
291
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, hooked_call.raised), ball)
292
+ end
293
+ end
294
+
295
+ if budget && timer
296
+ remaining -= timer.duration
297
+ if remaining < 0.0
298
+ request[:time_budget_expended]
299
+ request[:overtime_cb] = c.name
222
300
  end
223
301
  end
224
302
 
225
303
  next unless c.flow && flow.is_a?(Flow)
304
+ conditions_passed_proc[] if flow.passed_conditions?
226
305
  hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
227
306
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
228
307
  hooked_call.retrying = true if flow.retry?
229
308
  break if flow.break?
230
309
  end unless hook.disabled?
310
+ rescue StandardError => e
311
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
312
+ Sqreen::RemoteException.record(e) if Sqreen.queue
231
313
  end
232
314
 
233
315
  retry if hooked_call.retrying
@@ -235,78 +317,108 @@ module Sqreen
235
317
  return hooked_call.return if hooked_call.returning
236
318
  raise
237
319
  else
238
- timer.start if timer
239
- Thread.current[:sqreen_hook_entered] = true
320
+ begin
321
+ sqreen_timer.start if sqreen_timer
322
+ Thread.current[:sqreen_hook_entered] = true
323
+
324
+ # TODO: hooked_call.returning should be always false here?
325
+ return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.after.empty?
240
326
 
241
- Timer.new("#{hook.point}@after") do |t|
242
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_after] << t
243
- end.measure do |after_chrono|
244
327
  hook.after.each do |c|
245
328
  next if c.ignore && c.ignore.call
246
329
 
247
- if timer && !c.mandatory
248
- remaining = budget - timer.elapsed
249
- 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
251
- next
252
- end
330
+ if budget && !c.mandatory && request[:time_budget_expended]
331
+ request[:skipped_callbacks] << c
332
+ next
253
333
  end
254
334
 
335
+ remaining = budget - sqreen_timer.elapsed if budget
336
+
337
+ timer = nil
255
338
  flow = catch(Ball.new) do |ball|
256
- Timer.new(c.name) 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
339
+ timer = Timer.new(c.name, &timed_callbacks_proc)
340
+ timer.measure(ignore: chrono) do
341
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
342
+ end
343
+ end
344
+
345
+ if budget && timer
346
+ remaining -= timer.duration
347
+ if remaining < 0.0
348
+ request[:time_budget_expended]
349
+ request[:overtime_cb] = c.name
262
350
  end
263
351
  end
264
352
 
265
353
  next unless c.flow && flow.is_a?(Flow)
354
+ conditions_passed_proc[] if flow.passed_conditions?
266
355
  hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
267
356
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
268
357
  break if flow.break?
269
358
  end unless hook.disabled?
359
+ rescue StandardError => e
360
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
361
+ Sqreen::RemoteException.record(e) if Sqreen.queue
270
362
  end
271
363
 
272
364
  raise hooked_call.raise if hooked_call.raising
273
365
  return hooked_call.returning ? hooked_call.return : hooked_call.returned
274
366
  ensure
275
- # TODO: timer.start if someone has thrown?
367
+ begin
368
+ # TODO: sqreen_timer.start if someone has thrown?
369
+ # TODO: sqreen_timer.stop at end of rescue+else?
370
+ # TODO: Thread.current[:sqreen_hook_entered] = true if neither rescue nor else ie thrown?
371
+ # TODO: Thread.current[:sqreen_hook_entered] = false at end of rescue+else? (risky?)
372
+
373
+ # TODO: uniform early bail out? (but don't forget sqreen_hook_entered and sqreen_timer)
374
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.ensured.empty?
375
+
376
+ # done at either rescue or else
377
+ # request_elapsed = request_timer.elapsed if budget # && !hook.ensured.empty?
276
378
 
277
- Timer.new("#{hook.point}@ensured") do |t|
278
- Thread.current[:sqreen_http_request] && Thread.current[:sqreen_http_request][:timed_hooks_ensured] << t
279
- end.measure do |ensured_chrono|
280
379
  hook.ensured.each do |c|
281
380
  next if c.ignore && c.ignore.call
282
381
 
283
- if timer && !c.mandatory
284
- remaining = budget - timer.elapsed
285
- 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
287
- next
288
- end
382
+ if budget && !c.mandatory && request[:time_budget_expended]
383
+ request[:skipped_callbacks] << c
384
+ next
289
385
  end
290
386
 
387
+ remaining = budget - sqreen_timer.elapsed if budget
388
+
389
+ timer = nil
291
390
  flow = catch(Ball.new) do |ball|
292
- Timer.new(c.name) 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 = Timer.new(c.name, &timed_callbacks_proc)
392
+ timer.measure(ignore: chrono) do
393
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
394
+ end
395
+ end
396
+
397
+ if budget && timer
398
+ remaining -= timer.duration
399
+ if remaining < 0.0
400
+ request[:time_budget_expended]
401
+ request[:overtime_cb] = c.name
298
402
  end
299
403
  end
300
404
 
301
405
  next unless c.flow && flow.is_a?(Flow)
406
+ conditions_passed_proc[] if flow.passed_conditions?
302
407
  hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
303
408
  hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
304
409
  break if flow.break?
305
- end unless hook.disabled?
410
+ end unless hook.ensured.empty? || hook.disabled?
411
+
412
+ Thread.current[:sqreen_hook_entered] = false
413
+ sqreen_timer.stop if sqreen_timer
414
+ rescue StandardError => e
415
+ Sqreen::Weave.logger.debug { "exception:#{e.class} message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
416
+ Sqreen::RemoteException.record(e) if Sqreen.queue
306
417
  end
307
418
 
308
- Thread.current[:sqreen_hook_entered] = false
309
- timer.stop if timer
419
+ # TODO: should we run the following?
420
+ # raise hooked_call.raise if hooked_call.raising
421
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned
310
422
  end
311
423
  end # chrono
312
424
  end