Class: RailsErrorDashboard::Services::StormProtection::CountBuffer
- Inherits:
-
Object
- Object
- RailsErrorDashboard::Services::StormProtection::CountBuffer
- Defined in:
- lib/rails_error_dashboard/services/storm_protection/count_buffer.rb
Overview
In-memory accumulator for events that are counted but not stored per-event (Layer 1 overflow and the breaker’s count-only mode).
Stores exact counts plus just enough identity to reconcile onto the right ErrorLog at flush time: the flush command recomputes the canonical error_hash from these parts (with application resolved in the background job, where DB access is allowed) and issues a single ‘occurrence_count = occurrence_count + N` UPDATE per fingerprint. Fingerprints first seen during count-only mode get a minimal ErrorLog created from the stored exemplar. Counting is exact — no extrapolation.
Memory: bounded map; beyond the cap events land in a single overflow counter (still exact in total, anonymous in identity).
Concurrency: snapshot! atomically swaps the whole map out via AtomicReference, so flushing never races with recording.
Defined Under Namespace
Classes: Entry
Instance Method Summary collapse
- #any? ⇒ Boolean
-
#initialize ⇒ CountBuffer
constructor
A new instance of CountBuffer.
-
#record(gate_key, parts) ⇒ Object
Record one counted-not-stored event.
- #reset! ⇒ Object
-
#snapshot! ⇒ Hash
Atomically swap the buffer out and return serializable entry hashes.
Constructor Details
#initialize ⇒ CountBuffer
Returns a new instance of CountBuffer.
29 30 31 |
# File 'lib/rails_error_dashboard/services/storm_protection/count_buffer.rb', line 29 def initialize reset! end |
Instance Method Details
#any? ⇒ Boolean
63 64 65 |
# File 'lib/rails_error_dashboard/services/storm_protection/count_buffer.rb', line 63 def any? @overflow.value.positive? || !@map_ref.get.empty? end |
#record(gate_key, parts) ⇒ Object
Record one counted-not-stored event.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/rails_error_dashboard/services/storm_protection/count_buffer.rb', line 41 def record(gate_key, parts) map = @map_ref.get entry = map[gate_key] unless entry if map.size >= max_tracked @overflow.increment return end entry = map.compute_if_absent(gate_key) do Entry.new( parts[:error_class], parts[:message], parts[:first_app_frame], parts[:controller_name], parts[:action_name], parts[:custom_hash], Concurrent::AtomicFixnum.new(0), Time.current, Time.current ) end end entry.count.increment entry.last_seen_at = Time.current end |
#reset! ⇒ Object
33 34 35 36 |
# File 'lib/rails_error_dashboard/services/storm_protection/count_buffer.rb', line 33 def reset! @map_ref = Concurrent::AtomicReference.new(Concurrent::Map.new) @overflow = Concurrent::AtomicFixnum.new(0) end |
#snapshot! ⇒ Hash
Atomically swap the buffer out and return serializable entry hashes.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/rails_error_dashboard/services/storm_protection/count_buffer.rb', line 69 def snapshot! old_map = @map_ref.get_and_set(Concurrent::Map.new) overflow = @overflow.value @overflow.update { |v| v - overflow } entries = [] old_map.each_pair do |_key, entry| entries << { "error_class" => entry.error_class, "message" => entry., "first_app_frame" => entry.first_app_frame, "controller_name" => entry.controller_name, "action_name" => entry.action_name, "custom_hash" => entry.custom_hash, "count" => entry.count.value, "first_seen_at" => entry.first_seen_at.iso8601, "last_seen_at" => entry.last_seen_at.iso8601 } end { entries: entries, overflow: overflow } end |