Class: SnitchReporting::SnitchReport
- Inherits:
-
ApplicationRecord
- Object
- ApplicationRecord
- SnitchReporting::SnitchReport
- Defined in:
- app/models/snitch_reporting/snitch_report.rb
Overview
text :error text :message integer :log_level string :klass string :action text :tags datetime :first_occurrence_at datetime :last_occurrence_at bigint :occurrence_count belongs_to :assigned_to datetime :resolved_at belongs_to :resolved_by datetime :ignored_at belongs_to :ignored_by
Instance Attribute Summary collapse
-
#acting_user ⇒ Object
Returns the value of attribute acting_user.
Class Method Summary collapse
- .add_controller_data_to_report(report_data, env) ⇒ Object
- .add_leftover_objects_to_report_data(report_data, exceptions, arg_hash, arg_values) ⇒ Object
- .add_sanitized_env_information_to_report_data(report_data, env) ⇒ Object
- .debug(*args) ⇒ Object
- .error(*args) ⇒ Object
- .extract_base_variables(exceptions, arg_hash, _arg_values) ⇒ Object
- .extract_relevant_ivars(report_data, kontroller) ⇒ Object
- .fatal(*args) ⇒ Object
- .format_args(args) ⇒ Object
- .gather_report_data(env, exceptions, arg_hash, arg_values) ⇒ Object
- .get_details_from_ivar(ivar) ⇒ Object
- .ignored_kontroller_ivars ⇒ Object
- .info(*args) ⇒ Object
- .relevant_env_keys ⇒ Object
- .report(log_level, *args) ⇒ Object
- .retrieve_error_string(exception, arg_hash, arg_values) ⇒ Object
- .retrieve_or_create_existing_report(log_level, sanitized_title, sanitized_error_msg, env, exception, arg_hash) ⇒ Object
- .retrieve_report_title(exception, arg_hash, arg_values) ⇒ Object
- .santize_title(report_title) ⇒ Object
- .set_user_vars_from_source(report_data, source) ⇒ Object
- .trace_from_exception(exception) ⇒ Object
- .unknown(*args) ⇒ Object
- .warn(*args) ⇒ Object
Instance Method Summary collapse
- #add_tags(new_tags) ⇒ Object
- #ignored=(bool) ⇒ Object
- #ignored? ⇒ Boolean
- #resolved=(bool) ⇒ Object
- #resolved? ⇒ Boolean
- #tags ⇒ Object
- #tags=(new_tags) ⇒ Object
- #tracker_for_date(date = Date.today) ⇒ Object
Instance Attribute Details
#acting_user ⇒ Object
Returns the value of attribute acting_user.
17 18 19 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 17 def acting_user @acting_user end |
Class Method Details
.add_controller_data_to_report(report_data, env) ⇒ Object
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 156 def add_controller_data_to_report(report_data, env) kontroller = env&.dig(:"action_controller.instance") return if kontroller.blank? extract_relevant_ivars(report_data, kontroller) kontroller.instance_variables.each do |ivar_key| begin next if ivar_key.to_s.starts_with?("@current_") # Already extracted these in the above method next if ivar_key.in?(ignored_kontroller_ivars) ivar = kontroller.instance_variable_get(ivar_key) next if ivar.blank? report_data[ivar_key] = get_details_from_ivar(ivar) rescue StandardError => ex report_data[ivar_key] = "!-- Failed to retrieve data from variable #{ivar_key}: #{ivar.try(:class).try(:name)} (#{ex.class}) --!" end end end |
.add_leftover_objects_to_report_data(report_data, exceptions, arg_hash, arg_values) ⇒ Object
197 198 199 200 201 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 197 def add_leftover_objects_to_report_data(report_data, exceptions, arg_hash, arg_values) report_data[:exceptions] = exceptions.map { |ex| "#{ex.try(:class)}: #{ex.try(:message)}" } if exceptions.present? report_data.merge!(arg_hash) report_data[:details] = arg_values if arg_values.present? end |
.add_sanitized_env_information_to_report_data(report_data, env) ⇒ Object
203 204 205 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 203 def add_sanitized_env_information_to_report_data(report_data, env) report_data[:env] = env.slice(*relevant_env_keys) if env.present? end |
.debug(*args) ⇒ Object
57 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 57 def debug(*args); report(:debug, args); end |
.error(*args) ⇒ Object
60 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 60 def error(*args); report(:error, args); end |
.extract_base_variables(exceptions, arg_hash, _arg_values) ⇒ Object
109 110 111 112 113 114 115 116 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 109 def extract_base_variables(exceptions, arg_hash, _arg_values) exceptions << arg_hash.delete(:exception) if arg_hash[:exception].present? base_exception = exceptions.first || {} # TODO: Deal with other exceptions here klass = arg_hash[:klass] || arg_hash[:class] env = arg_hash.delete(:env) || {} [env, klass, base_exception] end |
.extract_relevant_ivars(report_data, kontroller) ⇒ Object
126 127 128 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 126 def extract_relevant_ivars(report_data, kontroller) set_user_vars_from_source(report_data, kontroller) end |
.fatal(*args) ⇒ Object
61 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 61 def fatal(*args); report(:fatal, args); end |
.format_args(args) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 94 def format_args(args) args = [args].flatten.compact exceptions = args.select { |arg| arg.is_a?(Exception) } reduced_arrays = args.select { |arg| arg.is_a?(Array) }.reduce([], :concat) reduced_values = args.select { |arg| [Array, Exception, Hash].all? { |klass| !arg.is_a?(klass) } } arg_values = reduced_arrays + reduced_values arg_hash = (args.select { |arg| arg.is_a?(Hash) }.inject(&:merge) || {}).deep_symbolize_keys # This will remove duplicate keys in the case there are multiple hashes # passed in for some reason. I don't foresee this being an issue, but # if it ever proves to be, this is the spot to refactor. [exceptions, arg_hash, arg_values] end |
.gather_report_data(env, exceptions, arg_hash, arg_values) ⇒ Object
233 234 235 236 237 238 239 240 241 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 233 def gather_report_data(env, exceptions, arg_hash, arg_values) report_data = {} add_controller_data_to_report(report_data, env) add_leftover_objects_to_report_data(report_data, exceptions, arg_hash, arg_values) add_sanitized_env_information_to_report_data(report_data, env) report_data end |
.get_details_from_ivar(ivar) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 144 def get_details_from_ivar(ivar) return ivar.class.name if ivar.class.name.include?("Abilit") case ivar.class.name when "String", "Array", "Hash" then ivar when "ActionController::Parameters" then ivar.to_json when "ActiveRecord::Relation" "#{ivar.klass} ids: [#{ivar.pluck(:id).join(', ')}]" else ivar.try(:to_data) || ivar.try(:to_json) || ivar.to_s.presence end end |
.ignored_kontroller_ivars ⇒ Object
243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 243 def ignored_kontroller_ivars [ :@_request, :@_response, :@_response, :@_lookup_context, :@_authorized, :@_main_app, :@_view_renderer, :@_view_context_class, :@_db_runtime, :@marked_for_same_origin_verification ] end |
.info(*args) ⇒ Object
58 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 58 def info(*args); report(:info, args); end |
.relevant_env_keys ⇒ Object
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 258 def relevant_env_keys [ :REQUEST_URI, :REQUEST_METHOD, :HTTP_REFERER, :HTTP_USER_AGENT, :PATH_INFO, :HTTP_CONNECTION, :REMOTE_USER, :SERVER_NAME, :QUERY_STRING, :REMOTE_HOST, :SERVER_PORT, :HTTP_ACCEPT_ENCODING, :HTTP_USER_AGENT, :SERVER_PROTOCOL, :HTTP_CACHE_CONTROL, :HTTP_ACCEPT_LANGUAGE, :HTTP_HOST, :REMOTE_ADDR, :SERVER_SOFTWARE, :HTTP_KEEP_ALIVE, :HTTP_REFERER, :HTTP_ACCEPT_CHARSET, :GATEWAY_INTERFACE, :HTTP_ACCEPT, :HTTP_COOKIE ] end |
.report(log_level, *args) ⇒ Object
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 64 def report(log_level, *args) exceptions, arg_hash, arg_values = format_args(args) env, klass, base_exception = extract_base_variables(exceptions, arg_hash, arg_values) always_notify = arg_hash.delete(:always_notify) report_title = retrieve_report_title(base_exception, arg_hash, arg_values) report_error = retrieve_error_string(base_exception, arg_hash, arg_values) report = retrieve_or_create_existing_report(log_level, santize_title(report_title), santize_title(report_error), env, base_exception, arg_hash) return SnitchReporting::SnitchReport.error("Failed to save report.", report.errors.) unless report.persisted? report_data = gather_report_data(env, exceptions, arg_hash, arg_values) occurrence = report.occurrences.create( http_method: env[:REQUEST_METHOD], url: env[:REQUEST_URI], user_agent: env[:HTTP_USER_AGENT], backtrace: trace_from_exception(base_exception), context: report_data, params: env&.dig(:"action_controller.instance")&.params&.permit!&.to_h&.except(:action, :controller), headers: env&.reject { |k, v| k.to_s.include?(".") || !v.is_a?(String) }, always_notify: always_notify ) return SnitchReporting::SnitchReport.error("Failed to save occurrence.", occurrence.errors.) unless occurrence.persisted? occurrence rescue StandardError => ex env ||= {} binding.pry SnitchReporting::SnitchReport.fatal("Failed to create report. (#{ex.class})", env.to_s, ex.to_s) end |
.retrieve_error_string(exception, arg_hash, arg_values) ⇒ Object
188 189 190 191 192 193 194 195 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 188 def retrieve_error_string(exception, arg_hash, arg_values) report_error ||= arg_values&.find { |arg_value| arg_value.is_a?(String) } report_error ||= (arg_hash[:klass] || arg_hash[:class]).presence report_error ||= arg_hash&.first.to_s.presence report_error ||= trace_from_exception(exception).find { |row| row.include?("/app/") } report_error ||= exception.try(:class).presence report_error end |
.retrieve_or_create_existing_report(log_level, sanitized_title, sanitized_error_msg, env, exception, arg_hash) ⇒ Object
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 215 def retrieve_or_create_existing_report(log_level, sanitized_title, sanitized_error_msg, env, exception, arg_hash) report_identifiable_data = { error: sanitized_title.presence, message: sanitized_error_msg.presence, log_level: log_level, klass: env&.dig(:"action_controller.instance").try(:class).to_s.split("::").last&.gsub("Controller", ""), action: env&.dig(:"action_controller.instance").try(:action_name) } report = find_by(report_identifiable_data) if sanitized_title.present? # Not using find or create because the slug might be `nil`- in these # cases, we want to create a new report so that we don't falsely group # unrelated errors together. report ||= create(report_identifiable_data) report.(arg_hash.delete(:tags)) report end |
.retrieve_report_title(exception, arg_hash, arg_values) ⇒ Object
177 178 179 180 181 182 183 184 185 186 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 177 def retrieve_report_title(exception, arg_hash, arg_values) report_title = arg_hash[:title].presence report_title ||= exception.try(:message).presence report_title ||= exception.try(:body).presence report_title ||= arg_values&.find { |arg_value| arg_value.is_a?(String) } report_title ||= (arg_hash[:klass] || arg_hash[:class]).presence report_title ||= trace_from_exception(exception).find { |row| row.include?("/app/") } report_title ||= exception.try(:class).presence report_title end |
.santize_title(report_title) ⇒ Object
207 208 209 210 211 212 213 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 207 def santize_title(report_title) regex_find_numbers_and_words_with_numbers = /\w*\d[\d\w]*/ # We remove all numbers and words that have numbers in them so that we can # more easily group similar errors together, but often times errors have # unique ids, so we strip those out. report_title.to_s.gsub(regex_find_numbers_and_words_with_numbers, "").presence end |
.set_user_vars_from_source(report_data, source) ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 130 def set_user_vars_from_source(report_data, source) user_vars = source.try(:instance_variables)&.select { |ivar_key| ivar_key.to_s.starts_with?("@current_") } || [] user_vars.each do |user_var| begin ivar = source.instance_variable_get(user_var) next if ivar.blank? report_data[user_var] = get_details_from_ivar(ivar) rescue StandardError => ex report_data[user_var] = "!-- Failed to retrieve data from user #{user_var}: #{ivar.try(:class).try(:name)} (#{ex.class}) --!" end end end |
.trace_from_exception(exception) ⇒ Object
118 119 120 121 122 123 124 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 118 def trace_from_exception(exception) trace = exception.try(:backtrace).presence return trace if trace.present? trace = caller.dup trace.shift until trace.first.exclude?("snitch_reporting") trace end |
.unknown(*args) ⇒ Object
62 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 62 def unknown(*args); report(:unknown, args); end |
.warn(*args) ⇒ Object
59 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 59 def warn(*args); report(:warn, args); end |
Instance Method Details
#add_tags(new_tags) ⇒ Object
301 302 303 304 305 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 301 def () return if .blank? update(tags: ( + []).compact.flatten.uniq) end |
#ignored=(bool) ⇒ Object
310 311 312 313 314 315 316 317 318 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 310 def ignored=(bool) if bool.to_s.downcase.in?(["t", "true", "1", "y", "yes"]) self.ignored_at ||= Time.current # self.ignored_by ||= acting_user else self.ignored_at = nil # self.ignored_by = nil end end |
#ignored? ⇒ Boolean
308 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 308 def ignored?; ignored_at?; end |
#resolved=(bool) ⇒ Object
320 321 322 323 324 325 326 327 328 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 320 def resolved=(bool) if bool.to_s.downcase.in?(["t", "true", "1", "y", "yes"]) self.resolved_at ||= Time.current # self.resolved_by ||= acting_user else self.resolved_at = nil # self.resolved_by = nil end end |
#resolved? ⇒ Boolean
307 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 307 def resolved?; resolved_at?; end |
#tags ⇒ Object
293 294 295 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 293 def (super || []).uniq end |
#tags=(new_tags) ⇒ Object
297 298 299 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 297 def () super((.try(:to_a) || []).compact.flatten.uniq) end |
#tracker_for_date(date = Date.today) ⇒ Object
289 290 291 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 289 def tracker_for_date(date=Date.today) trackers.tracker_for_date(date) end |