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_or_create_existing_report(log_level, sanitized_title, env, exception, arg_hash) ⇒ Object
- .retrieve_report_title(exception, arg_hash) ⇒ 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
-
#assigned_to ⇒ Object
belongs_to :assigned_to.
- #ignored=(bool) ⇒ Object
- #ignored? ⇒ Boolean
- #resolved=(bool) ⇒ Object
- #resolved? ⇒ Boolean
- #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
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 140 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
171 172 173 174 175 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 171 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
177 178 179 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 177 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
43 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 43 def debug(*args); report(:debug, args); end |
.error(*args) ⇒ Object
46 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 46 def error(*args); report(:error, args); end |
.extract_base_variables(exceptions, arg_hash, _arg_values) ⇒ Object
93 94 95 96 97 98 99 100 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 93 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
110 111 112 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 110 def extract_relevant_ivars(report_data, kontroller) set_user_vars_from_source(report_data, kontroller) end |
.fatal(*args) ⇒ Object
47 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 47 def fatal(*args); report(:fatal, args); end |
.format_args(args) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 78 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
205 206 207 208 209 210 211 212 213 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 205 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
128 129 130 131 132 133 134 135 136 137 138 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 128 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
215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 215 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
44 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 44 def info(*args); report(:info, args); end |
.relevant_env_keys ⇒ Object
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 230 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
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 50 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) report = retrieve_or_create_existing_report(log_level, santize_title(report_title), 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 ||= {} SnitchReporting::SnitchReport.fatal("Failed to create report. (#{ex.class})", env, ex) end |
.retrieve_or_create_existing_report(log_level, sanitized_title, env, exception, arg_hash) ⇒ Object
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 189 def retrieve_or_create_existing_report(log_level, sanitized_title, env, exception, arg_hash) report_identifiable_data = { error: (exception.try(:class) || sanitized_title.presence).to_s, message: sanitized_title.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) end |
.retrieve_report_title(exception, arg_hash) ⇒ Object
161 162 163 164 165 166 167 168 169 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 161 def retrieve_report_title(exception, arg_hash) report_title = arg_hash[:title].presence report_title ||= exception.try(:message).presence report_title ||= exception.try(:body).presence report_title ||= exception.try(:class).presence report_title ||= (arg_hash[:klass] || arg_hash[:class]).presence report_title ||= trace_from_exception(exception).find { |row| row.include?("/app/") } report_title end |
.santize_title(report_title) ⇒ Object
181 182 183 184 185 186 187 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 181 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
114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 114 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
102 103 104 105 106 107 108 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 102 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
48 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 48 def unknown(*args); report(:unknown, args); end |
.warn(*args) ⇒ Object
45 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 45 def warn(*args); report(:warn, args); end |
Instance Method Details
#assigned_to ⇒ Object
belongs_to :assigned_to
23 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 23 def assigned_to; end |
#ignored=(bool) ⇒ Object
268 269 270 271 272 273 274 275 276 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 268 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
266 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 266 def ignored?; ignored_at?; end |
#resolved=(bool) ⇒ Object
278 279 280 281 282 283 284 285 286 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 278 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
265 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 265 def resolved?; resolved_at?; end |
#tracker_for_date(date = Date.today) ⇒ Object
261 262 263 |
# File 'app/models/snitch_reporting/snitch_report.rb', line 261 def tracker_for_date(date=Date.today) trackers.tracker_for_date(date) end |