brainzlab 0.1.2 → 0.1.4
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/LICENSE +6 -21
- data/README.md +16 -2
- data/lib/brainzlab/beacon/client.rb +38 -40
- data/lib/brainzlab/beacon/provisioner.rb +1 -1
- data/lib/brainzlab/beacon.rb +15 -15
- data/lib/brainzlab/configuration.rb +112 -90
- data/lib/brainzlab/context.rb +2 -3
- data/lib/brainzlab/cortex/client.rb +29 -31
- data/lib/brainzlab/cortex/provisioner.rb +1 -1
- data/lib/brainzlab/cortex.rb +7 -11
- data/lib/brainzlab/dendrite/client.rb +42 -44
- data/lib/brainzlab/dendrite/provisioner.rb +1 -1
- data/lib/brainzlab/dendrite.rb +4 -4
- data/lib/brainzlab/devtools/data/collector.rb +22 -22
- data/lib/brainzlab/devtools/middleware/asset_server.rb +14 -14
- data/lib/brainzlab/devtools/middleware/database_handler.rb +52 -55
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +19 -19
- data/lib/brainzlab/devtools/middleware/error_page.rb +45 -44
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +39 -35
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +13 -9
- data/lib/brainzlab/devtools.rb +11 -11
- data/lib/brainzlab/flux/buffer.rb +3 -3
- data/lib/brainzlab/flux/client.rb +14 -16
- data/lib/brainzlab/flux/provisioner.rb +13 -13
- data/lib/brainzlab/flux.rb +8 -8
- data/lib/brainzlab/instrumentation/action_cable.rb +351 -0
- data/lib/brainzlab/instrumentation/action_controller.rb +649 -0
- data/lib/brainzlab/instrumentation/action_dispatch.rb +259 -0
- data/lib/brainzlab/instrumentation/action_mailbox.rb +197 -0
- data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
- data/lib/brainzlab/instrumentation/action_view.rb +380 -0
- data/lib/brainzlab/instrumentation/active_job.rb +569 -0
- data/lib/brainzlab/instrumentation/active_record.rb +467 -36
- data/lib/brainzlab/instrumentation/active_storage.rb +541 -0
- data/lib/brainzlab/instrumentation/active_support_cache.rb +700 -0
- data/lib/brainzlab/instrumentation/aws.rb +43 -39
- data/lib/brainzlab/instrumentation/dalli.rb +20 -20
- data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
- data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
- data/lib/brainzlab/instrumentation/excon.rb +27 -27
- data/lib/brainzlab/instrumentation/faraday.rb +3 -4
- data/lib/brainzlab/instrumentation/good_job.rb +28 -28
- data/lib/brainzlab/instrumentation/grape.rb +24 -24
- data/lib/brainzlab/instrumentation/graphql.rb +24 -23
- data/lib/brainzlab/instrumentation/httparty.rb +13 -14
- data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
- data/lib/brainzlab/instrumentation/net_http.rb +6 -6
- data/lib/brainzlab/instrumentation/rails_deprecation.rb +139 -0
- data/lib/brainzlab/instrumentation/railties.rb +134 -0
- data/lib/brainzlab/instrumentation/redis.rb +14 -21
- data/lib/brainzlab/instrumentation/resque.rb +23 -24
- data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
- data/lib/brainzlab/instrumentation/solid_queue.rb +37 -41
- data/lib/brainzlab/instrumentation/stripe.rb +36 -37
- data/lib/brainzlab/instrumentation/typhoeus.rb +19 -17
- data/lib/brainzlab/instrumentation.rb +111 -21
- data/lib/brainzlab/nerve/client.rb +38 -40
- data/lib/brainzlab/nerve/provisioner.rb +1 -1
- data/lib/brainzlab/nerve.rb +6 -6
- data/lib/brainzlab/pulse/client.rb +15 -11
- data/lib/brainzlab/pulse/instrumentation.rb +61 -57
- data/lib/brainzlab/pulse/propagation.rb +28 -28
- data/lib/brainzlab/pulse/provisioner.rb +12 -12
- data/lib/brainzlab/pulse/tracer.rb +3 -3
- data/lib/brainzlab/pulse.rb +13 -13
- data/lib/brainzlab/rails/log_formatter.rb +127 -121
- data/lib/brainzlab/rails/log_subscriber.rb +70 -76
- data/lib/brainzlab/rails/railtie.rb +66 -89
- data/lib/brainzlab/recall/buffer.rb +1 -1
- data/lib/brainzlab/recall/client.rb +14 -10
- data/lib/brainzlab/recall/logger.rb +16 -18
- data/lib/brainzlab/recall/provisioner.rb +16 -16
- data/lib/brainzlab/recall.rb +11 -13
- data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
- data/lib/brainzlab/reflex/client.rb +14 -10
- data/lib/brainzlab/reflex/provisioner.rb +12 -12
- data/lib/brainzlab/reflex.rb +29 -29
- data/lib/brainzlab/sentinel/client.rb +40 -42
- data/lib/brainzlab/sentinel/provisioner.rb +1 -1
- data/lib/brainzlab/sentinel.rb +5 -5
- data/lib/brainzlab/signal/client.rb +12 -14
- data/lib/brainzlab/signal/provisioner.rb +12 -12
- data/lib/brainzlab/signal.rb +7 -7
- data/lib/brainzlab/synapse/client.rb +42 -44
- data/lib/brainzlab/synapse/provisioner.rb +1 -1
- data/lib/brainzlab/synapse.rb +6 -6
- data/lib/brainzlab/utilities/circuit_breaker.rb +37 -41
- data/lib/brainzlab/utilities/health_check.rb +53 -55
- data/lib/brainzlab/utilities/log_formatter.rb +38 -40
- data/lib/brainzlab/utilities/rate_limiter.rb +5 -5
- data/lib/brainzlab/utilities.rb +4 -4
- data/lib/brainzlab/vault/cache.rb +1 -1
- data/lib/brainzlab/vault/client.rb +39 -41
- data/lib/brainzlab/vault/provisioner.rb +1 -1
- data/lib/brainzlab/vault.rb +19 -25
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +20 -20
- data/lib/brainzlab/vision/provisioner.rb +21 -21
- data/lib/brainzlab/vision.rb +17 -19
- data/lib/brainzlab-sdk.rb +1 -1
- data/lib/brainzlab.rb +22 -24
- data/lib/generators/brainzlab/install/install_generator.rb +29 -27
- metadata +11 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
class ActionDispatch
|
|
6
|
+
# Thresholds for slow operations (in milliseconds)
|
|
7
|
+
SLOW_MIDDLEWARE_THRESHOLD = 50
|
|
8
|
+
VERY_SLOW_MIDDLEWARE_THRESHOLD = 200
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def install!
|
|
12
|
+
return unless defined?(::ActionDispatch)
|
|
13
|
+
return if @installed
|
|
14
|
+
|
|
15
|
+
install_process_middleware_subscriber!
|
|
16
|
+
install_redirect_subscriber!
|
|
17
|
+
install_request_subscriber!
|
|
18
|
+
|
|
19
|
+
@installed = true
|
|
20
|
+
BrainzLab.debug_log('ActionDispatch instrumentation installed')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def installed?
|
|
24
|
+
@installed == true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# ============================================
|
|
30
|
+
# process_middleware.action_dispatch
|
|
31
|
+
# Fired when a middleware in the stack runs
|
|
32
|
+
# ============================================
|
|
33
|
+
def install_process_middleware_subscriber!
|
|
34
|
+
ActiveSupport::Notifications.subscribe('process_middleware.action_dispatch') do |*args|
|
|
35
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
36
|
+
handle_process_middleware(event)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def handle_process_middleware(event)
|
|
41
|
+
payload = event.payload
|
|
42
|
+
duration = event.duration.round(2)
|
|
43
|
+
|
|
44
|
+
middleware = payload[:middleware]
|
|
45
|
+
|
|
46
|
+
# Skip fast middleware to reduce noise
|
|
47
|
+
return if duration < 1
|
|
48
|
+
|
|
49
|
+
# Determine level based on duration
|
|
50
|
+
level = case duration
|
|
51
|
+
when 0...SLOW_MIDDLEWARE_THRESHOLD then :info
|
|
52
|
+
when SLOW_MIDDLEWARE_THRESHOLD...VERY_SLOW_MIDDLEWARE_THRESHOLD then :warning
|
|
53
|
+
else :error
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Record breadcrumb
|
|
57
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
58
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
59
|
+
"Middleware: #{middleware} (#{duration}ms)",
|
|
60
|
+
category: 'dispatch.middleware',
|
|
61
|
+
level: level,
|
|
62
|
+
data: {
|
|
63
|
+
middleware: middleware,
|
|
64
|
+
duration_ms: duration
|
|
65
|
+
}.compact
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Add Pulse span
|
|
70
|
+
record_middleware_span(event, middleware, duration)
|
|
71
|
+
|
|
72
|
+
# Log slow middleware
|
|
73
|
+
if duration >= SLOW_MIDDLEWARE_THRESHOLD && BrainzLab.configuration.recall_effectively_enabled?
|
|
74
|
+
log_level = duration >= VERY_SLOW_MIDDLEWARE_THRESHOLD ? :error : :warn
|
|
75
|
+
BrainzLab::Recall.send(
|
|
76
|
+
log_level,
|
|
77
|
+
"Slow middleware: #{middleware} (#{duration}ms)",
|
|
78
|
+
middleware: middleware,
|
|
79
|
+
duration_ms: duration
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
BrainzLab.debug_log("ActionDispatch process_middleware instrumentation failed: #{e.message}")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# ============================================
|
|
87
|
+
# redirect.action_dispatch
|
|
88
|
+
# Fired when a redirect response is sent
|
|
89
|
+
# ============================================
|
|
90
|
+
def install_redirect_subscriber!
|
|
91
|
+
ActiveSupport::Notifications.subscribe('redirect.action_dispatch') do |*args|
|
|
92
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
93
|
+
handle_redirect(event)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def handle_redirect(event)
|
|
98
|
+
payload = event.payload
|
|
99
|
+
duration = event.duration.round(2)
|
|
100
|
+
|
|
101
|
+
status = payload[:status]
|
|
102
|
+
location = payload[:location]
|
|
103
|
+
request = payload[:request]
|
|
104
|
+
|
|
105
|
+
# Record breadcrumb
|
|
106
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
107
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
108
|
+
"Redirect #{status}: #{truncate_url(location)}",
|
|
109
|
+
category: 'dispatch.redirect',
|
|
110
|
+
level: :info,
|
|
111
|
+
data: {
|
|
112
|
+
status: status,
|
|
113
|
+
location: truncate_url(location),
|
|
114
|
+
duration_ms: duration
|
|
115
|
+
}.compact
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Add Pulse span
|
|
120
|
+
record_redirect_span(event, status, location, duration)
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
BrainzLab.debug_log("ActionDispatch redirect instrumentation failed: #{e.message}")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# ============================================
|
|
126
|
+
# request.action_dispatch
|
|
127
|
+
# Fired for the full request lifecycle
|
|
128
|
+
# ============================================
|
|
129
|
+
def install_request_subscriber!
|
|
130
|
+
ActiveSupport::Notifications.subscribe('request.action_dispatch') do |*args|
|
|
131
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
132
|
+
handle_request(event)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def handle_request(event)
|
|
137
|
+
payload = event.payload
|
|
138
|
+
duration = event.duration.round(2)
|
|
139
|
+
|
|
140
|
+
request = payload[:request]
|
|
141
|
+
response = payload[:response]
|
|
142
|
+
|
|
143
|
+
method = request&.method
|
|
144
|
+
path = request&.path
|
|
145
|
+
status = response&.status
|
|
146
|
+
|
|
147
|
+
# Record breadcrumb
|
|
148
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
149
|
+
level = status && status >= 400 ? :warning : :info
|
|
150
|
+
level = :error if status && status >= 500
|
|
151
|
+
|
|
152
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
153
|
+
"Request: #{method} #{path} -> #{status} (#{duration}ms)",
|
|
154
|
+
category: 'dispatch.request',
|
|
155
|
+
level: level,
|
|
156
|
+
data: {
|
|
157
|
+
method: method,
|
|
158
|
+
path: path,
|
|
159
|
+
status: status,
|
|
160
|
+
duration_ms: duration
|
|
161
|
+
}.compact
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Add Pulse span
|
|
166
|
+
record_request_span(event, method, path, status, duration)
|
|
167
|
+
rescue StandardError => e
|
|
168
|
+
BrainzLab.debug_log("ActionDispatch request instrumentation failed: #{e.message}")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# ============================================
|
|
172
|
+
# Span Recording Helpers
|
|
173
|
+
# ============================================
|
|
174
|
+
def record_middleware_span(event, middleware, duration)
|
|
175
|
+
return unless BrainzLab.configuration.pulse_effectively_enabled?
|
|
176
|
+
|
|
177
|
+
tracer = BrainzLab::Pulse.tracer
|
|
178
|
+
return unless tracer.current_trace
|
|
179
|
+
|
|
180
|
+
span_data = {
|
|
181
|
+
span_id: SecureRandom.uuid,
|
|
182
|
+
name: "middleware.#{middleware.to_s.demodulize.underscore}",
|
|
183
|
+
kind: 'middleware',
|
|
184
|
+
started_at: event.time,
|
|
185
|
+
ended_at: event.end,
|
|
186
|
+
duration_ms: duration,
|
|
187
|
+
error: false,
|
|
188
|
+
data: {
|
|
189
|
+
'middleware.class' => middleware
|
|
190
|
+
}.compact
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
tracer.current_spans << span_data
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def record_redirect_span(event, status, location, duration)
|
|
197
|
+
return unless BrainzLab.configuration.pulse_effectively_enabled?
|
|
198
|
+
|
|
199
|
+
tracer = BrainzLab::Pulse.tracer
|
|
200
|
+
return unless tracer.current_trace
|
|
201
|
+
|
|
202
|
+
span_data = {
|
|
203
|
+
span_id: SecureRandom.uuid,
|
|
204
|
+
name: 'dispatch.redirect',
|
|
205
|
+
kind: 'http',
|
|
206
|
+
started_at: event.time,
|
|
207
|
+
ended_at: event.end,
|
|
208
|
+
duration_ms: duration,
|
|
209
|
+
error: false,
|
|
210
|
+
data: {
|
|
211
|
+
'http.status' => status,
|
|
212
|
+
'http.redirect_location' => truncate_url(location)
|
|
213
|
+
}.compact
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
tracer.current_spans << span_data
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def record_request_span(event, method, path, status, duration)
|
|
220
|
+
return unless BrainzLab.configuration.pulse_effectively_enabled?
|
|
221
|
+
|
|
222
|
+
tracer = BrainzLab::Pulse.tracer
|
|
223
|
+
return unless tracer.current_trace
|
|
224
|
+
|
|
225
|
+
span_data = {
|
|
226
|
+
span_id: SecureRandom.uuid,
|
|
227
|
+
name: 'dispatch.request',
|
|
228
|
+
kind: 'http',
|
|
229
|
+
started_at: event.time,
|
|
230
|
+
ended_at: event.end,
|
|
231
|
+
duration_ms: duration,
|
|
232
|
+
error: status && status >= 500,
|
|
233
|
+
data: {
|
|
234
|
+
'http.method' => method,
|
|
235
|
+
'http.path' => path,
|
|
236
|
+
'http.status' => status
|
|
237
|
+
}.compact
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
tracer.current_spans << span_data
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# ============================================
|
|
244
|
+
# Helper Methods
|
|
245
|
+
# ============================================
|
|
246
|
+
def truncate_url(url, max_length = 200)
|
|
247
|
+
return 'unknown' unless url
|
|
248
|
+
|
|
249
|
+
url_str = url.to_s
|
|
250
|
+
if url_str.length > max_length
|
|
251
|
+
"#{url_str[0, max_length - 3]}..."
|
|
252
|
+
else
|
|
253
|
+
url_str
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BrainzLab
|
|
4
|
+
module Instrumentation
|
|
5
|
+
class ActionMailbox
|
|
6
|
+
# Thresholds for slow processing (in milliseconds)
|
|
7
|
+
SLOW_PROCESSING_THRESHOLD = 1000 # 1 second
|
|
8
|
+
VERY_SLOW_PROCESSING_THRESHOLD = 5000 # 5 seconds
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def install!
|
|
12
|
+
return unless defined?(::ActionMailbox)
|
|
13
|
+
return if @installed
|
|
14
|
+
|
|
15
|
+
install_process_subscriber!
|
|
16
|
+
|
|
17
|
+
@installed = true
|
|
18
|
+
BrainzLab.debug_log('ActionMailbox instrumentation installed')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def installed?
|
|
22
|
+
@installed == true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# ============================================
|
|
28
|
+
# process.action_mailbox
|
|
29
|
+
# Fired when an inbound email is processed
|
|
30
|
+
# ============================================
|
|
31
|
+
def install_process_subscriber!
|
|
32
|
+
ActiveSupport::Notifications.subscribe('process.action_mailbox') do |*args|
|
|
33
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
34
|
+
handle_process(event)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def handle_process(event)
|
|
39
|
+
payload = event.payload
|
|
40
|
+
duration = event.duration.round(2)
|
|
41
|
+
|
|
42
|
+
mailbox = payload[:mailbox]
|
|
43
|
+
inbound_email = payload[:inbound_email]
|
|
44
|
+
|
|
45
|
+
mailbox_class = mailbox.is_a?(Class) ? mailbox.name : mailbox.class.name
|
|
46
|
+
email_id = inbound_email&.id
|
|
47
|
+
email_status = inbound_email&.status
|
|
48
|
+
message_id = inbound_email&.message_id
|
|
49
|
+
|
|
50
|
+
# Extract sender/recipient info if available
|
|
51
|
+
from = extract_from(inbound_email)
|
|
52
|
+
to = extract_to(inbound_email)
|
|
53
|
+
subject = extract_subject(inbound_email)
|
|
54
|
+
|
|
55
|
+
# Determine level based on duration
|
|
56
|
+
level = case duration
|
|
57
|
+
when 0...SLOW_PROCESSING_THRESHOLD then :info
|
|
58
|
+
when SLOW_PROCESSING_THRESHOLD...VERY_SLOW_PROCESSING_THRESHOLD then :warning
|
|
59
|
+
else :error
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Record breadcrumb
|
|
63
|
+
if BrainzLab.configuration.reflex_effectively_enabled?
|
|
64
|
+
BrainzLab::Reflex.add_breadcrumb(
|
|
65
|
+
"Mailbox process: #{mailbox_class} (#{duration}ms)",
|
|
66
|
+
category: 'mailbox.process',
|
|
67
|
+
level: level,
|
|
68
|
+
data: {
|
|
69
|
+
mailbox: mailbox_class,
|
|
70
|
+
email_id: email_id,
|
|
71
|
+
status: email_status,
|
|
72
|
+
message_id: truncate(message_id),
|
|
73
|
+
from: truncate(from),
|
|
74
|
+
subject: truncate(subject, 100),
|
|
75
|
+
duration_ms: duration
|
|
76
|
+
}.compact
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Add Pulse span
|
|
81
|
+
record_process_span(event, mailbox_class, email_id, duration, email_status, from, to, subject)
|
|
82
|
+
|
|
83
|
+
# Log to Recall
|
|
84
|
+
log_email_processing(mailbox_class, email_id, email_status, duration, from, to, subject)
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
BrainzLab.debug_log("ActionMailbox process instrumentation failed: #{e.message}")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# ============================================
|
|
90
|
+
# Span Recording
|
|
91
|
+
# ============================================
|
|
92
|
+
def record_process_span(event, mailbox_class, email_id, duration, status, from, to, subject)
|
|
93
|
+
return unless BrainzLab.configuration.pulse_effectively_enabled?
|
|
94
|
+
|
|
95
|
+
tracer = BrainzLab::Pulse.tracer
|
|
96
|
+
return unless tracer.current_trace
|
|
97
|
+
|
|
98
|
+
span_data = {
|
|
99
|
+
span_id: SecureRandom.uuid,
|
|
100
|
+
name: "mailbox.process.#{mailbox_class.underscore}",
|
|
101
|
+
kind: 'mailbox',
|
|
102
|
+
started_at: event.time,
|
|
103
|
+
ended_at: event.end,
|
|
104
|
+
duration_ms: duration,
|
|
105
|
+
error: status == 'bounced' || status == 'failed',
|
|
106
|
+
data: {
|
|
107
|
+
'mailbox.class' => mailbox_class,
|
|
108
|
+
'mailbox.email_id' => email_id,
|
|
109
|
+
'mailbox.status' => status,
|
|
110
|
+
'mailbox.from' => truncate(from),
|
|
111
|
+
'mailbox.to' => truncate(to),
|
|
112
|
+
'mailbox.subject' => truncate(subject, 100)
|
|
113
|
+
}.compact
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
tracer.current_spans << span_data
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# ============================================
|
|
120
|
+
# Logging
|
|
121
|
+
# ============================================
|
|
122
|
+
def log_email_processing(mailbox_class, email_id, status, duration, from, to, subject)
|
|
123
|
+
return unless BrainzLab.configuration.recall_effectively_enabled?
|
|
124
|
+
|
|
125
|
+
# Determine log level based on status and duration
|
|
126
|
+
if status == 'bounced' || status == 'failed'
|
|
127
|
+
BrainzLab::Recall.error(
|
|
128
|
+
"Mailbox processing failed: #{mailbox_class}",
|
|
129
|
+
mailbox: mailbox_class,
|
|
130
|
+
email_id: email_id,
|
|
131
|
+
status: status,
|
|
132
|
+
from: from,
|
|
133
|
+
to: to,
|
|
134
|
+
subject: truncate(subject, 200),
|
|
135
|
+
duration_ms: duration
|
|
136
|
+
)
|
|
137
|
+
elsif duration >= SLOW_PROCESSING_THRESHOLD
|
|
138
|
+
level = duration >= VERY_SLOW_PROCESSING_THRESHOLD ? :error : :warn
|
|
139
|
+
BrainzLab::Recall.send(
|
|
140
|
+
level,
|
|
141
|
+
"Slow mailbox processing: #{mailbox_class} (#{duration}ms)",
|
|
142
|
+
mailbox: mailbox_class,
|
|
143
|
+
email_id: email_id,
|
|
144
|
+
status: status,
|
|
145
|
+
duration_ms: duration,
|
|
146
|
+
threshold_exceeded: duration >= VERY_SLOW_PROCESSING_THRESHOLD ? 'critical' : 'warning'
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# ============================================
|
|
152
|
+
# Helper Methods
|
|
153
|
+
# ============================================
|
|
154
|
+
def extract_from(inbound_email)
|
|
155
|
+
return nil unless inbound_email
|
|
156
|
+
|
|
157
|
+
if inbound_email.respond_to?(:mail) && inbound_email.mail.respond_to?(:from)
|
|
158
|
+
Array(inbound_email.mail.from).first
|
|
159
|
+
end
|
|
160
|
+
rescue StandardError
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def extract_to(inbound_email)
|
|
165
|
+
return nil unless inbound_email
|
|
166
|
+
|
|
167
|
+
if inbound_email.respond_to?(:mail) && inbound_email.mail.respond_to?(:to)
|
|
168
|
+
Array(inbound_email.mail.to).first
|
|
169
|
+
end
|
|
170
|
+
rescue StandardError
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def extract_subject(inbound_email)
|
|
175
|
+
return nil unless inbound_email
|
|
176
|
+
|
|
177
|
+
if inbound_email.respond_to?(:mail) && inbound_email.mail.respond_to?(:subject)
|
|
178
|
+
inbound_email.mail.subject
|
|
179
|
+
end
|
|
180
|
+
rescue StandardError
|
|
181
|
+
nil
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def truncate(value, max_length = 200)
|
|
185
|
+
return nil unless value
|
|
186
|
+
|
|
187
|
+
str = value.to_s
|
|
188
|
+
if str.length > max_length
|
|
189
|
+
"#{str[0, max_length - 3]}..."
|
|
190
|
+
else
|
|
191
|
+
str
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -11,19 +11,19 @@ module BrainzLab
|
|
|
11
11
|
return if @installed
|
|
12
12
|
|
|
13
13
|
# Subscribe to deliver notification
|
|
14
|
-
ActiveSupport::Notifications.subscribe(
|
|
14
|
+
ActiveSupport::Notifications.subscribe('deliver.action_mailer') do |*args|
|
|
15
15
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
16
16
|
record_delivery(event)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# Subscribe to process notification (when mail is being prepared)
|
|
20
|
-
ActiveSupport::Notifications.subscribe(
|
|
20
|
+
ActiveSupport::Notifications.subscribe('process.action_mailer') do |*args|
|
|
21
21
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
22
22
|
record_process(event)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
@installed = true
|
|
26
|
-
BrainzLab.debug_log(
|
|
26
|
+
BrainzLab.debug_log('ActionMailer instrumentation installed')
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def installed?
|
|
@@ -46,13 +46,13 @@ module BrainzLab
|
|
|
46
46
|
mail = payload[:mail]
|
|
47
47
|
to = sanitize_recipients(mail&.to)
|
|
48
48
|
subject = mail&.subject
|
|
49
|
-
delivery_method = payload[:perform_deliveries] ?
|
|
49
|
+
delivery_method = payload[:perform_deliveries] ? 'delivered' : 'skipped'
|
|
50
50
|
|
|
51
51
|
# Add breadcrumb for Reflex
|
|
52
52
|
if BrainzLab.configuration.reflex_enabled
|
|
53
53
|
BrainzLab::Reflex.add_breadcrumb(
|
|
54
54
|
"Mail #{delivery_method}: #{mailer}",
|
|
55
|
-
category:
|
|
55
|
+
category: 'mailer.deliver',
|
|
56
56
|
level: :info,
|
|
57
57
|
data: {
|
|
58
58
|
mailer: mailer,
|
|
@@ -67,13 +67,13 @@ module BrainzLab
|
|
|
67
67
|
# Record span for Pulse
|
|
68
68
|
record_span(
|
|
69
69
|
name: "Mail deliver #{mailer}",
|
|
70
|
-
kind:
|
|
70
|
+
kind: 'mailer',
|
|
71
71
|
started_at: event.time,
|
|
72
72
|
ended_at: event.end,
|
|
73
73
|
duration_ms: duration_ms,
|
|
74
74
|
data: {
|
|
75
75
|
mailer: mailer,
|
|
76
|
-
action:
|
|
76
|
+
action: 'deliver',
|
|
77
77
|
to: to,
|
|
78
78
|
subject: truncate_subject(subject),
|
|
79
79
|
message_id: message_id,
|
|
@@ -106,7 +106,7 @@ module BrainzLab
|
|
|
106
106
|
if BrainzLab.configuration.reflex_enabled
|
|
107
107
|
BrainzLab::Reflex.add_breadcrumb(
|
|
108
108
|
"Mail process: #{mailer}##{action}",
|
|
109
|
-
category:
|
|
109
|
+
category: 'mailer.process',
|
|
110
110
|
level: :info,
|
|
111
111
|
data: {
|
|
112
112
|
mailer: mailer,
|
|
@@ -119,7 +119,7 @@ module BrainzLab
|
|
|
119
119
|
# Record span for Pulse
|
|
120
120
|
record_span(
|
|
121
121
|
name: "Mail process #{mailer}##{action}",
|
|
122
|
-
kind:
|
|
122
|
+
kind: 'mailer',
|
|
123
123
|
started_at: event.time,
|
|
124
124
|
ended_at: event.end,
|
|
125
125
|
duration_ms: duration_ms,
|
|
@@ -152,27 +152,28 @@ module BrainzLab
|
|
|
152
152
|
|
|
153
153
|
case recipients
|
|
154
154
|
when Array
|
|
155
|
-
recipients.map { |r| mask_email(r) }.join(
|
|
155
|
+
recipients.map { |r| mask_email(r) }.join(', ')
|
|
156
156
|
else
|
|
157
157
|
mask_email(recipients.to_s)
|
|
158
158
|
end
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
def mask_email(email)
|
|
162
|
-
return email unless email.include?(
|
|
162
|
+
return email unless email.include?('@')
|
|
163
163
|
|
|
164
|
-
local, domain = email.split(
|
|
164
|
+
local, domain = email.split('@', 2)
|
|
165
165
|
if local.length > 2
|
|
166
166
|
"#{local[0..1]}***@#{domain}"
|
|
167
167
|
else
|
|
168
168
|
"***@#{domain}"
|
|
169
169
|
end
|
|
170
170
|
rescue StandardError
|
|
171
|
-
|
|
171
|
+
'[email]'
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
def truncate_subject(subject)
|
|
175
175
|
return nil unless subject
|
|
176
|
+
|
|
176
177
|
subject.to_s[0, 100]
|
|
177
178
|
end
|
|
178
179
|
end
|