sqreen 1.22.0 → 1.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/bin/sqreen +43 -0
  4. data/lib/sqreen/actions.rb +1 -1
  5. data/lib/sqreen/actions/actions_index.rb +5 -1
  6. data/lib/sqreen/actions/base.rb +1 -1
  7. data/lib/sqreen/actions/block_ip.rb +1 -1
  8. data/lib/sqreen/actions/block_user.rb +1 -1
  9. data/lib/sqreen/actions/ip_range_indexed_action_class.rb +1 -1
  10. data/lib/sqreen/actions/ip_ranges_index.rb +1 -1
  11. data/lib/sqreen/actions/redirect_ip.rb +1 -1
  12. data/lib/sqreen/actions/redirect_user.rb +1 -1
  13. data/lib/sqreen/actions/repository.rb +1 -1
  14. data/lib/sqreen/actions/unknown_action_type.rb +1 -1
  15. data/lib/sqreen/actions/user_action_class.rb +1 -1
  16. data/lib/sqreen/actions/users_index.rb +5 -1
  17. data/lib/sqreen/agent_message.rb +5 -0
  18. data/lib/sqreen/aggregated_metric.rb +5 -0
  19. data/lib/sqreen/attack_blocked.rb +1 -1
  20. data/lib/sqreen/binding_accessor.rb +1 -1
  21. data/lib/sqreen/binding_accessor/path_elem.rb +1 -1
  22. data/lib/sqreen/binding_accessor/transforms.rb +1 -1
  23. data/lib/sqreen/call_countable.rb +1 -1
  24. data/lib/sqreen/capped_queue.rb +1 -1
  25. data/lib/sqreen/cb.rb +1 -1
  26. data/lib/sqreen/condition_evaluator.rb +1 -1
  27. data/lib/sqreen/conditionable.rb +1 -1
  28. data/lib/sqreen/configuration.rb +2 -0
  29. data/lib/sqreen/context.rb +1 -1
  30. data/lib/sqreen/default_cb.rb +1 -1
  31. data/lib/sqreen/deferred_logger.rb +1 -1
  32. data/lib/sqreen/deliveries.rb +1 -1
  33. data/lib/sqreen/deliveries/batch.rb +1 -1
  34. data/lib/sqreen/deliveries/simple.rb +1 -1
  35. data/lib/sqreen/dependency.rb +1 -1
  36. data/lib/sqreen/dependency/new_relic.rb +1 -1
  37. data/lib/sqreen/deprecation.rb +1 -1
  38. data/lib/sqreen/ecosystem.rb +5 -0
  39. data/lib/sqreen/ecosystem/databases/database_connection_data.rb +5 -0
  40. data/lib/sqreen/ecosystem/databases/mongo.rb +5 -0
  41. data/lib/sqreen/ecosystem/databases/mysql.rb +5 -0
  42. data/lib/sqreen/ecosystem/databases/postgres.rb +5 -0
  43. data/lib/sqreen/ecosystem/databases/redis.rb +5 -0
  44. data/lib/sqreen/ecosystem/dispatch_table.rb +5 -0
  45. data/lib/sqreen/ecosystem/exception_reporting.rb +5 -0
  46. data/lib/sqreen/ecosystem/http/net_http.rb +5 -0
  47. data/lib/sqreen/ecosystem/http/rack_request.rb +5 -0
  48. data/lib/sqreen/ecosystem/loggable.rb +5 -0
  49. data/lib/sqreen/ecosystem/messaging/bunny.rb +5 -0
  50. data/lib/sqreen/ecosystem/messaging/kafka.rb +5 -0
  51. data/lib/sqreen/ecosystem/messaging/kinesis.rb +5 -0
  52. data/lib/sqreen/ecosystem/messaging/sqs.rb +5 -0
  53. data/lib/sqreen/ecosystem/module_api.rb +5 -0
  54. data/lib/sqreen/ecosystem/module_api/event_listener.rb +5 -0
  55. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +5 -0
  56. data/lib/sqreen/ecosystem/module_api/message_producer.rb +5 -0
  57. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +5 -0
  58. data/lib/sqreen/ecosystem/module_api/tracing.rb +5 -0
  59. data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +5 -0
  60. data/lib/sqreen/ecosystem/module_api/tracing/consumer_data.rb +5 -0
  61. data/lib/sqreen/ecosystem/module_api/tracing/messaging_data.rb +5 -0
  62. data/lib/sqreen/ecosystem/module_api/tracing/producer_data.rb +5 -0
  63. data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +5 -0
  64. data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +5 -0
  65. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +5 -0
  66. data/lib/sqreen/ecosystem/module_registry.rb +5 -0
  67. data/lib/sqreen/ecosystem/tracing/modules/client.rb +5 -0
  68. data/lib/sqreen/ecosystem/tracing/modules/consumer.rb +5 -0
  69. data/lib/sqreen/ecosystem/tracing/modules/determine_ip.rb +5 -0
  70. data/lib/sqreen/ecosystem/tracing/modules/producer.rb +5 -0
  71. data/lib/sqreen/ecosystem/tracing/modules/server.rb +5 -0
  72. data/lib/sqreen/ecosystem/tracing/sampler.rb +5 -0
  73. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +5 -0
  74. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +5 -0
  75. data/lib/sqreen/ecosystem/tracing/signals/tracing_consumer.rb +5 -0
  76. data/lib/sqreen/ecosystem/tracing/signals/tracing_producer.rb +5 -0
  77. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +5 -0
  78. data/lib/sqreen/ecosystem/tracing_broker.rb +5 -0
  79. data/lib/sqreen/ecosystem/tracing_id_setup.rb +5 -0
  80. data/lib/sqreen/ecosystem/transaction_storage.rb +5 -0
  81. data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +5 -0
  82. data/lib/sqreen/ecosystem_integration.rb +5 -0
  83. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +5 -0
  84. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +5 -0
  85. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +5 -0
  86. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +6 -8
  87. data/lib/sqreen/endpoint_testing.rb +5 -0
  88. data/lib/sqreen/error_handling_middleware.rb +1 -1
  89. data/lib/sqreen/event.rb +1 -1
  90. data/lib/sqreen/events/attack.rb +9 -1
  91. data/lib/sqreen/events/remote_exception.rb +1 -1
  92. data/lib/sqreen/events/request_record.rb +1 -1
  93. data/lib/sqreen/exception.rb +1 -1
  94. data/lib/sqreen/formatter_with_tid.rb +1 -1
  95. data/lib/sqreen/framework_cb.rb +1 -1
  96. data/lib/sqreen/frameworks/generic.rb +18 -1
  97. data/lib/sqreen/frameworks/sqreen_test.rb +1 -1
  98. data/lib/sqreen/graft.rb +1 -1
  99. data/lib/sqreen/graft/call.rb +1 -1
  100. data/lib/sqreen/graft/callback.rb +1 -1
  101. data/lib/sqreen/graft/hook.rb +8 -294
  102. data/lib/sqreen/graft/hook.ruby_2.rb +305 -0
  103. data/lib/sqreen/graft/hook.ruby_3.rb +305 -0
  104. data/lib/sqreen/graft/hook_point.rb +7 -7
  105. data/lib/sqreen/graft/hook_point.ruby_2.rb +18 -0
  106. data/lib/sqreen/graft/hook_point.ruby_3.rb +19 -0
  107. data/lib/sqreen/graft/hook_point_error.rb +1 -1
  108. data/lib/sqreen/invalid_signature_exception.rb +1 -1
  109. data/lib/sqreen/js.rb +1 -1
  110. data/lib/sqreen/js/call_context.rb +1 -1
  111. data/lib/sqreen/js/context_pool.rb +8 -6
  112. data/lib/sqreen/js/exec_js_runnable.rb +1 -1
  113. data/lib/sqreen/js/execjs_adapter.rb +1 -1
  114. data/lib/sqreen/js/executable_js.rb +1 -1
  115. data/lib/sqreen/js/js_service_adapter.rb +1 -1
  116. data/lib/sqreen/js/mini_racer_adapter.rb +2 -1
  117. data/lib/sqreen/js/mini_racer_executable_js.rb +2 -0
  118. data/lib/sqreen/js/thread_local_exec_js_runnable.rb +1 -1
  119. data/lib/sqreen/legacy.rb +1 -1
  120. data/lib/sqreen/log/loggable.rb +1 -1
  121. data/lib/sqreen/logger.rb +1 -1
  122. data/lib/sqreen/metrics.rb +1 -1
  123. data/lib/sqreen/metrics/average.rb +1 -1
  124. data/lib/sqreen/metrics/base.rb +1 -1
  125. data/lib/sqreen/metrics/binning.rb +1 -1
  126. data/lib/sqreen/metrics/collect.rb +1 -1
  127. data/lib/sqreen/metrics/sum.rb +1 -1
  128. data/lib/sqreen/metrics_store.rb +1 -1
  129. data/lib/sqreen/metrics_store/already_registered_metric.rb +1 -1
  130. data/lib/sqreen/metrics_store/unknown_metric.rb +1 -1
  131. data/lib/sqreen/metrics_store/unregistered_metric.rb +1 -1
  132. data/lib/sqreen/middleware.rb +1 -1
  133. data/lib/sqreen/node.rb +1 -1
  134. data/lib/sqreen/not_implemented_yet.rb +1 -1
  135. data/lib/sqreen/null_logger.rb +1 -1
  136. data/lib/sqreen/payload_creator/header_section.rb +1 -1
  137. data/lib/sqreen/performance_notifications.rb +1 -1
  138. data/lib/sqreen/performance_notifications/binned_metrics.rb +1 -1
  139. data/lib/sqreen/performance_notifications/log.rb +1 -1
  140. data/lib/sqreen/performance_notifications/log_performance.rb +1 -1
  141. data/lib/sqreen/performance_notifications/metrics.rb +1 -1
  142. data/lib/sqreen/prefix.rb +1 -1
  143. data/lib/sqreen/rails_middleware.rb +1 -1
  144. data/lib/sqreen/remote_command.rb +1 -1
  145. data/lib/sqreen/remote_command/failure_output.rb +1 -1
  146. data/lib/sqreen/rules/attrs.rb +1 -1
  147. data/lib/sqreen/rules/execjs_cb.rb +1 -0
  148. data/lib/sqreen/rules/run_user_actions.rb +1 -1
  149. data/lib/sqreen/run_when_called_cb.rb +1 -1
  150. data/lib/sqreen/runner.rb +11 -0
  151. data/lib/sqreen/safe_json.rb +1 -1
  152. data/lib/sqreen/sensitive_data_redactor.rb +2 -2
  153. data/lib/sqreen/serializer.rb +1 -1
  154. data/lib/sqreen/shared_storage.rb +1 -1
  155. data/lib/sqreen/shrink_wrap.rb +1 -1
  156. data/lib/sqreen/signals/conversions.rb +22 -2
  157. data/lib/sqreen/signals/http_trace_redaction.rb +5 -0
  158. data/lib/sqreen/signals/signals_submission_strategy.rb +5 -0
  159. data/lib/sqreen/signature_verifier.rb +1 -1
  160. data/lib/sqreen/sinatra_middleware.rb +1 -1
  161. data/lib/sqreen/sqreen_signed_verifier.rb +1 -1
  162. data/lib/sqreen/token_invalid_exception.rb +1 -1
  163. data/lib/sqreen/token_not_found_exception.rb +1 -1
  164. data/lib/sqreen/trie.rb +1 -1
  165. data/lib/sqreen/unauthorized.rb +1 -1
  166. data/lib/sqreen/util.rb +1 -1
  167. data/lib/sqreen/util/capped_array.rb +1 -1
  168. data/lib/sqreen/util/capped_hash.rb +1 -1
  169. data/lib/sqreen/util/capped_string.rb +1 -1
  170. data/lib/sqreen/util/capper.rb +1 -1
  171. data/lib/sqreen/version.rb +2 -2
  172. data/lib/sqreen/waf_error.rb +1 -1
  173. data/lib/sqreen/weave.rb +1 -1
  174. data/lib/sqreen/weave/budget.rb +1 -1
  175. data/lib/sqreen/weave/hardcoded.rb +1 -1
  176. data/lib/sqreen/weave/instrumentor.rb +1 -1
  177. data/lib/sqreen/weave/legacy.rb +1 -1
  178. data/lib/sqreen/weave/legacy/instrumentation.rb +62 -7
  179. data/lib/sqreen/web_server/generic.rb +1 -1
  180. data/lib/sqreen/web_server/webrick.rb +1 -1
  181. data/lib/sqreen/worker.rb +1 -1
  182. metadata +24 -12
