solid_errors 0.8.0 → 0.9.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11cd9651413ea528780669e5c01abec456118a34a70c93042a85a471c0d1fd07
4
- data.tar.gz: 39f16c51d5bb62144c8b1b138c18fc6fa4dff4e601c635ed3046f9d37f1d960a
3
+ metadata.gz: 1d793218b26cf26f6f6d6a4a87f8e14088cf555739d5ea007894a75cef13646a
4
+ data.tar.gz: a4bfc87e5a230db9ba392991ac37624ea58e5027a487260e9c3982eb13b4ef8c
5
5
  SHA512:
6
- metadata.gz: 39341060f4ee0e9bfdc009fedd2176fb9bb7ae6c60aaddc175f8e4449d2274f7014f6df71b683865a94eb6c779700c4b0356a0addf2c435a40162027a45b5bec
7
- data.tar.gz: 87f876f3bdcdf11dcdeac4ded4c6ae873c3f5a53c87aebe454ce925416a66ad19dc37d4984b9a6c326fe79e2c1bab16f6c20f6feb9f79381a70965e35d058030
6
+ metadata.gz: 230583bf528c6765a6205fcfbb1981d935fb866c28126fec475800dc0a11aab1e1a0aaad4a03f631e5b58a166d3d4a805f288b0b4e270ae95a949b0dd4bca7dc
7
+ data.tar.gz: 903a70c2663cf40335e5697ee882728e8aeb56e34519be322fa78be3a3505a2255f16eb78552ad0d7ba2d5bd6602ff8204beeaf63041a0b1b5c65bbf998d32aa
data/README.md CHANGED
@@ -79,6 +79,9 @@ production:
79
79
 
80
80
  Then run `db:prepare` in production to ensure the database is created and the schema is loaded.
81
81
 
82
+ > [!WARNING]
83
+ > Use `db:prepare`, not `db:create` followed by `db:migrate`. Solid Errors ships a schema rather than migrations, so `db:migrate` leaves the errors database empty and then dumps that empty schema over `db/errors_schema.rb`. If that has happened, copy the schema back from [the install template](lib/generators/solid_errors/install/templates/db/errors_schema.rb) and run `db:prepare` again.
84
+
82
85
  Then mount the engine in your `config/routes.rb` file:
83
86
  ```ruby
84
87
  authenticate :user, -> (user) { user.admin? } do
@@ -151,6 +154,24 @@ Rails.error.handle(context: { user_id: user.id }, severity: :info) do
151
154
  end
152
155
  ```
153
156
 
