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,700 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BrainzLab
4
+ module Instrumentation
5
+ class ActiveSupportCache
6
+ # Thresholds for slow cache operations (in milliseconds)
7
+ SLOW_CACHE_THRESHOLD = 10
8
+ VERY_SLOW_CACHE_THRESHOLD = 50
9
+
10
+ class << self
11
+ def install!
12
+ return unless defined?(::ActiveSupport::Cache)
13
+ return if @installed
14
+
15
+ install_cache_read_subscriber!
16
+ install_cache_read_multi_subscriber!
17
+ install_cache_write_subscriber!
18
+ install_cache_write_multi_subscriber!
19
+ install_cache_delete_subscriber!
20
+ install_cache_exist_subscriber!
21
+ install_cache_fetch_hit_subscriber!
22
+ install_cache_generate_subscriber!
23
+ install_cache_increment_subscriber!
24
+ install_cache_decrement_subscriber!
25
+ install_cache_delete_multi_subscriber!
26
+ install_cache_delete_matched_subscriber!
27
+ install_cache_cleanup_subscriber!
28
+ install_cache_prune_subscriber!
29
+ install_message_serializer_fallback_subscriber!
30
+
31
+ @installed = true
32
+ BrainzLab.debug_log('ActiveSupport::Cache instrumentation installed')
33
+ end
34
+
35
+ def installed?
36
+ @installed == true
37
+ end
38
+
39
+ private
40
+
41
+ # ============================================
42
+ # Cache Read
43
+ # ============================================
44
+ def install_cache_read_subscriber!
45
+ ActiveSupport::Notifications.subscribe('cache_read.active_support') do |*args|
46
+ event = ActiveSupport::Notifications::Event.new(*args)
47
+ handle_cache_read(event)
48
+ end
49
+ end
50
+
51
+ def handle_cache_read(event)
52
+ payload = event.payload
53
+ duration = event.duration.round(2)
54
+
55
+ key = payload[:key]
56
+ hit = payload[:hit]
57
+ super_operation = payload[:super_operation]
58
+
59
+ # Skip if this is part of a fetch operation (will be tracked separately)
60
+ return if super_operation == :fetch
61
+
62
+ # Record breadcrumb
63
+ record_cache_breadcrumb('read', key, duration, hit: hit)
64
+
65
+ # Add Pulse span
66
+ record_cache_span(event, 'read', key, duration, hit: hit)
67
+
68
+ # Track cache metrics
69
+ track_cache_metrics('read', hit, duration)
70
+ rescue StandardError => e
71
+ BrainzLab.debug_log("ActiveSupport::Cache read instrumentation failed: #{e.message}")
72
+ end
73
+
74
+ # ============================================
75
+ # Cache Read Multi
76
+ # ============================================
77
+ def install_cache_read_multi_subscriber!
78
+ ActiveSupport::Notifications.subscribe('cache_read_multi.active_support') do |*args|
79
+ event = ActiveSupport::Notifications::Event.new(*args)
80
+ handle_cache_read_multi(event)
81
+ end
82
+ end
83
+
84
+ def handle_cache_read_multi(event)
85
+ payload = event.payload
86
+ duration = event.duration.round(2)
87
+
88
+ key = payload[:key] # Array of keys
89
+ hits = payload[:hits] # Keys that were found
90
+ super_operation = payload[:super_operation]
91
+
92
+ return if super_operation == :fetch_multi
93
+
94
+ key_count = Array(key).size
95
+ hit_count = Array(hits).size
96
+
97
+ # Record breadcrumb
98
+ if BrainzLab.configuration.reflex_effectively_enabled?
99
+ BrainzLab::Reflex.add_breadcrumb(
100
+ "Cache read_multi: #{hit_count}/#{key_count} hits (#{duration}ms)",
101
+ category: 'cache.read_multi',
102
+ level: duration >= SLOW_CACHE_THRESHOLD ? :warning : :info,
103
+ data: {
104
+ key_count: key_count,
105
+ hit_count: hit_count,
106
+ miss_count: key_count - hit_count,
107
+ duration_ms: duration,
108
+ hit_rate: key_count > 0 ? (hit_count.to_f / key_count * 100).round(1) : 0
109
+ }
110
+ )
111
+ end
112
+
113
+ # Add Pulse span
114
+ record_cache_span(event, 'read_multi', "#{key_count} keys", duration,
115
+ key_count: key_count, hit_count: hit_count)
116
+ rescue StandardError => e
117
+ BrainzLab.debug_log("ActiveSupport::Cache read_multi instrumentation failed: #{e.message}")
118
+ end
119
+
120
+ # ============================================
121
+ # Cache Write
122
+ # ============================================
123
+ def install_cache_write_subscriber!
124
+ ActiveSupport::Notifications.subscribe('cache_write.active_support') do |*args|
125
+ event = ActiveSupport::Notifications::Event.new(*args)
126
+ handle_cache_write(event)
127
+ end
128
+ end
129
+
130
+ def handle_cache_write(event)
131
+ payload = event.payload
132
+ duration = event.duration.round(2)
133
+
134
+ key = payload[:key]
135
+
136
+ # Record breadcrumb
137
+ record_cache_breadcrumb('write', key, duration)
138
+
139
+ # Add Pulse span
140
+ record_cache_span(event, 'write', key, duration)
141
+
142
+ # Log slow writes
143
+ log_slow_cache_operation('write', key, duration) if duration >= SLOW_CACHE_THRESHOLD
144
+ rescue StandardError => e
145
+ BrainzLab.debug_log("ActiveSupport::Cache write instrumentation failed: #{e.message}")
146
+ end
147
+
148
+ # ============================================
149
+ # Cache Write Multi
150
+ # ============================================
151
+ def install_cache_write_multi_subscriber!
152
+ ActiveSupport::Notifications.subscribe('cache_write_multi.active_support') do |*args|
153
+ event = ActiveSupport::Notifications::Event.new(*args)
154
+ handle_cache_write_multi(event)
155
+ end
156
+ end
157
+
158
+ def handle_cache_write_multi(event)
159
+ payload = event.payload
160
+ duration = event.duration.round(2)
161
+
162
+ key = payload[:key] # Hash of key => value pairs
163
+ key_count = key.is_a?(Hash) ? key.size : 1
164
+
165
+ # Record breadcrumb
166
+ if BrainzLab.configuration.reflex_effectively_enabled?
167
+ BrainzLab::Reflex.add_breadcrumb(
168
+ "Cache write_multi: #{key_count} keys (#{duration}ms)",
169
+ category: 'cache.write_multi',
170
+ level: duration >= SLOW_CACHE_THRESHOLD ? :warning : :info,
171
+ data: {
172
+ key_count: key_count,
173
+ duration_ms: duration
174
+ }
175
+ )
176
+ end
177
+
178
+ # Add Pulse span
179
+ record_cache_span(event, 'write_multi', "#{key_count} keys", duration, key_count: key_count)
180
+ rescue StandardError => e
181
+ BrainzLab.debug_log("ActiveSupport::Cache write_multi instrumentation failed: #{e.message}")
182
+ end
183
+
184
+ # ============================================
185
+ # Cache Delete
186
+ # ============================================
187
+ def install_cache_delete_subscriber!
188
+ ActiveSupport::Notifications.subscribe('cache_delete.active_support') do |*args|
189
+ event = ActiveSupport::Notifications::Event.new(*args)
190
+ handle_cache_delete(event)
191
+ end
192
+ end
193
+
194
+ def handle_cache_delete(event)
195
+ payload = event.payload
196
+ duration = event.duration.round(2)
197
+
198
+ key = payload[:key]
199
+
200
+ # Record breadcrumb
201
+ record_cache_breadcrumb('delete', key, duration)
202
+
203
+ # Add Pulse span
204
+ record_cache_span(event, 'delete', key, duration)
205
+ rescue StandardError => e
206
+ BrainzLab.debug_log("ActiveSupport::Cache delete instrumentation failed: #{e.message}")
207
+ end
208
+
209
+ # ============================================
210
+ # Cache Exist?
211
+ # ============================================
212
+ def install_cache_exist_subscriber!
213
+ ActiveSupport::Notifications.subscribe('cache_exist?.active_support') do |*args|
214
+ event = ActiveSupport::Notifications::Event.new(*args)
215
+ handle_cache_exist(event)
216
+ end
217
+ end
218
+
219
+ def handle_cache_exist(event)
220
+ payload = event.payload
221
+ duration = event.duration.round(2)
222
+
223
+ key = payload[:key]
224
+
225
+ # Only track if slow or significant
226
+ return if duration < 1
227
+
228
+ # Add Pulse span (skip breadcrumb for exist? as it's noisy)
229
+ record_cache_span(event, 'exist', key, duration)
230
+ rescue StandardError => e
231
+ BrainzLab.debug_log("ActiveSupport::Cache exist? instrumentation failed: #{e.message}")
232
+ end
233
+
234
+ # ============================================
235
+ # Cache Fetch Hit (successful fetch from cache)
236
+ # ============================================
237
+ def install_cache_fetch_hit_subscriber!
238
+ ActiveSupport::Notifications.subscribe('cache_fetch_hit.active_support') do |*args|
239
+ event = ActiveSupport::Notifications::Event.new(*args)
240
+ handle_cache_fetch_hit(event)
241
+ end
242
+ end
243
+
244
+ def handle_cache_fetch_hit(event)
245
+ payload = event.payload
246
+ duration = event.duration.round(2)
247
+
248
+ key = payload[:key]
249
+
250
+ # Record breadcrumb
251
+ record_cache_breadcrumb('fetch', key, duration, hit: true)
252
+
253
+ # Add Pulse span
254
+ record_cache_span(event, 'fetch', key, duration, hit: true)
255
+
256
+ # Track cache metrics
257
+ track_cache_metrics('fetch', true, duration)
258
+ rescue StandardError => e
259
+ BrainzLab.debug_log("ActiveSupport::Cache fetch_hit instrumentation failed: #{e.message}")
260
+ end
261
+
262
+ # ============================================
263
+ # Cache Generate (cache miss, value computed)
264
+ # ============================================
265
+ def install_cache_generate_subscriber!
266
+ ActiveSupport::Notifications.subscribe('cache_generate.active_support') do |*args|
267
+ event = ActiveSupport::Notifications::Event.new(*args)
268
+ handle_cache_generate(event)
269
+ end
270
+ end
271
+
272
+ def handle_cache_generate(event)
273
+ payload = event.payload
274
+ duration = event.duration.round(2)
275
+
276
+ key = payload[:key]
277
+
278
+ # Record breadcrumb - this is a cache miss that triggered computation
279
+ if BrainzLab.configuration.reflex_effectively_enabled?
280
+ level = case duration
281
+ when 0...SLOW_CACHE_THRESHOLD then :info
282
+ when SLOW_CACHE_THRESHOLD...VERY_SLOW_CACHE_THRESHOLD then :warning
283
+ else :error
284
+ end
285
+
286
+ BrainzLab::Reflex.add_breadcrumb(
287
+ "Cache miss + generate: #{truncate_key(key)} (#{duration}ms)",
288
+ category: 'cache.generate',
289
+ level: level,
290
+ data: {
291
+ key: truncate_key(key),
292
+ duration_ms: duration,
293
+ hit: false
294
+ }
295
+ )
296
+ end
297
+
298
+ # Add Pulse span
299
+ record_cache_span(event, 'generate', key, duration, hit: false)
300
+
301
+ # Track cache metrics
302
+ track_cache_metrics('fetch', false, duration)
303
+
304
+ # Log slow cache generations
305
+ log_slow_cache_operation('generate', key, duration) if duration >= SLOW_CACHE_THRESHOLD
306
+ rescue StandardError => e
307
+ BrainzLab.debug_log("ActiveSupport::Cache generate instrumentation failed: #{e.message}")
308
+ end
309
+
310
+ # ============================================
311
+ # Cache Increment
312
+ # ============================================
313
+ def install_cache_increment_subscriber!
314
+ ActiveSupport::Notifications.subscribe('cache_increment.active_support') do |*args|
315
+ event = ActiveSupport::Notifications::Event.new(*args)
316
+ handle_cache_increment(event)
317
+ end
318
+ end
319
+
320
+ def handle_cache_increment(event)
321
+ payload = event.payload
322
+ duration = event.duration.round(2)
323
+
324
+ key = payload[:key]
325
+ amount = payload[:amount]
326
+
327
+ # Record breadcrumb
328
+ if BrainzLab.configuration.reflex_effectively_enabled?
329
+ BrainzLab::Reflex.add_breadcrumb(
330
+ "Cache increment: #{truncate_key(key)} by #{amount} (#{duration}ms)",
331
+ category: 'cache.increment',
332
+ level: :info,
333
+ data: {
334
+ key: truncate_key(key),
335
+ amount: amount,
336
+ duration_ms: duration
337
+ }.compact
338
+ )
339
+ end
340
+
341
+ # Add Pulse span
342
+ record_cache_span(event, 'increment', key, duration, amount: amount)
343
+ rescue StandardError => e
344
+ BrainzLab.debug_log("ActiveSupport::Cache increment instrumentation failed: #{e.message}")
345
+ end
346
+
347
+ # ============================================
348
+ # Cache Decrement
349
+ # ============================================
350
+ def install_cache_decrement_subscriber!
351
+ ActiveSupport::Notifications.subscribe('cache_decrement.active_support') do |*args|
352
+ event = ActiveSupport::Notifications::Event.new(*args)
353
+ handle_cache_decrement(event)
354
+ end
355
+ end
356
+
357
+ def handle_cache_decrement(event)
358
+ payload = event.payload
359
+ duration = event.duration.round(2)
360
+
361
+ key = payload[:key]
362
+ amount = payload[:amount]
363
+
364
+ # Record breadcrumb
365
+ if BrainzLab.configuration.reflex_effectively_enabled?
366
+ BrainzLab::Reflex.add_breadcrumb(
367
+ "Cache decrement: #{truncate_key(key)} by #{amount} (#{duration}ms)",
368
+ category: 'cache.decrement',
369
+ level: :info,
370
+ data: {
371
+ key: truncate_key(key),
372
+ amount: amount,
373
+ duration_ms: duration
374
+ }.compact
375
+ )
376
+ end
377
+
378
+ # Add Pulse span
379
+ record_cache_span(event, 'decrement', key, duration, amount: amount)
380
+ rescue StandardError => e
381
+ BrainzLab.debug_log("ActiveSupport::Cache decrement instrumentation failed: #{e.message}")
382
+ end
383
+
384
+ # ============================================
385
+ # Cache Delete Multi
386
+ # ============================================
387
+ def install_cache_delete_multi_subscriber!
388
+ ActiveSupport::Notifications.subscribe('cache_delete_multi.active_support') do |*args|
389
+ event = ActiveSupport::Notifications::Event.new(*args)
390
+ handle_cache_delete_multi(event)
391
+ end
392
+ end
393
+
394
+ def handle_cache_delete_multi(event)
395
+ payload = event.payload
396
+ duration = event.duration.round(2)
397
+
398
+ key = payload[:key] # Array of keys
399
+ key_count = Array(key).size
400
+
401
+ # Record breadcrumb
402
+ if BrainzLab.configuration.reflex_effectively_enabled?
403
+ BrainzLab::Reflex.add_breadcrumb(
404
+ "Cache delete_multi: #{key_count} keys (#{duration}ms)",
405
+ category: 'cache.delete_multi',
406
+ level: :info,
407
+ data: {
408
+ key_count: key_count,
409
+ duration_ms: duration
410
+ }
411
+ )
412
+ end
413
+
414
+ # Add Pulse span
415
+ record_cache_span(event, 'delete_multi', "#{key_count} keys", duration, key_count: key_count)
416
+ rescue StandardError => e
417
+ BrainzLab.debug_log("ActiveSupport::Cache delete_multi instrumentation failed: #{e.message}")
418
+ end
419
+
420
+ # ============================================
421
+ # Cache Delete Matched (pattern-based delete)
422
+ # ============================================
423
+ def install_cache_delete_matched_subscriber!
424
+ ActiveSupport::Notifications.subscribe('cache_delete_matched.active_support') do |*args|
425
+ event = ActiveSupport::Notifications::Event.new(*args)
426
+ handle_cache_delete_matched(event)
427
+ end
428
+ end
429
+
430
+ def handle_cache_delete_matched(event)
431
+ payload = event.payload
432
+ duration = event.duration.round(2)
433
+
434
+ key = payload[:key] # Pattern
435
+
436
+ # Record breadcrumb - pattern deletes are significant
437
+ if BrainzLab.configuration.reflex_effectively_enabled?
438
+ BrainzLab::Reflex.add_breadcrumb(
439
+ "Cache delete_matched: #{truncate_key(key)} (#{duration}ms)",
440
+ category: 'cache.delete_matched',
441
+ level: :warning,
442
+ data: {
443
+ pattern: truncate_key(key),
444
+ duration_ms: duration
445
+ }
446
+ )
447
+ end
448
+
449
+ # Add Pulse span
450
+ record_cache_span(event, 'delete_matched', key, duration)
451
+
452
+ # Log pattern deletes
453
+ if BrainzLab.configuration.recall_effectively_enabled?
454
+ BrainzLab::Recall.info(
455
+ "Cache pattern delete",
456
+ pattern: truncate_key(key),
457
+ duration_ms: duration
458
+ )
459
+ end
460
+ rescue StandardError => e
461
+ BrainzLab.debug_log("ActiveSupport::Cache delete_matched instrumentation failed: #{e.message}")
462
+ end
463
+
464
+ # ============================================
465
+ # Cache Cleanup (remove expired entries)
466
+ # ============================================
467
+ def install_cache_cleanup_subscriber!
468
+ ActiveSupport::Notifications.subscribe('cache_cleanup.active_support') do |*args|
469
+ event = ActiveSupport::Notifications::Event.new(*args)
470
+ handle_cache_cleanup(event)
471
+ end
472
+ end
473
+
474
+ def handle_cache_cleanup(event)
475
+ payload = event.payload
476
+ duration = event.duration.round(2)
477
+
478
+ size = payload[:size]
479
+ key = payload[:key]
480
+
481
+ # Record breadcrumb
482
+ if BrainzLab.configuration.reflex_effectively_enabled?
483
+ BrainzLab::Reflex.add_breadcrumb(
484
+ "Cache cleanup: size=#{size} (#{duration}ms)",
485
+ category: 'cache.cleanup',
486
+ level: :info,
487
+ data: {
488
+ size: size,
489
+ duration_ms: duration
490
+ }.compact
491
+ )
492
+ end
493
+
494
+ # Add Pulse span
495
+ record_cache_span(event, 'cleanup', 'cleanup', duration, size: size)
496
+ rescue StandardError => e
497
+ BrainzLab.debug_log("ActiveSupport::Cache cleanup instrumentation failed: #{e.message}")
498
+ end
499
+
500
+ # ============================================
501
+ # Cache Prune (reduce cache size)
502
+ # ============================================
503
+ def install_cache_prune_subscriber!
504
+ ActiveSupport::Notifications.subscribe('cache_prune.active_support') do |*args|
505
+ event = ActiveSupport::Notifications::Event.new(*args)
506
+ handle_cache_prune(event)
507
+ end
508
+ end
509
+
510
+ def handle_cache_prune(event)
511
+ payload = event.payload
512
+ duration = event.duration.round(2)
513
+
514
+ key = payload[:key]
515
+ from = payload[:from]
516
+ to = payload[:to]
517
+
518
+ # Record breadcrumb
519
+ if BrainzLab.configuration.reflex_effectively_enabled?
520
+ BrainzLab::Reflex.add_breadcrumb(
521
+ "Cache prune: #{from} -> #{to} (#{duration}ms)",
522
+ category: 'cache.prune',
523
+ level: :info,
524
+ data: {
525
+ from: from,
526
+ to: to,
527
+ duration_ms: duration
528
+ }.compact
529
+ )
530
+ end
531
+
532
+ # Add Pulse span
533
+ record_cache_span(event, 'prune', 'prune', duration, from: from, to: to)
534
+
535
+ # Log prune operations
536
+ if BrainzLab.configuration.recall_effectively_enabled?
537
+ BrainzLab::Recall.info(
538
+ "Cache pruned",
539
+ from: from,
540
+ to: to,
541
+ duration_ms: duration
542
+ )
543
+ end
544
+ rescue StandardError => e
545
+ BrainzLab.debug_log("ActiveSupport::Cache prune instrumentation failed: #{e.message}")
546
+ end
547
+
548
+ # ============================================
549
+ # Message Serializer Fallback
550
+ # Fired when a message is deserialized using a fallback serializer
551
+ # This typically indicates a migration between serialization formats
552
+ # ============================================
553
+ def install_message_serializer_fallback_subscriber!
554
+ ActiveSupport::Notifications.subscribe('message_serializer_fallback.active_support') do |*args|
555
+ event = ActiveSupport::Notifications::Event.new(*args)
556
+ handle_message_serializer_fallback(event)
557
+ end
558
+ end
559
+
560
+ def handle_message_serializer_fallback(event)
561
+ payload = event.payload
562
+ duration = event.duration.round(2)
563
+
564
+ serializer = payload[:serializer]
565
+ fallback = payload[:fallback]
566
+ serialized = payload[:serialized]
567
+ deserialized = payload[:deserialized]
568
+
569
+ # Record breadcrumb - this is a warning as it indicates a format mismatch
570
+ if BrainzLab.configuration.reflex_effectively_enabled?
571
+ BrainzLab::Reflex.add_breadcrumb(
572
+ "Message serializer fallback: #{serializer} -> #{fallback}",
573
+ category: 'serializer.fallback',
574
+ level: :warning,
575
+ data: {
576
+ serializer: serializer.to_s,
577
+ fallback: fallback.to_s,
578
+ duration_ms: duration
579
+ }.compact
580
+ )
581
+ end
582
+
583
+ # Log to Recall - this is significant for debugging serialization issues
584
+ if BrainzLab.configuration.recall_effectively_enabled?
585
+ BrainzLab::Recall.warn(
586
+ "Message serializer fallback used",
587
+ serializer: serializer.to_s,
588
+ fallback: fallback.to_s,
589
+ duration_ms: duration
590
+ )
591
+ end
592
+ rescue StandardError => e
593
+ BrainzLab.debug_log("ActiveSupport message_serializer_fallback instrumentation failed: #{e.message}")
594
+ end
595
+
596
+ # ============================================
597
+ # Recording Helpers
598
+ # ============================================
599
+ def record_cache_breadcrumb(operation, key, duration, hit: nil)
600
+ return unless BrainzLab.configuration.reflex_effectively_enabled?
601
+
602
+ level = duration >= SLOW_CACHE_THRESHOLD ? :warning : :info
603
+
604
+ message = if hit.nil?
605
+ "Cache #{operation}: #{truncate_key(key)} (#{duration}ms)"
606
+ elsif hit
607
+ "Cache #{operation} hit: #{truncate_key(key)} (#{duration}ms)"
608
+ else
609
+ "Cache #{operation} miss: #{truncate_key(key)} (#{duration}ms)"
610
+ end
611
+
612
+ data = {
613
+ key: truncate_key(key),
614
+ operation: operation,
615
+ duration_ms: duration
616
+ }
617
+ data[:hit] = hit unless hit.nil?
618
+
619
+ BrainzLab::Reflex.add_breadcrumb(
620
+ message,
621
+ category: "cache.#{operation}",
622
+ level: level,
623
+ data: data.compact
624
+ )
625
+ end
626
+
627
+ def record_cache_span(event, operation, key, duration, hit: nil, **extra)
628
+ return unless BrainzLab.configuration.pulse_effectively_enabled?
629
+
630
+ tracer = BrainzLab::Pulse.tracer
631
+ return unless tracer.current_trace
632
+
633
+ span_data = {
634
+ span_id: SecureRandom.uuid,
635
+ name: "cache.#{operation}",
636
+ kind: 'cache',
637
+ started_at: event.time,
638
+ ended_at: event.end,
639
+ duration_ms: duration,
640
+ error: false,
641
+ data: {
642
+ 'cache.operation' => operation,
643
+ 'cache.key' => truncate_key(key),
644
+ 'cache.hit' => hit
645
+ }.merge(extra.transform_keys { |k| "cache.#{k}" }).compact
646
+ }
647
+
648
+ tracer.current_spans << span_data
649
+ end
650
+
651
+ def track_cache_metrics(operation, hit, duration)
652
+ return unless BrainzLab.configuration.pulse_effectively_enabled?
653
+
654
+ # Increment cache operation counter
655
+ BrainzLab::Pulse.counter(
656
+ "cache.#{operation}.total",
657
+ 1,
658
+ tags: { hit: hit.to_s }
659
+ )
660
+
661
+ # Record cache operation duration
662
+ BrainzLab::Pulse.histogram(
663
+ "cache.#{operation}.duration_ms",
664
+ duration,
665
+ tags: { hit: hit.to_s }
666
+ )
667
+ end
668
+
669
+ def log_slow_cache_operation(operation, key, duration)
670
+ return unless BrainzLab.configuration.recall_effectively_enabled?
671
+
672
+ level = duration >= VERY_SLOW_CACHE_THRESHOLD ? :error : :warn
673
+
674
+ BrainzLab::Recall.send(
675
+ level,
676
+ "Slow cache #{operation}: #{truncate_key(key)} (#{duration}ms)",
677
+ operation: operation,
678
+ key: truncate_key(key),
679
+ duration_ms: duration,
680
+ threshold_exceeded: duration >= VERY_SLOW_CACHE_THRESHOLD ? 'critical' : 'warning'
681
+ )
682
+ end
683
+
684
+ # ============================================
685
+ # Helper Methods
686
+ # ============================================
687
+ def truncate_key(key, max_length = 100)
688
+ return 'unknown' unless key
689
+
690
+ key_str = key.to_s
691
+ if key_str.length > max_length
692
+ "#{key_str[0, max_length - 3]}..."
693
+ else
694
+ key_str
695
+ end
696
+ end
697
+ end
698
+ end
699
+ end
700
+ end