@@ -0,0 +1,305 @@
1
+ # typed: ignore
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ require 'sqreen/graft/call'
7
+
8
+ module Sqreen
9
+ module Graft
10
+ class Hook
11
+ def self.wrapper(hook)
12
+ timed_hooks_proc = proc do |t|
13
+ if (request = Thread.current[:sqreen_http_request])
14
+ request[:timed_hooks] << t if request[:timed_level] >= 1
15
+ end
16
+ end
17
+ timed_callbacks_proc = proc do |t|
18
+ if (request = Thread.current[:sqreen_http_request])
19
+ request[:timed_callbacks] << t if request[:timed_level] >= 1
20
+ end
21
+ end
22
+
23
+ Proc.new do |*args, &block|
24
+ request = Thread.current[:sqreen_http_request]
25
+
26
+ if Thread.current[:sqreen_hook_entered]
27
+ if hook.point.super?
28
+ return super(*args, &block)
29
+ else
30
+ return hook.point.apply(self, 'sqreen_hook', *args, &block)
31
+ end
32
+ end
33
+
34
+ if request && request[:time_budget_expended] && (hook.before + hook.after + hook.raised + hook.ensured).none?(&:mandatory)
35
+ if request[:timed_level] >= 2
36
+ begin
37
+ request[:skipped_callbacks].concat(hook.before)
38
+
39
+ if hook.point.super?
40
+ return super(*args, &block)
41
+ else
42
+ return hook.point.apply(self, 'sqreen_hook', *args, &block)
43
+ end
44
+ rescue ::Exception # rubocop:disable Lint/RescueException
45
+ request[:skipped_callbacks].concat(hook.raised)
46
+ raise
47
+ else
48
+ request[:skipped_callbacks].concat(hook.after)
49
+ ensure
50
+ request[:skipped_callbacks].concat(hook.ensured)
51
+ end
52
+ else
53
+ if hook.point.super? # rubocop:disable Style/IfInsideElse
54
+ return super(*args, &block)
55
+ else
56
+ return hook.point.apply(self, 'sqreen_hook', *args, &block)
57
+ end
58
+ end
59
+ end
60
+
61
+ hook_point_super = hook.point.super?
62
+ logger = Sqreen::Graft.logger
63
+ logger_debug = Sqreen::Graft.logger.debug?
64
+
65
+ Timer.new(hook.point, &timed_hooks_proc).measure do |chrono|
66
+ # budget implies request
67
+ # TODO: make budget depend on a generic context (currently "request")
68
+ budget = request[:time_budget] if request
69
+ if budget
70
+ budget_threshold = request[:time_budget_threshold]
71
+ budget_ratio = request[:time_budget_ratio]
72
+ sqreen_timer = request[:sqreen_timer]
73
+ request_timer = request[:request_timer]
74
+ end
75
+
76
+ hooked_call = HookedCall.new(self, args)
77
+
78
+ begin
79
+ begin
80
+ sqreen_timer.start if budget
81
+ Thread.current[:sqreen_hook_entered] = true
82
+
83
+ # TODO: make Call have #ball to throw by cb
84
+ # TODO: can Call be the ball? r = catch(Call.new, &c)
85
+ # TODO: is catch return value a Call? a #dispatch?
86
+ # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
87
+ # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
88
+ # TODO: HookCall x CallbackCollection#each_with_call x Flow
89
+ # TODO: TimedHookCall TimedCallbackCall
90
+ # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
91
+
92
+ request_elapsed = request_timer.elapsed if budget
93
+
94
+ hook.before.each do |c|
95
+ next if c.ignore && c.ignore.call
96
+
97
+ if budget && !c.mandatory
98
+ sqreen_elapsed = sqreen_timer.elapsed
99
+ if budget_ratio && !request[:time_budget_expended]
100
+ fixed_budget = budget_threshold
101
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
102
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
103
+ else
104
+ remaining = budget_threshold - sqreen_elapsed
105
+ end
106
+ unless remaining > 0
107
+ request[:skipped_callbacks] << c
108
+ request[:time_budget_expended] = true
109
+ next
110
+ end
111
+ end
112
+
113
+ flow = catch(Ball.new) do |ball|
114
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
115
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
116
+ end
117
+ end
118
+
119
+ next unless c.flow && flow.is_a?(Flow)
120
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
121
+ hooked_call.args_pass = flow.args and hooked_call.args_passing = true if flow.args?
122
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
123
+ break if flow.break?
124
+ end unless hook.disabled?
125
+ rescue StandardError => e
126
+ Sqreen::Weave.logger.debug { "exception:#{e.class} when:before message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
127
+ Sqreen::RemoteException.record(e) if Sqreen.queue
128
+ end
129
+
130
+ raise hooked_call.raise if hooked_call.raising
131
+ return hooked_call.return if hooked_call.returning
132
+ ensure
133
+ Thread.current[:sqreen_hook_entered] = false
134
+ sqreen_timer.stop if budget
135
+ end unless hook.before.empty?
136
+
137
+ begin
138
+ chrono.ignore do
139
+ if hook_point_super
140
+ hooked_call.returned = super(*(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
141
+ else
142
+ hooked_call.returned = hook.point.apply(hooked_call.instance, 'sqreen_hook', *(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
143
+ end
144
+ end
145
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
146
+ begin
147
+ sqreen_timer.start if budget
148
+ Thread.current[:sqreen_hook_entered] = true
149
+ hooked_call.raised = e
150
+
151
+ logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" } if logger_debug
152
+
153
+ # TODO: early escape causes raise too early then caught by ensure which mistakenly reports legit errors to sqreen
154
+ # raise if hook.raised.empty?
155
+
156
+ request_elapsed = request_timer.elapsed if budget
157
+
158
+ hook.raised.each do |c|
159
+ next if c.ignore && c.ignore.call
160
+
161
+ if budget && !c.mandatory
162
+ sqreen_elapsed = sqreen_timer.elapsed
163
+ if budget_ratio && !request[:time_budget_expended]
164
+ fixed_budget = budget_threshold
165
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
166
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
167
+ else
168
+ remaining = budget_threshold - sqreen_elapsed
169
+ end
170
+ unless remaining > 0
171
+ request[:skipped_callbacks] << c
172
+ request[:time_budget_expended] = true
173
+ next
174
+ end
175
+ end
176
+
177
+ flow = catch(Ball.new) do |ball|
178
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
179
+ 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)
180
+ end
181
+ end
182
+
183
+ next unless c.flow && flow.is_a?(Flow)
184
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
185
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
186
+ hooked_call.retrying = true if flow.retry?
187
+ break if flow.break?
188
+ end unless hook.disabled?
189
+ rescue StandardError => e
190
+ Sqreen::Weave.logger.debug { "exception:#{e.class} when:raised message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
191
+ Sqreen::RemoteException.record(e) if Sqreen.queue
192
+ end
193
+
194
+ retry if hooked_call.retrying
195
+ raise hooked_call.raise if hooked_call.raising
196
+ return hooked_call.return if hooked_call.returning
197
+ raise
198
+ else
199
+ begin
200
+ sqreen_timer.start if budget
201
+ Thread.current[:sqreen_hook_entered] = true
202
+
203
+ # TODO: hooked_call.returning should be always false here?
204
+ return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.after.empty?
205
+
206
+ request_elapsed = request_timer.elapsed if budget
207
+
208
+ hook.after.each do |c|
209
+ next if c.ignore && c.ignore.call
210
+
211
+ if budget && !c.mandatory
212
+ sqreen_elapsed = sqreen_timer.elapsed
213
+ if budget_ratio && !request[:time_budget_expended]
214
+ fixed_budget = budget_threshold
215
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
216
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
217
+ else
218
+ remaining = budget_threshold - sqreen_elapsed
219
+ end
220
+ unless remaining > 0
221
+ request[:skipped_callbacks] << c
222
+ request[:time_budget_expended] = true
223
+ next
224
+ end
225
+ end
226
+
227
+ flow = catch(Ball.new) do |ball|
228
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
229
+ 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)
230
+ end
231
+ end
232
+
233
+ next unless c.flow && flow.is_a?(Flow)
234
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
235
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
236
+ break if flow.break?
237
+ end unless hook.disabled?
238
+ rescue StandardError => e
239
+ Sqreen::Weave.logger.debug { "exception:#{e.class} when:after message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
240
+ Sqreen::RemoteException.record(e) if Sqreen.queue
241
+ end
242
+
243
+ raise hooked_call.raise if hooked_call.raising
244
+ return hooked_call.returning ? hooked_call.return : hooked_call.returned
245
+ ensure
246
+ begin
247
+ # TODO: sqreen_timer.start if someone has thrown?
248
+ # TODO: sqreen_timer.stop at end of rescue+else?
249
+ # TODO: Thread.current[:sqreen_hook_entered] = true if neither rescue nor else ie thrown?
250
+ # TODO: Thread.current[:sqreen_hook_entered] = false at end of rescue+else? (risky?)
251
+
252
+ # TODO: uniform early bail out? (but don't forget sqreen_hook_entered and sqreen_timer)
253
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.ensured.empty?
254
+
255
+ # done at either rescue or else
256
+ # request_elapsed = request_timer.elapsed if budget # && !hook.ensured.empty?
257
+
258
+ hook.ensured.each do |c|
259
+ next if c.ignore && c.ignore.call
260
+
261
+ if budget && !c.mandatory
262
+ sqreen_elapsed = sqreen_timer.elapsed
263
+ if budget_ratio && !request[:time_budget_expended]
264
+ fixed_budget = budget_threshold
265
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
266
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
267
+ else
268
+ remaining = budget_threshold - sqreen_elapsed
269
+ end
270
+ unless remaining > 0
271
+ request[:skipped_callbacks] << c
272
+ request[:time_budget_expended] = true
273
+ next
274
+ end
275
+ end
276
+
277
+ flow = catch(Ball.new) do |ball|
278
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
279
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
280
+ end
281
+ end
282
+
283
+ next unless c.flow && flow.is_a?(Flow)
284
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
285
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
286
+ break if flow.break?
287
+ end unless hook.ensured.empty? || hook.disabled?
288
+
289
+ Thread.current[:sqreen_hook_entered] = false
290
+ sqreen_timer.stop if budget
291
+ rescue StandardError => e
292
+ Sqreen::Weave.logger.debug { "exception:#{e.class} when:ensured message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
293
+ Sqreen::RemoteException.record(e) if Sqreen.queue
294
+ end
295
+
296
+ # TODO: should we run the following?
297
+ # raise hooked_call.raise if hooked_call.raising
298
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned
299
+ end
300
+ end # chrono
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end
@@ -0,0 +1,305 @@
1
+ # typed: ignore
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ require 'sqreen/graft/call'
7
+
8
+ module Sqreen
9
+ module Graft
10
+ class Hook
11
+ def self.wrapper(hook)
12
+ timed_hooks_proc = proc do |t|
13
+ if (request = Thread.current[:sqreen_http_request])
14
+ request[:timed_hooks] << t if request[:timed_level] >= 1
15
+ end
16
+ end
17
+ timed_callbacks_proc = proc do |t|
18
+ if (request = Thread.current[:sqreen_http_request])
19
+ request[:timed_callbacks] << t if request[:timed_level] >= 1
20
+ end
21
+ end
22
+
23
+ Proc.new do |*args, **kwargs, &block|
24
+ request = Thread.current[:sqreen_http_request]
25
+
26
+ if Thread.current[:sqreen_hook_entered]
27
+ if hook.point.super?
28
+ return super(*args, **kwargs, &block)
29
+ else
30
+ return hook.point.apply(self, 'sqreen_hook', *args, **kwargs, &block)
31
+ end
32
+ end
33
+
34
+ if request && request[:time_budget_expended] && (hook.before + hook.after + hook.raised + hook.ensured).none?(&:mandatory)
35
+ if request[:timed_level] >= 2
36
+ begin
37
+ request[:skipped_callbacks].concat(hook.before)
38
+
39
+ if hook.point.super?
40
+ return super(*args, **kwargs, &block)
41
+ else
42
+ return hook.point.apply(self, 'sqreen_hook', *args, **kwargs, &block)
43
+ end
44
+ rescue ::Exception # rubocop:disable Lint/RescueException
45
+ request[:skipped_callbacks].concat(hook.raised)
46
+ raise
47
+ else
48
+ request[:skipped_callbacks].concat(hook.after)
49
+ ensure
50
+ request[:skipped_callbacks].concat(hook.ensured)
51
+ end
52
+ else
53
+ if hook.point.super? # rubocop:disable Style/IfInsideElse
54
+ return super(*args, **kwargs, &block)
55
+ else
56
+ return hook.point.apply(self, 'sqreen_hook', *args, **kwargs, &block)
57
+ end
58
+ end
59
+ end
60
+
61
+ hook_point_super = hook.point.super?
62
+ logger = Sqreen::Graft.logger
63
+ logger_debug = Sqreen::Graft.logger.debug?
64
+
65
+ Timer.new(hook.point, &timed_hooks_proc).measure do |chrono|
66
+ # budget implies request
67
+ # TODO: make budget depend on a generic context (currently "request")
68
+ budget = request[:time_budget] if request
69
+ if budget
70
+ budget_threshold = request[:time_budget_threshold]
71
+ budget_ratio = request[:time_budget_ratio]
72
+ sqreen_timer = request[:sqreen_timer]
73
+ request_timer = request[:request_timer]
74
+ end
75
+
76
+ hooked_call = HookedCall.new(self, args)
77
+
78
+ begin
79
+ begin
80
+ sqreen_timer.start if budget
81
+ Thread.current[:sqreen_hook_entered] = true
82
+
83
+ # TODO: make Call have #ball to throw by cb
84
+ # TODO: can Call be the ball? r = catch(Call.new, &c)
85
+ # TODO: is catch return value a Call? a #dispatch?
86
+ # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
87
+ # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
88
+ # TODO: HookCall x CallbackCollection#each_with_call x Flow
89
+ # TODO: TimedHookCall TimedCallbackCall
90
+ # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
91
+
92
+ request_elapsed = request_timer.elapsed if budget
93
+
94
+ hook.before.each do |c|
95
+ next if c.ignore && c.ignore.call
96
+
97
+ if budget && !c.mandatory
98
+ sqreen_elapsed = sqreen_timer.elapsed
99
+ if budget_ratio && !request[:time_budget_expended]
100
+ fixed_budget = budget_threshold
101
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
102
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
103
+ else
104
+ remaining = budget_threshold - sqreen_elapsed
105
+ end
106
+ unless remaining > 0
107
+ request[:skipped_callbacks] << c
108
+ request[:time_budget_expended] = true
109
+ next
110
+ end
111
+ end
112
+
113
+ flow = catch(Ball.new) do |ball|
114
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
115
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
116
+ end
117
+ end
118
+
119
+ next unless c.flow && flow.is_a?(Flow)
120
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
121
+ hooked_call.args_pass = flow.args and hooked_call.args_passing = true if flow.args?
122
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
123
+ break if flow.break?
124
+ end unless hook.disabled?
125
+ rescue StandardError => e
126
+ Sqreen::Weave.logger.debug { "exception:#{e.class} when:before message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
127
+ Sqreen::RemoteException.record(e) if Sqreen.queue
128
+ end
129
+
130
+ raise hooked_call.raise if hooked_call.raising
131
+ return hooked_call.return if hooked_call.returning
132
+ ensure
133
+ Thread.current[:sqreen_hook_entered] = false
134
+ sqreen_timer.stop if budget
135
+ end unless hook.before.empty?
136
+
137
+ begin
138
+ chrono.ignore do
139
+ if hook_point_super
140
+ hooked_call.returned = super(*(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), **kwargs, &block)
141
+ else
142
+ hooked_call.returned = hook.point.apply(hooked_call.instance, 'sqreen_hook', *(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), **kwargs, &block)
143
+ end
144
+ end
145
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
146
+ begin
147
+ sqreen_timer.start if budget
148
+ Thread.current[:sqreen_hook_entered] = true
149
+ hooked_call.raised = e
150
+
151
+ logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" } if logger_debug
152
+
153
+ # TODO: early escape causes raise too early then caught by ensure which mistakenly reports legit errors to sqreen
154
+ # raise if hook.raised.empty?
155
+
156
+ request_elapsed = request_timer.elapsed if budget
157
+
158
+ hook.raised.each do |c|
159
+ next if c.ignore && c.ignore.call
160
+
161
+ if budget && !c.mandatory
162
+ sqreen_elapsed = sqreen_timer.elapsed
163
+ if budget_ratio && !request[:time_budget_expended]
164
+ fixed_budget = budget_threshold
165
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
166
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
167
+ else
168
+ remaining = budget_threshold - sqreen_elapsed
169
+ end
170
+ unless remaining > 0
171
+ request[:skipped_callbacks] << c
172
+ request[:time_budget_expended] = true
173
+ next
174
+ end
175
+ end
176
+
177
+ flow = catch(Ball.new) do |ball|
178
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
179
+ 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)
180
+ end
181
+ end
182
+
183
+ next unless c.flow && flow.is_a?(Flow)
184
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
185
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
186
+ hooked_call.retrying = true if flow.retry?
187
+ break if flow.break?
188
+ end unless hook.disabled?
189
+ rescue StandardError => e
190
+ Sqreen::Weave.logger.debug { "exception:#{e.class} when:raised message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
191
+ Sqreen::RemoteException.record(e) if Sqreen.queue
192
+ end
193
+
194
+ retry if hooked_call.retrying
195
+ raise hooked_call.raise if hooked_call.raising
196
+ return hooked_call.return if hooked_call.returning
197
+ raise
198
+ else
199
+ begin
200
+ sqreen_timer.start if budget
201
+ Thread.current[:sqreen_hook_entered] = true
202
+
203
+ # TODO: hooked_call.returning should be always false here?
204
+ return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.after.empty?
205
+
206
+ request_elapsed = request_timer.elapsed if budget
207
+
208
+ hook.after.each do |c|
209
+ next if c.ignore && c.ignore.call
210
+
211
+ if budget && !c.mandatory
212
+ sqreen_elapsed = sqreen_timer.elapsed
213
+ if budget_ratio && !request[:time_budget_expended]
214
+ fixed_budget = budget_threshold
215
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
216
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
217
+ else
218
+ remaining = budget_threshold - sqreen_elapsed
219
+ end
220
+ unless remaining > 0
221
+ request[:skipped_callbacks] << c
222
+ request[:time_budget_expended] = true
223
+ next
224
+ end
225
+ end
226
+
227
+ flow = catch(Ball.new) do |ball|
228
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
229
+ 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)
230
+ end
231
+ end
232
+
233
+ next unless c.flow && flow.is_a?(Flow)
234
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
235
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
236
+ break if flow.break?
237
+ end unless hook.disabled?
238
+ rescue StandardError => e
239
+ Sqreen::Weave.logger.debug { "exception:#{e.class} when:after message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
240
+ Sqreen::RemoteException.record(e) if Sqreen.queue
241
+ end
242
+
243
+ raise hooked_call.raise if hooked_call.raising
244
+ return hooked_call.returning ? hooked_call.return : hooked_call.returned
245
+ ensure
246
+ begin
247
+ # TODO: sqreen_timer.start if someone has thrown?
248
+ # TODO: sqreen_timer.stop at end of rescue+else?
249
+ # TODO: Thread.current[:sqreen_hook_entered] = true if neither rescue nor else ie thrown?
250
+ # TODO: Thread.current[:sqreen_hook_entered] = false at end of rescue+else? (risky?)
251
+
252
+ # TODO: uniform early bail out? (but don't forget sqreen_hook_entered and sqreen_timer)
253
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.ensured.empty?
254
+
255
+ # done at either rescue or else
256
+ # request_elapsed = request_timer.elapsed if budget # && !hook.ensured.empty?
257
+
258
+ hook.ensured.each do |c|
259
+ next if c.ignore && c.ignore.call
260
+
261
+ if budget && !c.mandatory
262
+ sqreen_elapsed = sqreen_timer.elapsed
263
+ if budget_ratio && !request[:time_budget_expended]
264
+ fixed_budget = budget_threshold
265
+ proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
266
+ remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
267
+ else
268
+ remaining = budget_threshold - sqreen_elapsed
269
+ end
270
+ unless remaining > 0
271
+ request[:skipped_callbacks] << c
272
+ request[:time_budget_expended] = true
273
+ next
274
+ end
275
+ end
276
+
277
+ flow = catch(Ball.new) do |ball|
278
+ Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
279
+ c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
280
+ end
281
+ end
282
+
283
+ next unless c.flow && flow.is_a?(Flow)
284
+ hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
285
+ hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
286
+ break if flow.break?
287
+ end unless hook.ensured.empty? || hook.disabled?
288
+
289
+ Thread.current[:sqreen_hook_entered] = false
290
+ sqreen_timer.stop if budget
291
+ rescue StandardError => e
292
+ Sqreen::Weave.logger.debug { "exception:#{e.class} when:ensured message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
293
+ Sqreen::RemoteException.record(e) if Sqreen.queue
294
+ end
295
+
296
+ # TODO: should we run the following?
297
+ # raise hooked_call.raise if hooked_call.raising
298
+ # return hooked_call.returning ? hooked_call.return : hooked_call.returned
299
+ end
300
+ end # chrono
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end