157
+
158
+ ### What each occurrence records
159
+
160
+ Every occurrence stores the backtrace and the error's context. Rails adds the current request or job to that context, and Solid Errors records which one it was: the controller action (`UsersController#show`), or the job's class, id and queue.
161
+
162
+ Request params and job arguments are not recorded unless you opt in, because they often carry user content that `config.filter_parameters` can't catch: it matches keys, so positional job arguments, mail delivery jobs (the mail's recipients and body) and webhook payloads would be stored and emailed as they are.
163
+
164
+ ```ruby
165
+ config.solid_errors.record_params = true # the request's params, with filter_parameters applied
166
+ config.solid_errors.record_arguments = true # the job's arguments, with filter_parameters applied
167
+ ```
168
+
169
+ Context you set yourself takes precedence. For example, `Rails.error.set_context(params: ...)` in a `before_action` replaces the recorded params.
170
+
171
+ Any Active Record object in the context is stored as its [GlobalID](https://github.com/rails/globalid) (`gid://my-app/User/1`), so you can tell which record was involved. Add your own context with `Rails.error.set_context(...)` or the `context:` option shown above.
172
+
173
+ Errors raised in background jobs reach Solid Errors when your job backend runs jobs inside the Rails executor, as Solid Queue, Sidekiq and GoodJob do. You don't need any extra setup for this.
174
+
154
175
  ### Configuration
155
176
 
156
177
  You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, the following configuration options are available:
@@ -164,8 +185,10 @@ You can configure Solid Errors via the Rails configuration object, under the `so
164
185
  * `email_to` - The email address(es) to send a notification to. See [Email notifications](#email-notifications) for more information.
165
186
  * `email_every_occurrence` - Whether to email every occurrence (the default), or only new errors and resolved errors that occur again. See [Email notifications](#email-notifications) for more information.
166
187
  * `email_subject_prefix` - Prefix added to the subject line for email notifications. See [Email notifications](#email-notifications) for more information.
188
+ * `email_subject` - A proc that builds the subject line for email notifications. See [Email notifications](#email-notifications) for more information.
167
189
  * `base_controller_class` - Specify a different controller as the base class for the Solid Errors controller. See [Authentication](#authentication) for more information.
168
190
  * `destroy_after` - If set, Solid Errors will periodically destroy resolved records that are older than the value specified. See [Automatically destroying old records](#automatically-destroying-old-records) for more information.
191
+ * `record_params` / `record_arguments` - Whether to record request params and job arguments with each occurrence. Both default to `false`. See [What each occurrence records](#what-each-occurrence-records).
169
192
  * `ignored_errors` - Error classes, or their names, to never record, in addition to the built-in list in `SolidErrors::Subscriber::IGNORED_ERRORS`. For example, `config.solid_errors.ignored_errors = ["ActiveStorage::FileNotFoundError"]`.
170
193
 
171
194
  ### Database Configuration
@@ -230,6 +253,10 @@ config.solid_errors.enabled = false
230
253
 
231
254
  When disabled, Solid Errors will not subscribe to the Rails error reporter and no errors will be recorded.
232
255
 
256
+ Disable it in every environment that loads the gem but has no errors database, such as development or test. Otherwise Solid Errors records errors to the primary database, fails with `Could not find table 'solid_errors'`, and in development and test that failure can hide the error you were looking for.
257
+
258
+ Configuration belongs in `config/environments/<environment>.rb`, not in `config/<environment>.rb`, which Rails does not load.
259
+
233
260
  #### Authentication
234
261
 
235
262
  Solid Errors does not restrict access out of the box. You must secure the dashboard yourself. However, it does provide basic HTTP authentication that can be used with basic authentication or Devise. All you need to do is setup a username and password.
@@ -272,14 +299,14 @@ Solid Errors _can_ send email notifications whenever an error occurs, if your ap
272
299
 
273
300
  There are two ways to configure email notifications. First, you can use environment variables:
274
301
 
275
- ```ruby
276
- ENV["SOLIDERRORS_SEND_EMAILS"] = true # defaults to false
277
- ENV["SOLIDERRORS_EMAIL_FROM"] = "errors@myapp.com" # defaults to "solid_errors@noreply.com"
278
- ENV["SOLIDERRORS_EMAIL_TO"] = "devs@myapp.com" # no default, must be set
279
- ENV["SOLIDERRORS_EMAIL_SUBJECT_PREFIX"] = "[Application name][Environment]" # no default, optional
302
+ ```sh
303
+ SOLIDERRORS_SEND_EMAILS=true # defaults to false
304
+ SOLIDERRORS_EMAIL_FROM=errors@myapp.com # defaults to solid_errors@noreply.com
305
+ SOLIDERRORS_EMAIL_TO=devs@myapp.com # no default, must be set
306
+ SOLIDERRORS_EMAIL_SUBJECT_PREFIX="[Application name][Environment]" # no default, optional
280
307
  ```
281
308
 
282
- Second, you can set the values via the configuration object:
309
+ Second, you can set the values via the configuration object, which takes precedence over the environment variables:
283
310
 
284
311
  ```ruby
285
312
  # Set authentication credentials and optional subject prefix for Solid Errors
@@ -297,6 +324,32 @@ To be emailed only when an error is new, or when a resolved error occurs again,
297
324
  config.solid_errors.email_every_occurrence = false # defaults to true
298
325
  ```
299
326
 
327
+ The subject names the error, and says when a resolved error has occurred again (`🔥 TimeoutError (reopened)`). To build it yourself, set `email_subject` to a proc. It is called with the error, the occurrence, and `reason`: `:created` or `:reopened` when the occurrence opened its error, `nil` for any other. `email_subject_prefix` is still added in front of what it returns.
328
+
329
+ ```ruby
330
+ config.solid_errors.email_subject = ->(error:, occurrence:, reason:) {
331
+ # Counting up to this occurrence, rather than all of them, gives the same answer however late the email is sent
332
+ count = error.occurrences.where(id: ..occurrence.id).count
333
+ subject = "#{error.severity_emoji} #{error.exception_class}"
334
+
335
+ case reason
336
+ when :reopened then "🔄 REOPENED: #{subject}"
337
+ when :resolved then "✅ RESOLVED: #{error.exception_class}"
338
+ else count.in?([10, 100, 1_000]) ? "#{subject} (#{count.ordinalize} occurrence)" : subject
339
+ end
340
+ }
341
+ ```
342
+
343
+ The error email can also be sent for events of your own, such as an error being resolved or occurring unusually often. Whatever reason you pass reaches `email_subject`, as `:resolved` does in the example above:
344
+
345
+ ```ruby
346
+ # config/initializers/solid_errors.rb
347
+ ActiveSupport::Notifications.subscribe("error_resolved.solid_errors") do |event|
348
+ occurrence = event.payload[:error].occurrences.last
349
+ SolidErrors::ErrorMailer.error_occurred(occurrence, :resolved).deliver_later if occurrence && SolidErrors.send_emails?
350
+ end
351
+ ```
352
+
300
353
  #### Lifecycle events
301
354
 
302
355
  Solid Errors publishes [Active Support notifications](https://guides.rubyonrails.org/active_support_instrumentation.html) once each change is committed, so you can send errors to Slack, a webhook or anywhere else:
@@ -330,6 +383,21 @@ config.solid_errors.destroy_after = 30.days
330
383
  config.solid_errors.destroy_after = 6.months
331
384
  ```
332
385
 
386
+ #### Checking the schema after an upgrade
387
+
388
+ When a release changes the schema, [UPGRADE.md](UPGRADE.md) describes the migration. To have your test suite fail until you've applied it, assert that the errors database has everything the installed version uses:
389
+
390
+ ```ruby
391
+ # test/solid_errors_test.rb
392
+ require "test_helper"
393
+
394
+ class SolidErrorsTest < ActiveSupport::TestCase
395
+ test "the errors database is up to date" do
396
+ assert SolidErrors.migrated?
397
+ end
398
+ end
399
+ ```
400
+
333
401
  ### Examples
334
402
 
335
403
  There are only two screens in the dashboard.
@@ -412,6 +480,11 @@ You can always take control of the views by creating your own views and/or parti
412
480
  </html>
413
481
  ```
414
482
 
483
+
484
+ ### Locales
485
+
486
+ The dashboard is written in English and renders in English whenever your app allows the `:en` locale. If `config.i18n.available_locales` leaves out `:en`, the dashboard renders in your app's locale instead, and you will need to provide the `datetime.distance_in_words.short` translations from [`config/locales/en.yml`](config/locales/en.yml) in that locale.
487
+
415
488
  ## Sponsors
416
489
 
417
490
  <p align="center">
@@ -1,6 +1,6 @@
1
1
  module SolidErrors
2
2
  class ErrorsController < ApplicationController
3
- around_action :force_english_locale!
3
+ around_action :use_english_when_available
4
4
 
5
5
  before_action :set_error, only: %i[show update destroy]
6
6
 
@@ -53,7 +53,10 @@ module SolidErrors
53
53
  @error = Error.find(params[:id])
54
54
  end
55
55
 
56
- def force_english_locale!(&action)
56
+ # The dashboard's strings ship in English only; apps that don't allow :en keep their own locale
57
+ def use_english_when_available(&action)
58
+ return action.call unless I18n.locale_available?(:en)
59
+
57
60
  I18n.with_locale(:en, &action)
58
61
  end
59
62
 
@@ -12,13 +12,15 @@ module SolidErrors
12
12
  helper_method :occurrence_url
13
13
  end
14
14
 
15
- def error_occurred(occurrence)
15
+ # reason is passed on to SolidErrors.email_subject: :created or :reopened when the occurrence
16
+ # opened its error, or whatever an app sending this email for its own events passes
17
+ def error_occurred(occurrence, reason = nil)
16
18
  unless defined?(ActionMailer::Base)
17
19
  raise "ActionMailer is not available. Make sure that you require \"action_mailer/railtie\" in application.rb"
18
20
  end
19
21
  @occurrence = occurrence
20
22
  @error = occurrence.error
21
- subject = "#{@error.severity_emoji} #{@error.exception_class}"
23
+ subject = SolidErrors.email_subject.call(error: @error, occurrence: @occurrence, reason: reason&.to_sym)
22
24
  if SolidErrors.email_subject_prefix.present?
23
25
  subject = [SolidErrors.email_subject_prefix, subject].join(" ").squish!
24
26
  end
@@ -35,7 +35,8 @@ module SolidErrors
35
35
  end
36
36
 
37
37
  def send_email
38
- ErrorMailer.error_occurred(self).deliver_later
38
+ # Passed along rather than read off the occurrence, which the mailer job loads afresh without it
39
+ ErrorMailer.error_occurred(self, opens_error).deliver_later
39
40
  end
40
41
 
41
42
  def clear_resolved_errors
@@ -1190,4 +1190,189 @@
1190
1190
  padding-right: 0.75rem
1191
1191
  }
1192
1192
  }
