sqreen 1.19.3-java → 1.21.0-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 (113) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +38 -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/agent_message.rb +20 -0
  7. data/lib/sqreen/aggregated_metric.rb +25 -0
  8. data/lib/sqreen/attack_detected.html +1 -2
  9. data/lib/sqreen/ca.crt +24 -0
  10. data/lib/sqreen/condition_evaluator.rb +8 -2
  11. data/lib/sqreen/configuration.rb +11 -5
  12. data/lib/sqreen/deferred_logger.rb +50 -14
  13. data/lib/sqreen/deliveries/batch.rb +12 -2
  14. data/lib/sqreen/deliveries/simple.rb +4 -0
  15. data/lib/sqreen/deprecation.rb +38 -0
  16. data/lib/sqreen/ecosystem.rb +123 -0
  17. data/lib/sqreen/ecosystem/databases/database_connection_data.rb +23 -0
  18. data/lib/sqreen/ecosystem/databases/mongo.rb +39 -0
  19. data/lib/sqreen/ecosystem/databases/mysql.rb +54 -0
  20. data/lib/sqreen/ecosystem/databases/postgres.rb +51 -0
  21. data/lib/sqreen/ecosystem/databases/redis.rb +36 -0
  22. data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
  23. data/lib/sqreen/ecosystem/exception_reporting.rb +28 -0
  24. data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
  25. data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
  26. data/lib/sqreen/ecosystem/loggable.rb +13 -0
  27. data/lib/sqreen/ecosystem/messaging/bunny.rb +61 -0
  28. data/lib/sqreen/ecosystem/messaging/kafka.rb +70 -0
  29. data/lib/sqreen/ecosystem/messaging/kinesis.rb +66 -0
  30. data/lib/sqreen/ecosystem/messaging/sqs.rb +68 -0
  31. data/lib/sqreen/ecosystem/module_api.rb +30 -0
  32. data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
  33. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
  34. data/lib/sqreen/ecosystem/module_api/message_producer.rb +57 -0
  35. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
  36. data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
  37. data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
  38. data/lib/sqreen/ecosystem/module_api/tracing/consumer_data.rb +13 -0
  39. data/lib/sqreen/ecosystem/module_api/tracing/messaging_data.rb +35 -0
  40. data/lib/sqreen/ecosystem/module_api/tracing/producer_data.rb +13 -0
  41. data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
  42. data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
  43. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
  44. data/lib/sqreen/ecosystem/module_registry.rb +48 -0
  45. data/lib/sqreen/ecosystem/tracing/modules/client.rb +35 -0
  46. data/lib/sqreen/ecosystem/tracing/modules/consumer.rb +35 -0
  47. data/lib/sqreen/ecosystem/tracing/modules/determine_ip.rb +28 -0
  48. data/lib/sqreen/ecosystem/tracing/modules/producer.rb +35 -0
  49. data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
  50. data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
  51. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
  52. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
  53. data/lib/sqreen/ecosystem/tracing/signals/tracing_consumer.rb +56 -0
  54. data/lib/sqreen/ecosystem/tracing/signals/tracing_producer.rb +56 -0
  55. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
  56. data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
  57. data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
  58. data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
  59. data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
  60. data/lib/sqreen/ecosystem_integration.rb +81 -0
  61. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +89 -0
  62. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +38 -0
  63. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
  64. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
  65. data/lib/sqreen/endpoint_testing.rb +184 -0
  66. data/lib/sqreen/event.rb +7 -5
  67. data/lib/sqreen/events/attack.rb +23 -18
  68. data/lib/sqreen/events/remote_exception.rb +0 -22
  69. data/lib/sqreen/events/request_record.rb +15 -71
  70. data/lib/sqreen/frameworks/generic.rb +24 -1
  71. data/lib/sqreen/frameworks/rails.rb +0 -7
  72. data/lib/sqreen/frameworks/request_recorder.rb +15 -2
  73. data/lib/sqreen/graft/call.rb +85 -18
  74. data/lib/sqreen/graft/callback.rb +1 -1
  75. data/lib/sqreen/graft/hook.rb +192 -88
  76. data/lib/sqreen/graft/hook_point.rb +18 -11
  77. data/lib/sqreen/kit/signals/specialized/aggregated_metric.rb +72 -0
  78. data/lib/sqreen/kit/signals/specialized/attack.rb +57 -0
  79. data/lib/sqreen/kit/signals/specialized/binning_metric.rb +76 -0
  80. data/lib/sqreen/kit/signals/specialized/http_trace.rb +26 -0
  81. data/lib/sqreen/kit/signals/specialized/sdk_track_call.rb +50 -0
  82. data/lib/sqreen/kit/signals/specialized/sqreen_exception.rb +59 -0
  83. data/lib/sqreen/legacy/instrumentation.rb +22 -10
  84. data/lib/sqreen/legacy/old_event_submission_strategy.rb +228 -0
  85. data/lib/sqreen/legacy/waf_redactions.rb +49 -0
  86. data/lib/sqreen/log.rb +3 -2
  87. data/lib/sqreen/log/loggable.rb +2 -1
  88. data/lib/sqreen/logger.rb +24 -0
  89. data/lib/sqreen/metrics/base.rb +3 -0
  90. data/lib/sqreen/metrics_store.rb +33 -12
  91. data/lib/sqreen/null_logger.rb +22 -0
  92. data/lib/sqreen/performance_notifications/binned_metrics.rb +8 -2
  93. data/lib/sqreen/remote_command.rb +4 -0
  94. data/lib/sqreen/rules.rb +12 -6
  95. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  96. data/lib/sqreen/rules/custom_error_cb.rb +3 -3
  97. data/lib/sqreen/rules/rule_cb.rb +4 -0
  98. data/lib/sqreen/rules/waf_cb.rb +14 -11
  99. data/lib/sqreen/runner.rb +122 -15
  100. data/lib/sqreen/sensitive_data_redactor.rb +19 -31
  101. data/lib/sqreen/session.rb +53 -43
  102. data/lib/sqreen/signals/conversions.rb +288 -0
  103. data/lib/sqreen/signals/http_trace_redaction.rb +111 -0
  104. data/lib/sqreen/signals/signals_submission_strategy.rb +78 -0
  105. data/lib/sqreen/version.rb +1 -1
  106. data/lib/sqreen/weave/budget.rb +46 -0
  107. data/lib/sqreen/weave/legacy/instrumentation.rb +194 -103
  108. data/lib/sqreen/worker.rb +6 -2
  109. metadata +96 -7
  110. data/lib/sqreen/backport.rb +0 -9
  111. data/lib/sqreen/backport/clock_gettime.rb +0 -74
  112. data/lib/sqreen/backport/original_name.rb +0 -88
  113. data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -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)
