Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,21 @@ end

### What each occurrence records

Every occurrence stores the backtrace and the error's context. Rails adds the current request or job to that context, and Solid Errors records it as:
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.

* **Requests:** the controller action (`UsersController#show`) and its params, with your `config.filter_parameters` applied.
* **Background jobs:** the job class, its id and queue, and its arguments, also filtered.
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.

```ruby
config.solid_errors.record_params = true # the request's params, with filter_parameters applied
config.solid_errors.record_arguments = true # the job's arguments, with filter_parameters applied
```

Context you set yourself takes precedence. For example, `Rails.error.set_context(params: ...)` in a `before_action` replaces the recorded params.

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.

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.

### Configuration

You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, the following configuration options are available:
Expand All @@ -181,6 +188,7 @@ You can configure Solid Errors via the Rails configuration object, under the `so
* `email_subject` - A proc that builds the subject line for email notifications. See [Email notifications](#email-notifications) for more information.
* `base_controller_class` - Specify a different controller as the base class for the Solid Errors controller. See [Authentication](#authentication) for more information.
* `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.
* `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).
* `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"]`.

### Database Configuration
Expand Down
3 changes: 3 additions & 0 deletions lib/solid_errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ module SolidErrors
}
mattr_accessor :destroy_after
mattr_accessor :ignored_errors, default: []
# Request params and job arguments carry user content that filter_parameters' key matching misses
mattr_accessor :record_params, default: false
mattr_accessor :record_arguments, default: false
mattr_writer :url_helper_name, :url_options

class << self
Expand Down
4 changes: 2 additions & 2 deletions lib/solid_errors/subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ def readable_context(context)
context = context.dup
if defined?(ActionController::Metal) && (controller = context[:controller]).is_a?(ActionController::Metal)
context[:controller] = "#{controller.class.name}##{controller.action_name}"
context[:params] ||= controller.request.filtered_parameters.except("controller", "action")
context[:params] ||= controller.request.filtered_parameters.except("controller", "action") if SolidErrors.record_params
end
if defined?(ActiveJob::Base) && (job = context[:job]).is_a?(ActiveJob::Base)
context[:job] = job.class.name
context[:job_id] ||= job.job_id
context[:queue] ||= job.queue_name
context[:arguments] ||= parameter_filter.filter(arguments: s(job.arguments))[:arguments]
context[:arguments] ||= parameter_filter.filter(arguments: s(job.arguments))[:arguments] if SolidErrors.record_arguments
end
context
end
Expand Down
29 changes: 27 additions & 2 deletions test/solid_errors/context_test.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,40 @@
require "test_helper"

class SolidErrors::ContextTest < ActionDispatch::IntegrationTest
test "an error in a request records the controller action and its filtered params" do
teardown do
SolidErrors.record_params = false
SolidErrors.record_arguments = false
end

test "an error in a request records the controller action but not its params by default" do
post "/reports", params: {message: {content: "something private"}}

context = SolidErrors::Occurrence.last.context
assert_equal "ReportsController#create", context["controller"]
assert_not context.key?("params")
end

test "an error in a job records the job but not its arguments by default" do
job = ReportJob.new("someone@example.com", {"body" => "something private"})
job.perform_now

context = SolidErrors::Occurrence.last.context
assert_equal ["ReportJob", job.job_id, "default"], context.values_at("job", "job_id", "queue")
assert_not context.key?("arguments")
end

test "an error in a request records its filtered params when the app opts in" do
SolidErrors.record_params = true

post "/reports", params: {name: "Ada", password: "secret"}

context = SolidErrors::Occurrence.last.context
assert_equal "ReportsController#create", context["controller"]
assert_equal({"name" => "Ada", "password" => "[FILTERED]"}, context["params"])
end

test "an error in a job records the job and its filtered arguments, with records as global ids" do
test "an error in a job records its filtered arguments, with records as global ids, when the app opts in" do
SolidErrors.record_arguments = true
record = record_an_error

job = ReportJob.new(record, {"password" => "secret", "retries" => 3})
Expand Down
Loading