1193
+
1194
+ /*
1195
+ Dark mode follows the operating system. Rather than adding `dark:` variants to
1196
+ the markup, it remaps the light utilities above to their dark counterparts, so
1197
+ the views and the classes the backtrace toggle swaps stay the same.
1198
+ */
1199
+
1200
+ :root{
1201
+ color-scheme: light dark
1202
+ }
1203
+
1204
+ @media (prefers-color-scheme: dark){
1205
+ *, ::before, ::after{
1206
+ border-color: #374151
1207
+ }
1208
+
1209
+ body{
1210
+ background-color: rgb(17 24 39);
1211
+ color: rgb(229 231 235)
1212
+ }
1213
+
1214
+ .divide-gray-300 > :not([hidden]) ~ :not([hidden]){
1215
+ --tw-divide-opacity: 1;
1216
+ border-color: rgb(55 65 81 / var(--tw-divide-opacity))
1217
+ }
1218
+
1219
+ .border-black{
1220
+ --tw-border-opacity: 1;
1221
+ border-color: rgb(209 213 219 / var(--tw-border-opacity))
1222
+ }
1223
+
1224
+ .border-blue-500{
1225
+ --tw-border-opacity: 1;
1226
+ border-color: rgb(96 165 250 / var(--tw-border-opacity))
1227
+ }
1228
+
1229
+ .border-gray-300{
1230
+ --tw-border-opacity: 1;
1231
+ border-color: rgb(75 85 99 / var(--tw-border-opacity))
1232
+ }
1233
+
1234
+ .border-red-500{
1235
+ --tw-border-opacity: 1;
1236
+ border-color: rgb(248 113 113 / var(--tw-border-opacity))
1237
+ }
1238
+
1239
+ .bg-gray-100{
1240
+ --tw-bg-opacity: 1;
1241
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity))
1242
+ }
1243
+
1244
+ .bg-green-50{
1245
+ --tw-bg-opacity: 1;
1246
+ background-color: rgb(5 46 22 / var(--tw-bg-opacity))
1247
+ }
1248
+
1249
+ .bg-red-50{
1250
+ --tw-bg-opacity: 1;
1251
+ background-color: rgb(69 10 10 / var(--tw-bg-opacity))
1252
+ }
1253
+
1254
+ .bg-white{
1255
+ --tw-bg-opacity: 1;
1256
+ background-color: rgb(17 24 39 / var(--tw-bg-opacity))
1257
+ }
1258
+
1259
+ .text-black{
1260
+ --tw-text-opacity: 1;
1261
+ color: rgb(243 244 246 / var(--tw-text-opacity))
1262
+ }
1263
+
1264
+ .bg-blue-100{
1265
+ background-color: rgb(30 58 138 / 0.5)
1266
+ }
1267
+
1268
+ .bg-red-100{
1269
+ background-color: rgb(127 29 29 / 0.5)
1270
+ }
1271
+
1272
+ .bg-yellow-100{
1273
+ background-color: rgb(113 63 18 / 0.5)
1274
+ }
1275
+
1276
+ .bg-green-100{
1277
+ background-color: rgb(20 83 45 / 0.5)
1278
+ }
1279
+
1280
+ .bg-violet-100{
1281
+ background-color: rgb(76 29 149 / 0.5)
1282
+ }
1283
+
1284
+ .text-blue-500{
1285
+ --tw-text-opacity: 1;
1286
+ color: rgb(96 165 250 / var(--tw-text-opacity))
1287
+ }
1288
+
1289
+ .text-gray-500{
1290
+ --tw-text-opacity: 1;
1291
+ color: rgb(156 163 175 / var(--tw-text-opacity))
1292
+ }
1293
+
1294
+ .text-gray-900{
1295
+ --tw-text-opacity: 1;
1296
+ color: rgb(243 244 246 / var(--tw-text-opacity))
1297
+ }
1298
+
1299
+ .text-green-500{
1300
+ --tw-text-opacity: 1;
1301
+ color: rgb(74 222 128 / var(--tw-text-opacity))
1302
+ }
1303
+
1304
+ .text-red-500{
1305
+ --tw-text-opacity: 1;
1306
+ color: rgb(248 113 113 / var(--tw-text-opacity))
1307
+ }
1308
+
1309
+ .text-blue-800{
1310
+ --tw-text-opacity: 1;
1311
+ color: rgb(191 219 254 / var(--tw-text-opacity))
1312
+ }
1313
+
1314
+ .text-red-800{
1315
+ --tw-text-opacity: 1;
1316
+ color: rgb(254 202 202 / var(--tw-text-opacity))
1317
+ }
1318
+
1319
+ .text-yellow-800{
1320
+ --tw-text-opacity: 1;
1321
+ color: rgb(254 240 138 / var(--tw-text-opacity))
1322
+ }
1323
+
1324
+ .text-green-800{
1325
+ --tw-text-opacity: 1;
1326
+ color: rgb(187 247 208 / var(--tw-text-opacity))
1327
+ }
1328
+
1329
+ .text-violet-800{
1330
+ --tw-text-opacity: 1;
1331
+ color: rgb(221 214 254 / var(--tw-text-opacity))
1332
+ }
1333
+
1334
+ .even\:bg-gray-50:nth-child(even){
1335
+ background-color: rgb(31 41 55 / 0.5)
1336
+ }
1337
+
1338
+ .hover\:bg-gray-50:hover{
1339
+ --tw-bg-opacity: 1;
1340
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity))
1341
+ }
1342
+
1343
+ .hover\:ring-blue-200:hover{
1344
+ --tw-ring-opacity: 1;
1345
+ --tw-ring-color: rgb(30 58 138 / var(--tw-ring-opacity))
1346
+ }
1347
+
1348
+ .hover\:ring-gray-200:hover{
1349
+ --tw-ring-opacity: 1;
1350
+ --tw-ring-color: rgb(55 65 81 / var(--tw-ring-opacity))
1351
+ }
1352
+
1353
+ .hover\:ring-red-200:hover{
1354
+ --tw-ring-opacity: 1;
1355
+ --tw-ring-color: rgb(127 29 29 / var(--tw-ring-opacity))
1356
+ }
1357
+
1358
+ .hover\:bg-black:hover{
1359
+ --tw-bg-opacity: 1;
1360
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity))
1361
+ }
1362
+
1363
+ .hover\:text-white:hover{
1364
+ --tw-text-opacity: 1;
1365
+ color: rgb(17 24 39 / var(--tw-text-opacity))
1366
+ }
1367
+
1368
+ .hover\:text-gray-700:hover{
1369
+ --tw-text-opacity: 1;
1370
+ color: rgb(229 231 235 / var(--tw-text-opacity))
1371
+ }
1372
+
1373
+ .hover\:border-gray-300:hover{
1374
+ --tw-border-opacity: 1;
1375
+ border-color: rgb(107 114 128 / var(--tw-border-opacity))
1376
+ }
1377
+ }
1193
1378
  </style>
