brainzlab 0.1.3 → 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.
@@ -0,0 +1,541 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ class ActiveStorage
6
+ # Thresholds for slow operations (in milliseconds)
7
+ SLOW_OPERATION_THRESHOLD = 500
8
+ VERY_SLOW_OPERATION_THRESHOLD = 2000
9
+
10
+ class << self
11
+ def install!
12
+ return unless defined?(::ActiveStorage)
13
+ return if @installed
14
+
15
+ # Core Active Storage events
16
+ install_preview_subscriber!
17
+ install_transform_subscriber!
18
+ install_analyze_subscriber!
19
+
20
+ # Storage service events
21
+ install_service_upload_subscriber!
22
+ install_service_download_subscriber!
23
+ install_service_streaming_download_subscriber!
24
+ install_service_delete_subscriber!
25
+ install_service_delete_prefixed_subscriber!
26
+ install_service_exist_subscriber!
27
+ install_service_url_subscriber!
28
+ install_service_download_chunk_subscriber!
29
+ install_service_update_metadata_subscriber!
30
+
31
+ @installed = true
32
+ BrainzLab.debug_log('ActiveStorage instrumentation installed')
33
+ end
34
+
35
+ def installed?
36
+ @installed == true
37
+ end
38
+
39
+ private
40
+
41
+ # ============================================
42
+ # Preview (generating previews for files)
43
+ # ============================================
44
+ def install_preview_subscriber!
45
+ ActiveSupport::Notifications.subscribe('preview.active_storage') do |*args|
46
+ event = ActiveSupport::Notifications::Event.new(*args)
47
+ handle_preview(event)
48
+ end
49
+ end
50
+
51
+ def handle_preview(event)
52
+ payload = event.payload
53
+ duration = event.duration.round(2)
54
+
55
+ key = payload[:key]
56
+
57
+ # Record breadcrumb
58
+ record_storage_breadcrumb('preview', key, duration)
59
+
60
+ # Add Pulse span
61
+ record_storage_span(event, 'preview', key, duration)
62
+
63
+ # Log slow operations
64
+ log_slow_operation('preview', key, duration) if duration >= SLOW_OPERATION_THRESHOLD
65
+ rescue StandardError => e
66
+ BrainzLab.debug_log("ActiveStorage preview instrumentation failed: #{e.message}")
67
+ end
68
+
69
+ # ============================================
70
+ # Transform (image transformations)
71
+ # ============================================
72
+ def install_transform_subscriber!
73
+ ActiveSupport::Notifications.subscribe('transform.active_storage') do |*args|
74
+ event = ActiveSupport::Notifications::Event.new(*args)
75
+ handle_transform(event)
76
+ end
77
+ end
78
+
79
+ def handle_transform(event)
80
+ payload = event.payload
81
+ duration = event.duration.round(2)
82
+
83
+ key = payload[:key]
84
+
85
+ # Record breadcrumb
86
+ record_storage_breadcrumb('transform', key, duration)
87
+
88
+ # Add Pulse span
89
+ record_storage_span(event, 'transform', key, duration)
90
+
91
+ # Log slow operations
92
+ log_slow_operation('transform', key, duration) if duration >= SLOW_OPERATION_THRESHOLD
93
+ rescue StandardError => e
94
+ BrainzLab.debug_log("ActiveStorage transform instrumentation failed: #{e.message}")
95
+ end
96
+
97
+ # ============================================
98
+ # Analyze (file analysis - dimensions, duration, etc.)
99
+ # ============================================
100
+ def install_analyze_subscriber!
101
+ ActiveSupport::Notifications.subscribe('analyze.active_storage') do |*args|
102
+ event = ActiveSupport::Notifications::Event.new(*args)
103
+ handle_analyze(event)
104
+ end
105
+ end
106
+
107
+ def handle_analyze(event)
108
+ payload = event.payload
109
+ duration = event.duration.round(2)
110
+
111
+ analyzer = payload[:analyzer]
112
+
113
+ # Record breadcrumb
114
+ if BrainzLab.configuration.reflex_effectively_enabled?
115
+ BrainzLab::Reflex.add_breadcrumb(
116
+ "Storage analyze: #{analyzer} (#{duration}ms)",
117
+ category: 'storage.analyze',
118
+ level: duration >= SLOW_OPERATION_THRESHOLD ? :warning : :info,
119
+ data: {
120
+ analyzer: analyzer,
121
+ duration_ms: duration
122
+ }.compact
123
+ )
124
+ end
125
+
126
+ # Add Pulse span
127
+ record_storage_span(event, 'analyze', analyzer, duration, analyzer: analyzer)
128
+ rescue StandardError => e
129
+ BrainzLab.debug_log("ActiveStorage analyze instrumentation failed: #{e.message}")
130
+ end
131
+
132
+ # ============================================
133
+ # Service Upload
134
+ # ============================================
135
+ def install_service_upload_subscriber!
136
+ ActiveSupport::Notifications.subscribe('service_upload.active_storage') do |*args|
137
+ event = ActiveSupport::Notifications::Event.new(*args)
138
+ handle_service_upload(event)
139
+ end
140
+ end
141
+
142
+ def handle_service_upload(event)
143
+ payload = event.payload
144
+ duration = event.duration.round(2)
145
+
146
+ key = payload[:key]
147
+ service = payload[:service]
148
+ checksum = payload[:checksum]
149
+
150
+ # Record breadcrumb
151
+ if BrainzLab.configuration.reflex_effectively_enabled?
152
+ level = case duration
153
+ when 0...SLOW_OPERATION_THRESHOLD then :info
154
+ when SLOW_OPERATION_THRESHOLD...VERY_SLOW_OPERATION_THRESHOLD then :warning
155
+ else :error
156
+ end
157
+
158
+ BrainzLab::Reflex.add_breadcrumb(
159
+ "Storage upload: #{truncate_key(key)} (#{duration}ms)",
160
+ category: 'storage.upload',
161
+ level: level,
162
+ data: {
163
+ key: truncate_key(key),
164
+ service: service,
165
+ duration_ms: duration
166
+ }.compact
167
+ )
168
+ end
169
+
170
+ # Add Pulse span
171
+ record_service_span(event, 'upload', key, duration, service: service)
172
+
173
+ # Log slow uploads
174
+ log_slow_operation('upload', key, duration, service: service) if duration >= SLOW_OPERATION_THRESHOLD
175
+ rescue StandardError => e
176
+ BrainzLab.debug_log("ActiveStorage service_upload instrumentation failed: #{e.message}")
177
+ end
178
+
179
+ # ============================================
180
+ # Service Download
181
+ # ============================================
182
+ def install_service_download_subscriber!
183
+ ActiveSupport::Notifications.subscribe('service_download.active_storage') do |*args|
184
+ event = ActiveSupport::Notifications::Event.new(*args)
185
+ handle_service_download(event)
186
+ end
187
+ end
188
+
189
+ def handle_service_download(event)
190
+ payload = event.payload
191
+ duration = event.duration.round(2)
192
+
193
+ key = payload[:key]
194
+ service = payload[:service]
195
+
196
+ # Record breadcrumb
197
+ record_storage_breadcrumb('download', key, duration, service: service)
198
+
199
+ # Add Pulse span
200
+ record_service_span(event, 'download', key, duration, service: service)
201
+
202
+ # Log slow downloads
203
+ log_slow_operation('download', key, duration, service: service) if duration >= SLOW_OPERATION_THRESHOLD
204
+ rescue StandardError => e
205
+ BrainzLab.debug_log("ActiveStorage service_download instrumentation failed: #{e.message}")
206
+ end
207
+
208
+ # ============================================
209
+ # Service Streaming Download
210
+ # ============================================
211
+ def install_service_streaming_download_subscriber!
212
+ ActiveSupport::Notifications.subscribe('service_streaming_download.active_storage') do |*args|
213
+ event = ActiveSupport::Notifications::Event.new(*args)
214
+ handle_service_streaming_download(event)
215
+ end
216
+ end
217
+
218
+ def handle_service_streaming_download(event)
219
+ payload = event.payload
220
+ duration = event.duration.round(2)
221
+
222
+ key = payload[:key]
223
+ service = payload[:service]
224
+
225
+ # Record breadcrumb
226
+ record_storage_breadcrumb('streaming_download', key, duration, service: service)
227
+
228
+ # Add Pulse span
229
+ record_service_span(event, 'streaming_download', key, duration, service: service)
230
+ rescue StandardError => e
231
+ BrainzLab.debug_log("ActiveStorage service_streaming_download instrumentation failed: #{e.message}")
232
+ end
233
+
234
+ # ============================================
235
+ # Service Delete
236
+ # ============================================
237
+ def install_service_delete_subscriber!
238
+ ActiveSupport::Notifications.subscribe('service_delete.active_storage') do |*args|
239
+ event = ActiveSupport::Notifications::Event.new(*args)
240
+ handle_service_delete(event)
241
+ end
242
+ end
243
+
244
+ def handle_service_delete(event)
245
+ payload = event.payload
246
+ duration = event.duration.round(2)
247
+
248
+ key = payload[:key]
249
+ service = payload[:service]
250
+
251
+ # Record breadcrumb
252
+ record_storage_breadcrumb('delete', key, duration, service: service)
253
+
254
+ # Add Pulse span
255
+ record_service_span(event, 'delete', key, duration, service: service)
256
+ rescue StandardError => e
257
+ BrainzLab.debug_log("ActiveStorage service_delete instrumentation failed: #{e.message}")
258
+ end
259
+
260
+ # ============================================
261
+ # Service Delete Prefixed (bulk delete)
262
+ # ============================================
263
+ def install_service_delete_prefixed_subscriber!
264
+ ActiveSupport::Notifications.subscribe('service_delete_prefixed.active_storage') do |*args|
265
+ event = ActiveSupport::Notifications::Event.new(*args)
266
+ handle_service_delete_prefixed(event)
267
+ end
268
+ end
269
+
270
+ def handle_service_delete_prefixed(event)
271
+ payload = event.payload
272
+ duration = event.duration.round(2)
273
+
274
+ prefix = payload[:prefix]
275
+ service = payload[:service]
276
+
277
+ # Record breadcrumb
278
+ if BrainzLab.configuration.reflex_effectively_enabled?
279
+ BrainzLab::Reflex.add_breadcrumb(
280
+ "Storage delete prefixed: #{truncate_key(prefix)}* (#{duration}ms)",
281
+ category: 'storage.delete_prefixed',
282
+ level: :warning,
283
+ data: {
284
+ prefix: truncate_key(prefix),
285
+ service: service,
286
+ duration_ms: duration
287
+ }.compact
288
+ )
289
+ end
290
+
291
+ # Add Pulse span
292
+ record_service_span(event, 'delete_prefixed', prefix, duration, service: service)
293
+
294
+ # Log to Recall - bulk deletes are significant
295
+ if BrainzLab.configuration.recall_effectively_enabled?
296
+ BrainzLab::Recall.info(
297
+ "Storage bulk delete by prefix",
298
+ prefix: truncate_key(prefix),
299
+ service: service,
300
+ duration_ms: duration
301
+ )
302
+ end
303
+ rescue StandardError => e
304
+ BrainzLab.debug_log("ActiveStorage service_delete_prefixed instrumentation failed: #{e.message}")
305
+ end
306
+
307
+ # ============================================
308
+ # Service Exist
309
+ # ============================================
310
+ def install_service_exist_subscriber!
311
+ ActiveSupport::Notifications.subscribe('service_exist.active_storage') do |*args|
312
+ event = ActiveSupport::Notifications::Event.new(*args)
313
+ handle_service_exist(event)
314
+ end
315
+ end
316
+
317
+ def handle_service_exist(event)
318
+ payload = event.payload
319
+ duration = event.duration.round(2)
320
+
321
+ key = payload[:key]
322
+ service = payload[:service]
323
+ exist = payload[:exist]
324
+
325
+ # Only track if slow (existence checks are frequent)
326
+ return if duration < 5
327
+
328
+ # Add Pulse span
329
+ record_service_span(event, 'exist', key, duration, service: service, exist: exist)
330
+ rescue StandardError => e
331
+ BrainzLab.debug_log("ActiveStorage service_exist instrumentation failed: #{e.message}")
332
+ end
333
+
334
+ # ============================================
335
+ # Service URL (generating signed URLs)
336
+ # ============================================
337
+ def install_service_url_subscriber!
338
+ ActiveSupport::Notifications.subscribe('service_url.active_storage') do |*args|
339
+ event = ActiveSupport::Notifications::Event.new(*args)
340
+ handle_service_url(event)
341
+ end
342
+ end
343
+
344
+ def handle_service_url(event)
345
+ payload = event.payload
346
+ duration = event.duration.round(2)
347
+
348
+ key = payload[:key]
349
+ service = payload[:service]
350
+
351
+ # Only track if slow (URL generation should be fast)
352
+ return if duration < 10
353
+
354
+ # Add Pulse span for slow URL generations
355
+ record_service_span(event, 'url', key, duration, service: service)
356
+
357
+ # Log slow URL generations
358
+ if duration >= 50 && BrainzLab.configuration.recall_effectively_enabled?
359
+ BrainzLab::Recall.warn(
360
+ "Slow storage URL generation",
361
+ key: truncate_key(key),
362
+ service: service,
363
+ duration_ms: duration
364
+ )
365
+ end
366
+ rescue StandardError => e
367
+ BrainzLab.debug_log("ActiveStorage service_url instrumentation failed: #{e.message}")
368
+ end
369
+
370
+ # ============================================
371
+ # Service Download Chunk (chunked downloads)
372
+ # ============================================
373
+ def install_service_download_chunk_subscriber!
374
+ ActiveSupport::Notifications.subscribe('service_download_chunk.active_storage') do |*args|
375
+ event = ActiveSupport::Notifications::Event.new(*args)
376
+ handle_service_download_chunk(event)
377
+ end
378
+ end
379
+
380
+ def handle_service_download_chunk(event)
381
+ payload = event.payload
382
+ duration = event.duration.round(2)
383
+
384
+ key = payload[:key]
385
+ service = payload[:service]
386
+ range = payload[:range]
387
+
388
+ # Only track slow chunks
389
+ return if duration < 10
390
+
391
+ # Add Pulse span for slow chunk downloads
392
+ record_service_span(event, 'download_chunk', key, duration, service: service, range: range.to_s)
393
+ rescue StandardError => e
394
+ BrainzLab.debug_log("ActiveStorage service_download_chunk instrumentation failed: #{e.message}")
395
+ end
396
+
397
+ # ============================================
398
+ # Service Update Metadata
399
+ # ============================================
400
+ def install_service_update_metadata_subscriber!
401
+ ActiveSupport::Notifications.subscribe('service_update_metadata.active_storage') do |*args|
402
+ event = ActiveSupport::Notifications::Event.new(*args)
403
+ handle_service_update_metadata(event)
404
+ end
405
+ end
406
+
407
+ def handle_service_update_metadata(event)
408
+ payload = event.payload
409
+ duration = event.duration.round(2)
410
+
411
+ key = payload[:key]
412
+ service = payload[:service]
413
+ content_type = payload[:content_type]
414
+ disposition = payload[:disposition]
415
+
416
+ # Record breadcrumb
417
+ if BrainzLab.configuration.reflex_effectively_enabled?
418
+ BrainzLab::Reflex.add_breadcrumb(
419
+ "Storage metadata update: #{truncate_key(key)} (#{duration}ms)",
420
+ category: 'storage.metadata',
421
+ level: :info,
422
+ data: {
423
+ key: truncate_key(key),
424
+ service: service,
425
+ content_type: content_type,
426
+ duration_ms: duration
427
+ }.compact
428
+ )
429
+ end
430
+
431
+ # Add Pulse span
432
+ record_service_span(event, 'update_metadata', key, duration,
433
+ service: service, content_type: content_type)
434
+ rescue StandardError => e
435
+ BrainzLab.debug_log("ActiveStorage service_update_metadata instrumentation failed: #{e.message}")
436
+ end
437
+
438
+ # ============================================
439
+ # Recording Helpers
440
+ # ============================================
441
+ def record_storage_breadcrumb(operation, key, duration, service: nil)
442
+ return unless BrainzLab.configuration.reflex_effectively_enabled?
443
+
444
+ level = case duration
445
+ when 0...SLOW_OPERATION_THRESHOLD then :info
446
+ when SLOW_OPERATION_THRESHOLD...VERY_SLOW_OPERATION_THRESHOLD then :warning
447
+ else :error
448
+ end
449
+
450
+ BrainzLab::Reflex.add_breadcrumb(
451
+ "Storage #{operation}: #{truncate_key(key)} (#{duration}ms)",
452
+ category: "storage.#{operation}",
453
+ level: level,
454
+ data: {
455
+ key: truncate_key(key),
456
+ service: service,
457
+ duration_ms: duration
458
+ }.compact
459
+ )
460
+ end
461
+
462
+ def record_storage_span(event, operation, key, duration, **extra)
463
+ return unless BrainzLab.configuration.pulse_effectively_enabled?
464
+
465
+ tracer = BrainzLab::Pulse.tracer
466
+ return unless tracer.current_trace
467
+
468
+ span_data = {
469
+ span_id: SecureRandom.uuid,
470
+ name: "storage.#{operation}",
471
+ kind: 'storage',
472
+ started_at: event.time,
473
+ ended_at: event.end,
474
+ duration_ms: duration,
475
+ error: false,
476
+ data: {
477
+ 'storage.operation' => operation,
478
+ 'storage.key' => truncate_key(key)
479
+ }.merge(extra.transform_keys { |k| "storage.#{k}" }).compact
480
+ }
481
+
482
+ tracer.current_spans << span_data
483
+ end
484
+
485
+ def record_service_span(event, operation, key, duration, service: nil, **extra)
486
+ return unless BrainzLab.configuration.pulse_effectively_enabled?
487
+
488
+ tracer = BrainzLab::Pulse.tracer
489
+ return unless tracer.current_trace
490
+
491
+ span_data = {
492
+ span_id: SecureRandom.uuid,
493
+ name: "storage.service.#{operation}",
494
+ kind: 'storage',
495
+ started_at: event.time,
496
+ ended_at: event.end,
497
+ duration_ms: duration,
498
+ error: false,
499
+ data: {
500
+ 'storage.operation' => operation,
501
+ 'storage.key' => truncate_key(key),
502
+ 'storage.service' => service
503
+ }.merge(extra.transform_keys { |k| "storage.#{k}" }).compact
504
+ }
505
+
506
+ tracer.current_spans << span_data
507
+ end
508
+
509
+ def log_slow_operation(operation, key, duration, service: nil)
510
+ return unless BrainzLab.configuration.recall_effectively_enabled?
511
+
512
+ level = duration >= VERY_SLOW_OPERATION_THRESHOLD ? :error : :warn
513
+
514
+ BrainzLab::Recall.send(
515
+ level,
516
+ "Slow storage #{operation}: #{truncate_key(key)} (#{duration}ms)",
517
+ operation: operation,
518
+ key: truncate_key(key),
519
+ service: service,
520
+ duration_ms: duration,
521
+ threshold_exceeded: duration >= VERY_SLOW_OPERATION_THRESHOLD ? 'critical' : 'warning'
522
+ )
523
+ end
524
+
525
+ # ============================================
526
+ # Helper Methods
527
+ # ============================================
528
+ def truncate_key(key, max_length = 100)
529
+ return 'unknown' unless key
530
+
531
+ key_str = key.to_s
532
+ if key_str.length > max_length
533
+ "#{key_str[0, max_length - 3]}..."
534
+ else
535
+ key_str
536
+ end
537
+ end
538
+ end
539
+ end
540
+ end
541
+ end