Class: Findbug::ErrorEvent
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Findbug::ErrorEvent
- Defined in:
- app/models/findbug/error_event.rb
Overview
ErrorEvent stores captured exceptions in the database.
DATABASE SCHEMA
This model expects a table created by the install generator:
create_table :findbug_error_events do |t|
t.string :fingerprint, null: false
t.string :exception_class, null: false
t.text :message
t.text :backtrace
t.jsonb :context, default: {}
t.jsonb :request_data, default: {}
t.string :environment
t.string :release_version
t.string :severity, default: 'error'
t.string :source
t.boolean :handled, default: false
t.integer :occurrence_count, default: 1
t.datetime :first_seen_at
t.datetime :last_seen_at
t.string :status, default: 'unresolved'
t.
end
WHY OVERRIDE JSON ACCESSORS?
The column type for context/request_data varies by adapter:
PostgreSQL → jsonb (AR returns Hash natively)
MySQL → json (AR returns Hash natively)
SQLite → text (AR returns raw JSON String)
The overrides below normalise both cases so callers always get a Hash.
Constant Summary collapse
- STATUS_UNRESOLVED =
Statuses
"unresolved"- STATUS_RESOLVED =
"resolved"- STATUS_IGNORED =
"ignored"- SEVERITY_ERROR =
Severities
"error"- SEVERITY_WARNING =
"warning"- SEVERITY_INFO =
"info"
Class Method Summary collapse
- .merge_contexts(old_context, new_context) ⇒ Object
- .serialize_backtrace(backtrace) ⇒ Object
-
.upsert_from_event(event_data) ⇒ ErrorEvent
Find or create an error event, incrementing count if exists.
Instance Method Summary collapse
-
#backtrace_lines ⇒ Object
Get parsed backtrace as array.
-
#breadcrumbs ⇒ Object
Get breadcrumbs from context.
-
#ignore! ⇒ Object
Mark this error as ignored.
-
#reopen! ⇒ Object
Reopen a resolved/ignored error.
-
#request ⇒ Object
Get request info from context.
-
#resolve! ⇒ Object
Mark this error as resolved.
-
#summary ⇒ Object
Short summary for lists.
-
#tags ⇒ Object
Get tags from context.
-
#user ⇒ Object
Get user info from context.
Class Method Details
.merge_contexts(old_context, new_context) ⇒ Object
211 212 213 214 215 216 217 |
# File 'app/models/findbug/error_event.rb', line 211 def self.merge_contexts(old_context, new_context) return new_context if old_context.blank? return old_context if new_context.blank? # Deep merge, preferring new values old_context.deep_merge(new_context) end |
.serialize_backtrace(backtrace) ⇒ Object
219 220 221 222 223 |
# File 'app/models/findbug/error_event.rb', line 219 def self.serialize_backtrace(backtrace) return nil unless backtrace backtrace.is_a?(Array) ? backtrace.to_json : backtrace end |
.upsert_from_event(event_data) ⇒ ErrorEvent
Find or create an error event, incrementing count if exists
UPSERT PATTERN
We use “upsert” logic: if an error with this fingerprint exists, we update it (increment count, update last_seen_at). Otherwise, we create a new record.
This groups similar errors together instead of creating thousands of duplicate records.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'app/models/findbug/error_event.rb', line 115 def self.upsert_from_event(event_data) fingerprint = event_data[:fingerprint] # Use database-level locking to prevent race conditions transaction do existing = find_by(fingerprint: fingerprint) if existing # Update existing error existing.occurrence_count += 1 existing.last_seen_at = Time.current # Update context with latest (might have new info) existing.context = merge_contexts(existing.context, event_data[:context]) # If it was resolved but happened again, reopen it if existing.status == STATUS_RESOLVED existing.status = STATUS_UNRESOLVED end existing.save! existing else # Create new error create!( fingerprint: fingerprint, exception_class: event_data[:exception_class], message: event_data[:message], backtrace: serialize_backtrace(event_data[:backtrace]), context: event_data[:context] || {}, request_data: event_data[:context]&.dig(:request) || {}, environment: event_data[:environment], release_version: event_data[:release], severity: event_data[:severity] || SEVERITY_ERROR, source: event_data[:source], handled: event_data[:handled] || false, occurrence_count: 1, first_seen_at: Time.current, last_seen_at: Time.current, status: STATUS_UNRESOLVED ) end end end |
Instance Method Details
#backtrace_lines ⇒ Object
Get parsed backtrace as array
176 177 178 179 180 181 182 |
# File 'app/models/findbug/error_event.rb', line 176 def backtrace_lines return [] unless backtrace backtrace.is_a?(Array) ? backtrace : JSON.parse(backtrace) rescue JSON::ParserError backtrace.to_s.split("\n") end |
#breadcrumbs ⇒ Object
Get breadcrumbs from context
195 196 197 |
# File 'app/models/findbug/error_event.rb', line 195 def context&.dig("breadcrumbs") || context&.dig(:breadcrumbs) || [] end |
#ignore! ⇒ Object
Mark this error as ignored
166 167 168 |
# File 'app/models/findbug/error_event.rb', line 166 def ignore! update!(status: STATUS_IGNORED) end |
#reopen! ⇒ Object
Reopen a resolved/ignored error
171 172 173 |
# File 'app/models/findbug/error_event.rb', line 171 def reopen! update!(status: STATUS_UNRESOLVED) end |
#request ⇒ Object
Get request info from context
190 191 192 |
# File 'app/models/findbug/error_event.rb', line 190 def request context&.dig("request") || context&.dig(:request) end |
#resolve! ⇒ Object
Mark this error as resolved
161 162 163 |
# File 'app/models/findbug/error_event.rb', line 161 def resolve! update!(status: STATUS_RESOLVED) end |
#summary ⇒ Object
Short summary for lists
205 206 207 |
# File 'app/models/findbug/error_event.rb', line 205 def summary "#{exception_class}: #{&.truncate(100)}" end |
#tags ⇒ Object
Get tags from context
200 201 202 |
# File 'app/models/findbug/error_event.rb', line 200 def context&.dig("tags") || context&.dig(:tags) || {} end |
#user ⇒ Object
Get user info from context
185 186 187 |
# File 'app/models/findbug/error_event.rb', line 185 def user context&.dig("user") || context&.dig(:user) end |