@@ -1,7 +1,6 @@
1
1
  <html>
2
2
  <head>
3
3
  <title>Solid Errors | <%= @error.severity_emoji %> <%= @error.exception_class %></title>
4
- <%= render "layouts/solid_errors/style" %>
5
4
  <style>
6
5
  body {
7
6
  padding: 20px;
@@ -10,7 +10,7 @@
10
10
  <p class="text-right">
11
11
  <% page_count = page.count %>
12
12
  <% if page_count.zero? %>
13
- No posts found
13
+ No occurrences found
14
14
  <% elsif page.pages == 1 %>
15
15
  <strong><%= page_count %></strong> total
16
16
  <% else %>
@@ -53,7 +53,9 @@
53
53
  collection: occurrences, as: :occurrence %>
54
54
  <% else %>
55
55
  <div class="flex items-center justify-center gap-2">
56
- <%= bootstrap_svg "list-ul" %>
56
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-ul" viewBox="0 0 16 16">
57
+ <path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
58
+ </svg>
57
59
  <span class="text-2xl">No occurrences yet&hellip;</span>
58
60
  </div>
59
61
  <% end %>
@@ -49,6 +49,10 @@ module SolidErrors
49
49
  when String
50
50
  sanitize_string(data)
51
51
  else # all other objects
52
+ if (global_id = global_id(data))
53
+ return global_id
54
+ end
55
+
52
56
  klass = data.class
53
57
 
54
58
  begin
@@ -67,6 +71,15 @@ module SolidErrors
67
71
 
68
72
  attr_reader :max_depth
69
73
 
74
+ # A saved record is identified by its GlobalID (gid://app/User/1) instead of just its class
75
+ def global_id(object)
76
+ return unless object.respond_to?(:to_global_id)
77
+
78
+ object.to_global_id.to_s
79
+ rescue
80
+ nil
81
+ end
82
+
70
83
  def basic_object?(object)
71
84
  object.respond_to?(:to_s)
72
85
  false
@@ -42,11 +42,31 @@ module SolidErrors
42
42
  SolidErrors::Occurrence.create(
43
43
  error_id: record.id,
44
44
  backtrace: error.backtrace&.join("\n"),
45
- context: s(context),
45
+ context: s(readable_context(context)),
46
46
  opens_error: opens_error
47
47
  )
48
48
  end
49
49
 
50
+ # Rails puts the current controller or job in the context; keep what identifies the work, not the object
51
+ def readable_context(context)
52
+ context = context.dup
53
+ if defined?(ActionController::Metal) && (controller = context[:controller]).is_a?(ActionController::Metal)
54
+ context[:controller] = "#{controller.class.name}##{controller.action_name}"
55
+ context[:params] ||= controller.request.filtered_parameters.except("controller", "action") if SolidErrors.record_params
56
+ end
57
+ if defined?(ActiveJob::Base) && (job = context[:job]).is_a?(ActiveJob::Base)
58
+ context[:job] = job.class.name
59
+ context[:job_id] ||= job.job_id
60
+ context[:queue] ||= job.queue_name
61
+ context[:arguments] ||= parameter_filter.filter(arguments: s(job.arguments))[:arguments] if SolidErrors.record_arguments
62
+ end
63
+ context
64
+ end
65
+
66
+ def parameter_filter
67
+ ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters)
68
+ end
69
+
50
70
  def s(data)
