fiber_audit 0.1.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/.fiber-audit.example.yml +19 -0
- data/ARCHITECTURE.md +726 -0
- data/CHANGELOG.md +41 -0
- data/LICENSE +201 -0
- data/README.md +66 -8
- data/lib/fiber_audit/cli.rb +87 -2
- data/lib/fiber_audit/configuration.rb +106 -6
- data/lib/fiber_audit/errors.rb +2 -0
- data/lib/fiber_audit/operation_vocabulary.rb +42 -0
- data/lib/fiber_audit/reporters/text.rb +1 -1
- data/lib/fiber_audit/runtime/active_operations.rb +146 -0
- data/lib/fiber_audit/runtime/boot.rb +83 -0
- data/lib/fiber_audit/runtime/clock.rb +35 -0
- data/lib/fiber_audit/runtime/environment.rb +289 -0
- data/lib/fiber_audit/runtime/event.rb +86 -0
- data/lib/fiber_audit/runtime/execution_context.rb +89 -0
- data/lib/fiber_audit/runtime/heartbeat.rb +113 -0
- data/lib/fiber_audit/runtime/jsonl/schema.rb +312 -0
- data/lib/fiber_audit/runtime/jsonl/writer.rb +122 -0
- data/lib/fiber_audit/runtime/lifecycle.rb +343 -0
- data/lib/fiber_audit/runtime/limits.rb +102 -0
- data/lib/fiber_audit/runtime/location.rb +44 -0
- data/lib/fiber_audit/runtime/policy.rb +121 -0
- data/lib/fiber_audit/runtime/probes/base.rb +333 -0
- data/lib/fiber_audit/runtime/probes/http.rb +80 -0
- data/lib/fiber_audit/runtime/probes/io_select.rb +50 -0
- data/lib/fiber_audit/runtime/probes/registry.rb +156 -0
- data/lib/fiber_audit/runtime/probes/socket.rb +76 -0
- data/lib/fiber_audit/runtime/probes/subprocess.rb +83 -0
- data/lib/fiber_audit/runtime/probes/synchronization.rb +58 -0
- data/lib/fiber_audit/runtime/probes/thread_state.rb +39 -0
- data/lib/fiber_audit/runtime/probes/thread_wait.rb +25 -0
- data/lib/fiber_audit/runtime/rails_integration.rb +320 -0
- data/lib/fiber_audit/runtime/recorder.rb +333 -0
- data/lib/fiber_audit/runtime/redactor.rb +102 -0
- data/lib/fiber_audit/runtime/sampler.rb +26 -0
- data/lib/fiber_audit/runtime/scheduler_observer.rb +128 -0
- data/lib/fiber_audit/runtime/session.rb +112 -0
- data/lib/fiber_audit/runtime/supervisor.rb +114 -0
- data/lib/fiber_audit/runtime/validation.rb +68 -0
- data/lib/fiber_audit/runtime/watchdog.rb +479 -0
- data/lib/fiber_audit/runtime/watchdog_policy.rb +64 -0
- data/lib/fiber_audit/runtime.rb +37 -0
- data/lib/fiber_audit/static/rules/blocking_subprocess.rb +3 -8
- data/lib/fiber_audit/static/rules/direct_socket.rb +2 -3
- data/lib/fiber_audit/static/rules/io_select.rb +2 -4
- data/lib/fiber_audit/static/rules/net_http_in_request.rb +3 -5
- data/lib/fiber_audit/static/rules/synchronization.rb +2 -6
- data/lib/fiber_audit/static/rules/thread_current_state.rb +3 -2
- data/lib/fiber_audit/static/rules/thread_join.rb +3 -2
- data/lib/fiber_audit/version.rb +1 -1
- data/lib/fiber_audit.rb +1 -0
- metadata +38 -2
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FiberAudit
|
|
4
|
+
module Runtime
|
|
5
|
+
module Probes
|
|
6
|
+
module Subprocess
|
|
7
|
+
module KernelInstanceHook
|
|
8
|
+
def system(...)
|
|
9
|
+
Registry.observe(operation: 'Kernel.system') { super }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def exec(...)
|
|
13
|
+
Registry.observe(operation: 'Kernel.exec', emit_start: true) { super }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def spawn(...)
|
|
17
|
+
Registry.observe(operation: 'Kernel.spawn') { super }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private :system, :exec, :spawn
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module KernelSingletonHook
|
|
24
|
+
def system(...)
|
|
25
|
+
Registry.observe(operation: 'Kernel.system') { super }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def exec(...)
|
|
29
|
+
Registry.observe(operation: 'Kernel.exec', emit_start: true) { super }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def spawn(...)
|
|
33
|
+
Registry.observe(operation: 'Kernel.spawn') { super }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module IOHook
|
|
38
|
+
def popen(...)
|
|
39
|
+
Registry.observe(operation: 'IO.popen') { super }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module ProcessHook
|
|
44
|
+
def waitall(...)
|
|
45
|
+
Registry.observe(operation: 'Process.waitall') { super }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def detach(...)
|
|
49
|
+
Registry.observe(operation: 'Process.detach') { super }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module Open3Hook
|
|
54
|
+
def capture2(...)
|
|
55
|
+
Registry.observe(operation: 'Open3.capture2') { super }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def capture2e(...)
|
|
59
|
+
Registry.observe(operation: 'Open3.capture2e') { super }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def capture3(...)
|
|
63
|
+
Registry.observe(operation: 'Open3.capture3') { super }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def pipeline(...)
|
|
67
|
+
Registry.observe(operation: 'Open3.pipeline') { super }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module_function
|
|
72
|
+
|
|
73
|
+
def install!(registry)
|
|
74
|
+
registry.prepend_once(Kernel, KernelInstanceHook)
|
|
75
|
+
registry.prepend_once(Kernel.singleton_class, KernelSingletonHook)
|
|
76
|
+
registry.prepend_once(IO.singleton_class, IOHook)
|
|
77
|
+
registry.prepend_once(Process.singleton_class, ProcessHook)
|
|
78
|
+
registry.prepend_once(Open3.singleton_class, Open3Hook) if defined?(Open3)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FiberAudit
|
|
4
|
+
module Runtime
|
|
5
|
+
module Probes
|
|
6
|
+
module Synchronization
|
|
7
|
+
TRY_LOCK_MEASUREMENTS = lambda do |result|
|
|
8
|
+
acquired = result == true
|
|
9
|
+
{ acquired: acquired, contention_observed: !acquired }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module MutexHook
|
|
13
|
+
def lock(...)
|
|
14
|
+
Registry.observe(operation: 'Mutex#lock') { super }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def synchronize(...)
|
|
18
|
+
Registry.observe(operation: 'Mutex#synchronize') { super }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def try_lock(...)
|
|
22
|
+
Registry.observe(
|
|
23
|
+
operation: 'Mutex#try_lock',
|
|
24
|
+
measurement_builder: TRY_LOCK_MEASUREMENTS
|
|
25
|
+
) { super }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
module ConditionVariableHook
|
|
30
|
+
def wait(...)
|
|
31
|
+
Registry.observe(operation: 'ConditionVariable#wait') { super }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module MonitorHook
|
|
36
|
+
def synchronize(...)
|
|
37
|
+
Registry.observe(operation: 'Monitor#synchronize') { super }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
module MonitorMixinHook
|
|
42
|
+
def synchronize(...)
|
|
43
|
+
Registry.observe(operation: 'MonitorMixin#synchronize') { super }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module_function
|
|
48
|
+
|
|
49
|
+
def install!(registry)
|
|
50
|
+
registry.prepend_once(Mutex, MutexHook)
|
|
51
|
+
registry.prepend_once(ConditionVariable, ConditionVariableHook) if defined?(ConditionVariable)
|
|
52
|
+
registry.prepend_once(Monitor, MonitorHook) if defined?(Monitor)
|
|
53
|
+
registry.prepend_once(MonitorMixin, MonitorMixinHook) if defined?(MonitorMixin)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FiberAudit
|
|
4
|
+
module Runtime
|
|
5
|
+
module Probes
|
|
6
|
+
module ThreadState
|
|
7
|
+
module Hook
|
|
8
|
+
def thread_variable_get(...)
|
|
9
|
+
Registry.observe(operation: 'Thread.thread_variable_get') { super }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def thread_variable_set(...)
|
|
13
|
+
Registry.observe(operation: 'Thread.thread_variable_set') { super }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def [](...)
|
|
17
|
+
return super unless equal?(Thread.current)
|
|
18
|
+
|
|
19
|
+
Registry.observe(operation: 'Thread.current.[]') { super }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def []=(...)
|
|
23
|
+
if equal?(Thread.current)
|
|
24
|
+
Registry.observe(operation: 'Thread.current.[]=') { super }
|
|
25
|
+
else
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
def install!(registry)
|
|
34
|
+
registry.prepend_once(Thread, Hook)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FiberAudit
|
|
4
|
+
module Runtime
|
|
5
|
+
module Probes
|
|
6
|
+
module ThreadWait
|
|
7
|
+
module Hook
|
|
8
|
+
def join(...)
|
|
9
|
+
Registry.observe(operation: 'Thread.join') { super }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def value(...)
|
|
13
|
+
Registry.observe(operation: 'Thread.value') { super }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def install!(registry)
|
|
20
|
+
registry.prepend_once(Thread, Hook)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'execution_context'
|
|
4
|
+
|
|
5
|
+
module FiberAudit
|
|
6
|
+
module Runtime
|
|
7
|
+
# Process-local, idempotent Rails integration.
|
|
8
|
+
# Installs prepend hooks for Rails boundaries when detected.
|
|
9
|
+
# Becomes inert after deactivation or fork.
|
|
10
|
+
# Supports late-load rescanning for Rails components loaded after boot.
|
|
11
|
+
class RailsIntegration
|
|
12
|
+
attr_reader :owner_pid, :context_store, :recorder
|
|
13
|
+
|
|
14
|
+
def initialize(context_store:, recorder: nil)
|
|
15
|
+
@context_store = context_store
|
|
16
|
+
@recorder = recorder
|
|
17
|
+
@owner_pid = Process.pid
|
|
18
|
+
@active = true
|
|
19
|
+
@middleware_stack = nil
|
|
20
|
+
@require_hook_installed = false
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def active_for_current_process?
|
|
25
|
+
@active && @owner_pid == Process.pid
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def deactivate!
|
|
29
|
+
@active = false
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def install!
|
|
34
|
+
return unless active_for_current_process?
|
|
35
|
+
|
|
36
|
+
@mutex.synchronize do
|
|
37
|
+
install_require_hook
|
|
38
|
+
install_controller_hook
|
|
39
|
+
install_job_hook
|
|
40
|
+
install_cable_hook
|
|
41
|
+
try_install_middleware_hook
|
|
42
|
+
end
|
|
43
|
+
self
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
handle_installation_failure(e)
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def rescan!
|
|
50
|
+
return unless active_for_current_process?
|
|
51
|
+
|
|
52
|
+
@mutex.synchronize do
|
|
53
|
+
install_controller_hook
|
|
54
|
+
install_job_hook
|
|
55
|
+
install_cable_hook
|
|
56
|
+
try_install_middleware_hook
|
|
57
|
+
end
|
|
58
|
+
self
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
handle_installation_failure(e)
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class << self
|
|
65
|
+
def activate(context_store: ExecutionContext, recorder: nil)
|
|
66
|
+
integration = new(context_store: context_store, recorder: recorder)
|
|
67
|
+
integration.install!
|
|
68
|
+
@current = integration
|
|
69
|
+
@current
|
|
70
|
+
rescue StandardError
|
|
71
|
+
@current = nil
|
|
72
|
+
raise
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
attr_reader :current
|
|
76
|
+
|
|
77
|
+
def deactivate(integration = nil)
|
|
78
|
+
target = integration || @current
|
|
79
|
+
return unless target
|
|
80
|
+
|
|
81
|
+
target.deactivate!
|
|
82
|
+
@current = nil if @current.equal?(target)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def rescan!
|
|
86
|
+
@current&.rescan! if @current&.active_for_current_process?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def rescan_after_require!
|
|
90
|
+
key = :fiber_audit_rails_require_rescan
|
|
91
|
+
thread = Thread.current
|
|
92
|
+
acquired = false
|
|
93
|
+
return if thread.thread_variable_get(key)
|
|
94
|
+
|
|
95
|
+
thread.thread_variable_set(key, true)
|
|
96
|
+
acquired = true
|
|
97
|
+
rescan!
|
|
98
|
+
ensure
|
|
99
|
+
thread&.thread_variable_set(key, false) if acquired
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def handle_installation_failure(error)
|
|
106
|
+
unless error.instance_variable_defined?(:@fiber_audit_runtime_accounted)
|
|
107
|
+
recorder&.internal_error! unless recorder&.disabled?
|
|
108
|
+
error.instance_variable_set(:@fiber_audit_runtime_accounted, true)
|
|
109
|
+
end
|
|
110
|
+
raise error unless fail_open?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def fail_open?
|
|
114
|
+
return true unless recorder
|
|
115
|
+
|
|
116
|
+
recorder.session.policy.fail_open?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def account_internal_error
|
|
120
|
+
return unless recorder
|
|
121
|
+
|
|
122
|
+
recorder.internal_error! unless recorder.disabled?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def install_require_hook
|
|
126
|
+
return if @require_hook_installed
|
|
127
|
+
|
|
128
|
+
# Zeitwerk replaces Kernel#require. Load its optional integration before
|
|
129
|
+
# prepending FiberAudit so later Rails autoloads retain Zeitwerk's require
|
|
130
|
+
# semantics. This remains a no-op when Zeitwerk is not installed.
|
|
131
|
+
begin
|
|
132
|
+
require 'zeitwerk'
|
|
133
|
+
rescue LoadError
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Install narrow guarded require hook for late Rails loading
|
|
138
|
+
# Independent of probe registry
|
|
139
|
+
::Kernel.prepend(RequireHook) unless ::Kernel.ancestors.include?(RequireHook)
|
|
140
|
+
@require_hook_installed = true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def install_controller_hook
|
|
144
|
+
namespace = loaded_constant(Object, :ActionController)
|
|
145
|
+
target = loaded_constant(namespace, :Metal)
|
|
146
|
+
return unless target
|
|
147
|
+
|
|
148
|
+
target.prepend(ControllerHook) unless target.ancestors.include?(ControllerHook)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def install_job_hook
|
|
152
|
+
namespace = loaded_constant(Object, :ActiveJob)
|
|
153
|
+
target = loaded_constant(namespace, :Base)
|
|
154
|
+
return unless target
|
|
155
|
+
|
|
156
|
+
target.prepend(JobHook) unless target.ancestors.include?(JobHook)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def install_cable_hook
|
|
160
|
+
namespace = loaded_constant(Object, :ActionCable)
|
|
161
|
+
channel = loaded_constant(namespace, :Channel)
|
|
162
|
+
target = loaded_constant(channel, :Base)
|
|
163
|
+
return unless target
|
|
164
|
+
|
|
165
|
+
target.prepend(CableHook) unless target.ancestors.include?(CableHook)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def loaded_constant(namespace, name)
|
|
169
|
+
return unless namespace.is_a?(Module)
|
|
170
|
+
return if namespace.autoload?(name)
|
|
171
|
+
return unless namespace.const_defined?(name, false)
|
|
172
|
+
|
|
173
|
+
namespace.const_get(name, false)
|
|
174
|
+
rescue NameError
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def try_install_middleware_hook
|
|
179
|
+
rails = loaded_constant(Object, :Rails)
|
|
180
|
+
return unless rails
|
|
181
|
+
|
|
182
|
+
# Rails.application may instantiate the application class. Calling it
|
|
183
|
+
# from a require hook while Rails itself is loading can re-enter a
|
|
184
|
+
# partially initialized application, so only inspect an existing instance.
|
|
185
|
+
application = rails.instance_variable_get(:@application)
|
|
186
|
+
return unless application
|
|
187
|
+
|
|
188
|
+
stack = application.config.middleware
|
|
189
|
+
return if @middleware_stack.equal?(stack) || middleware_installed_in?(stack)
|
|
190
|
+
|
|
191
|
+
# Try to insert middleware into Rails stack. A replacement stack (for
|
|
192
|
+
# example after a Rails reload) is eligible for installation again.
|
|
193
|
+
begin
|
|
194
|
+
stack.insert_before(0, Middleware)
|
|
195
|
+
@middleware_stack = stack
|
|
196
|
+
rescue StandardError => e
|
|
197
|
+
# Stack may be finalized - that's okay, middleware is optional
|
|
198
|
+
handle_installation_failure(e)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def middleware_installed_in?(stack)
|
|
203
|
+
return false unless stack.respond_to?(:any?)
|
|
204
|
+
|
|
205
|
+
stack.any? do |entry|
|
|
206
|
+
entry.equal?(Middleware) || (entry.respond_to?(:klass) && entry.klass.equal?(Middleware))
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Require hook for late Rails loading - independent of probe registry
|
|
211
|
+
# Narrow, guarded, behavior-preserving
|
|
212
|
+
module RequireHook
|
|
213
|
+
def require(path)
|
|
214
|
+
result = super
|
|
215
|
+
# Only rescan after a successful require that loaded a feature.
|
|
216
|
+
RailsIntegration.rescan_after_require! if result
|
|
217
|
+
result
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private :require
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Prepend hooks - consult active integration before setting context
|
|
224
|
+
# Resilient to context store failures in fail-open mode
|
|
225
|
+
# Critical: must not catch application exceptions or invoke app twice
|
|
226
|
+
module ControllerHook
|
|
227
|
+
def process_action(...)
|
|
228
|
+
integration = RailsIntegration.current
|
|
229
|
+
return super unless integration&.active_for_current_process?
|
|
230
|
+
|
|
231
|
+
executed = false
|
|
232
|
+
begin
|
|
233
|
+
integration.context_store.with(:request) do
|
|
234
|
+
executed = true
|
|
235
|
+
super
|
|
236
|
+
end
|
|
237
|
+
rescue StandardError
|
|
238
|
+
raise if executed
|
|
239
|
+
# Context setup failed before application code ran
|
|
240
|
+
raise unless integration.send(:fail_open?)
|
|
241
|
+
|
|
242
|
+
integration.send(:account_internal_error)
|
|
243
|
+
super
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
module JobHook
|
|
249
|
+
def perform_now(...)
|
|
250
|
+
integration = RailsIntegration.current
|
|
251
|
+
return super unless integration&.active_for_current_process?
|
|
252
|
+
|
|
253
|
+
executed = false
|
|
254
|
+
begin
|
|
255
|
+
integration.context_store.with(:job) do
|
|
256
|
+
executed = true
|
|
257
|
+
super
|
|
258
|
+
end
|
|
259
|
+
rescue StandardError
|
|
260
|
+
raise if executed
|
|
261
|
+
# Context setup failed before application code ran
|
|
262
|
+
raise unless integration.send(:fail_open?)
|
|
263
|
+
|
|
264
|
+
integration.send(:account_internal_error)
|
|
265
|
+
super
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
module CableHook
|
|
271
|
+
def dispatch_action(...)
|
|
272
|
+
integration = RailsIntegration.current
|
|
273
|
+
return super unless integration&.active_for_current_process?
|
|
274
|
+
|
|
275
|
+
executed = false
|
|
276
|
+
begin
|
|
277
|
+
integration.context_store.with(:websocket) do
|
|
278
|
+
executed = true
|
|
279
|
+
super
|
|
280
|
+
end
|
|
281
|
+
rescue StandardError
|
|
282
|
+
raise if executed
|
|
283
|
+
# Context setup failed before application code ran
|
|
284
|
+
raise unless integration.send(:fail_open?)
|
|
285
|
+
|
|
286
|
+
integration.send(:account_internal_error)
|
|
287
|
+
super
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Rack middleware for :middleware context
|
|
293
|
+
class Middleware
|
|
294
|
+
def initialize(app)
|
|
295
|
+
@app = app
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def call(env)
|
|
299
|
+
integration = RailsIntegration.current
|
|
300
|
+
return @app.call(env) unless integration&.active_for_current_process?
|
|
301
|
+
|
|
302
|
+
executed = false
|
|
303
|
+
begin
|
|
304
|
+
integration.context_store.with(:middleware) do
|
|
305
|
+
executed = true
|
|
306
|
+
@app.call(env)
|
|
307
|
+
end
|
|
308
|
+
rescue StandardError
|
|
309
|
+
raise if executed
|
|
310
|
+
# Context setup failed before application code ran
|
|
311
|
+
raise unless integration.send(:fail_open?)
|
|
312
|
+
|
|
313
|
+
integration.send(:account_internal_error)
|
|
314
|
+
@app.call(env)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|