@@ -58,12 +58,22 @@ module Sqreen
58
58
  Sqreen.log.debug { "close_request_record called. observed_items: #{observed_items}" }
59
59
 
60
60
  clean_request_record if observed_items.nil?
61
- if only_metric_observation
61
+ if Sqreen.features['use_signals'] || only_metric_observation
62
62
  push_metrics(observations_queue, queue)
63
- return clean_request_record
64
63
  end
64
+
65
+ if only_metric_observation
66
+ clean_request_record
67
+ return
68
+ end
69
+
70
+ # signals require request section to be present
71
+ payload_requests << 'request'
72
+ # for signals, response is optional, but the backend team wants them
73
+ payload_requests << 'response'
65
74
  payload = payload_creator.payload(payload_requests)
66
75
  payload[:observed] = observed_items
76
+
67
77
  queue.push create_request_record(payload)
68
78
  clean_request_record
69
79
  end
@@ -79,10 +89,13 @@ module Sqreen
79
89
  @redactor ||= SensitiveDataRedactor.from_config
80
90
  end
81
91
 
92
+ # pushes metric observations to the observations queue
93
+ # and clears the list for the request record
82
94
  def push_metrics(observations_queue, event_queue)
83
95
  observed_items[:observations].each do |obs|
84
96
  observations_queue.push obs
85
97
  end
98
+ observed_items[:observations] = []
86
99
  return unless observations_queue.size > MAX_OBS_QUEUE_LENGTH / 2
87
100
  event_queue.push Sqreen::METRICS_EVENT
88
101
  end
@@ -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