51
71
  Sanitizer.sanitize(data)
52
72
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidErrors
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.1"
5
5
  end
data/lib/solid_errors.rb CHANGED
@@ -11,13 +11,19 @@ module SolidErrors
11
11
  mattr_accessor :base_controller_class, default: "::ActionController::Base"
12
12
  mattr_writer :username
13
13
  mattr_writer :password
14
- mattr_accessor :send_emails, default: false
15
- mattr_accessor :email_from, default: "solid_errors@noreply.com"
16
- mattr_accessor :email_to
17
- mattr_accessor :email_subject_prefix
14
+ mattr_writer :send_emails, :email_from, :email_to, :email_subject_prefix, default: nil
18
15
  mattr_accessor :email_every_occurrence, default: true
16
+ # Builds the error email's subject, before email_subject_prefix. reason is :created or :reopened when
17
+ # the occurrence opened its error, nil for any other occurrence, or the app's own reason when it sends the email itself
18
+ mattr_accessor :email_subject, default: ->(error:, occurrence:, reason:) {
19
+ subject = "#{error.severity_emoji} #{error.exception_class}"
20
+ (reason == :reopened) ? "#{subject} (reopened)" : subject
21
+ }
19
22
  mattr_accessor :destroy_after
20
23
  mattr_accessor :ignored_errors, default: []
