sqreen 1.20.1-java → 1.21.0.beta3-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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