24
+ # Request params and job arguments carry user content that filter_parameters' key matching misses
25
+ mattr_accessor :record_params, default: false
26
+ mattr_accessor :record_arguments, default: false
21
27
  mattr_writer :url_helper_name, :url_options
22
28
 
23
29
  class << self
@@ -33,6 +39,34 @@ module SolidErrors
33
39
  @password ||= ENV["SOLIDERRORS_PASSWORD"] || @@password
34
40
  end
35
41
 
42
+ # Each email setting falls back to its SOLIDERRORS_* environment variable when not configured
43
+ def send_emails
44
+ @@send_emails.nil? ? ActiveModel::Type::Boolean.new.cast(ENV["SOLIDERRORS_SEND_EMAILS"]) || false : @@send_emails
45
+ end
46
+
47
+ def email_from
48
+ @@email_from || ENV["SOLIDERRORS_EMAIL_FROM"] || "solid_errors@noreply.com"
49
+ end
50
+
51
+ def email_to
52
+ @@email_to || ENV["SOLIDERRORS_EMAIL_TO"]
53
+ end
54
+
55
+ def email_subject_prefix
56
+ @@email_subject_prefix || ENV["SOLIDERRORS_EMAIL_SUBJECT_PREFIX"]
57
+ end
58
+
59
+ # Whether the errors database has every table and column this version reads and writes.
60
+ # Assert it in your test suite to catch a schema change you haven't applied yet.
61
+ def migrated?
62
+ {
63
+ SolidErrors::Error => %w[exception_class message severity source resolved_at fingerprint created_at updated_at],
64
+ SolidErrors::Occurrence => %w[error_id backtrace context created_at updated_at]
65
+ }.all? do |model, columns|
66
+ model.table_exists? && (columns - model.column_names).empty?
67
+ end
68
+ end
69
+
36
70
  def send_emails?
37
71
  send_emails && email_to.present? && !ActiveSupport::IsolatedExecutionState[:solid_errors_without_emails]
38
72
  end
@@ -47,11 +81,11 @@ module SolidErrors
47
81
  end
48
82
 
49
83
  def url_helper_name
50
- @url_helper_name ||= detect_url_helper_name
84
+ @@url_helper_name ||= detect_url_helper_name
51
85
  end
52
86
 
53
87
  def url_options
54
- @url_options ||= detect_url_options
88
+ @@url_options ||= detect_url_options
55
89
  end
56
90
 
57
91
  private
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_errors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Margheim