exception_hunter 0.4.2 → 1.1.0
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 +4 -4
- data/README.md +86 -2
- data/app/assets/stylesheets/exception_hunter/base.css +4 -0
- data/app/assets/stylesheets/exception_hunter/errors.css +25 -0
- data/app/controllers/exception_hunter/errors_controller.rb +2 -0
- data/app/controllers/exception_hunter/ignored_errors_controller.rb +25 -0
- data/app/helpers/exception_hunter/application_helper.rb +22 -0
- data/app/jobs/exception_hunter/async_logging_job.rb +13 -0
- data/app/jobs/exception_hunter/send_notification_job.rb +15 -0
- data/app/models/exception_hunter/error.rb +5 -1
- data/app/models/exception_hunter/error_group.rb +1 -1
- data/app/presenters/exception_hunter/dashboard_presenter.rb +5 -3
- data/app/presenters/exception_hunter/error_group_presenter.rb +2 -1
- data/app/views/exception_hunter/errors/_error_row.erb +19 -11
- data/app/views/exception_hunter/errors/_last_7_days_errors_table.erb +1 -5
- data/app/views/exception_hunter/errors/index.html.erb +17 -3
- data/app/views/layouts/exception_hunter/application.html.erb +3 -1
- data/config/rails_best_practices.yml +1 -1
- data/config/routes.rb +2 -0
- data/lib/exception_hunter.rb +33 -0
- data/lib/exception_hunter/config.rb +22 -2
- data/lib/exception_hunter/data_redacter.rb +27 -0
- data/lib/exception_hunter/devise.rb +2 -0
- data/lib/exception_hunter/engine.rb +1 -0
- data/lib/exception_hunter/error_creator.rb +42 -11
- data/lib/exception_hunter/error_reaper.rb +8 -0
- data/lib/exception_hunter/middleware/delayed_job_hunter.rb +3 -1
- data/lib/exception_hunter/middleware/request_hunter.rb +4 -2
- data/lib/exception_hunter/middleware/sidekiq_hunter.rb +1 -1
- data/lib/exception_hunter/notifiers/misconfigured_notifiers.rb +10 -0
- data/lib/exception_hunter/notifiers/slack_notifier.rb +42 -0
- data/lib/exception_hunter/notifiers/slack_notifier_serializer.rb +20 -0
- data/lib/exception_hunter/tracking.rb +41 -1
- data/lib/exception_hunter/user_attributes_collector.rb +18 -1
- data/lib/exception_hunter/version.rb +1 -1
- data/lib/generators/exception_hunter/install/templates/exception_hunter.rb.erb +29 -0
- data/lib/tasks/exception_hunter_tasks.rake +1 -1
- metadata +27 -7
- data/lib/tasks/code_analysis.rake +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fb501bfc67399400c0d73e8d6df68fb8a20c25f6e12ef2dbc0507afda20aa93
|
4
|
+
data.tar.gz: 7f9af95d0edc69281d63c6b9cffb3ae259dec67bda2ce7e7901d6cb5bda371b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6170a00e2225b75eb4c7b9e8ee4278cf520df79728fe717ba5bd785136195f31fe72c417d8d794e38d63b64b528dcbc4a449fee09f2976b286542e1c69ff2dcd
|
7
|
+
data.tar.gz: b349b87bf65da803cf32044b69cfb53a81329d6a02b50dc535c6a257aaad16dd32789547eb3858d2714015322f67008efdf217e4d3f5474418ed008f19abb2d5
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](https://codeclimate.com/github/rootstrap/exception_hunter/maintainability)
|
5
5
|
[](https://codeclimate.com/github/rootstrap/exception_hunter/test_coverage)
|
6
6
|
|
7
|
-

|
8
8
|
|
9
9
|
Exception Hunter is a Rails engine meant to track errors in your Rails project. It works
|
10
10
|
by using your Postgres database to save errors with their corresponding metadata (like backtrace
|
@@ -21,11 +21,16 @@ Error tracking is one of the most important tools a developer can have in their
|
|
21
21
|
we think it'd be nice to provide a way for everyone to have it in their project, be it a personal
|
22
22
|
project, and MVP or something else.
|
23
23
|
|
24
|
+
## Docs
|
25
|
+
|
26
|
+
You can check the full documentation at [https://rootstrap.github.io/exception_hunter](https://rootstrap.github.io/exception_hunter).
|
27
|
+
|
24
28
|
## Installation
|
29
|
+
|
25
30
|
Add Exception Hunter to your application's Gemfile:
|
26
31
|
|
27
32
|
```ruby
|
28
|
-
gem 'exception_hunter', '~> 0
|
33
|
+
gem 'exception_hunter', '~> 1.0'
|
29
34
|
```
|
30
35
|
|
31
36
|
You may also need to add [Devise](https://github.com/heartcombo/devise) to your Gemfile
|
@@ -49,6 +54,18 @@ you can run the command with the `--skip-users` flag.
|
|
49
54
|
Additionally it should add the 'ExceptionHunter.routes(self)' line to your routes, which means you can go to
|
50
55
|
`/exception_hunter/errors` in your browser and start enjoying some good old fashioned exception tracking!
|
51
56
|
|
57
|
+
#### Testing it on dev:
|
58
|
+
|
59
|
+
ExceptionHunter is disabled on dev by default so if you want to test it before shipping it to another
|
60
|
+
environment, which we highly recommend, you should enable it by going to the initializer and changing the
|
61
|
+
line that says `config.enabled = !(Rails.env.development? || Rails.env.test?)` with something like
|
62
|
+
`config.enabled = !(Rails.env.test?)` while you test. Don't forget to change it back if you don't
|
63
|
+
want a bunch of errors in your local DB!
|
64
|
+
|
65
|
+
You can then open a `rails console` and manually track an exception to check that it
|
66
|
+
works `ExceptionHunter.track(StandardError.new("It works!"))`. You should now see the exception
|
67
|
+
on [http://localhost:3000/exception_hunter]().
|
68
|
+
|
52
69
|
## Stale data
|
53
70
|
|
54
71
|
You can get rid of stale errors by running the rake task to purge them:
|
@@ -62,7 +79,74 @@ a week would be ideal. You can also purge errors by running `ExceptionHunter::Er
|
|
62
79
|
|
63
80
|
The time it takes for an error to go stale defaults to 45 days but it's configurable via the initializer.
|
64
81
|
|
82
|
+
## Manual tracking
|
83
|
+
|
84
|
+
ExceptionHunter also includes a facility to manually log from anywhere in the code. Imagine the following case:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
case current_user.status
|
88
|
+
when :inactive then do_something
|
89
|
+
when :active then do_something_else
|
90
|
+
when :banned then do_something_else_else
|
91
|
+
else
|
92
|
+
ExceptionHunter.track(ArgumentError.new('This should never happen'), custom_data: { status: current_user.status }, user: current_user)
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
In this scenario we don't really want to raise an exception but we might want to be alerted if by any chance a user
|
97
|
+
has an invalid status.
|
98
|
+
|
99
|
+
## Slack notifications
|
100
|
+
|
101
|
+
You can configure ExceptionHunter to send a message to slack every time an error occurs.
|
102
|
+
You have to do the following:
|
103
|
+
|
104
|
+
1. Create a Slack app.
|
105
|
+
1. Add it to your workspace.
|
106
|
+
1. Add one or more webhooks linked to the channels you want to receive the notifications.
|
107
|
+
1. Set the webhook urls in the `exception_hunter` initializer.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
config.notifiers << {
|
111
|
+
name: :slack,
|
112
|
+
options: {
|
113
|
+
webhook: 'SLACK_WEBHOOK_URL_1'
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
config.notifiers << {
|
118
|
+
name: :slack,
|
119
|
+
options: {
|
120
|
+
webhook: 'SLACK_WEBHOOK_URL_2'
|
121
|
+
}
|
122
|
+
}
|
123
|
+
```
|
124
|
+
|
125
|
+
1. Add the code below to the environment config file where you are using ExceptionHunter with the correct server url.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
ExceptionHunter::Engine.configure do |config|
|
129
|
+
config.routes.default_url_options = { host: "your_server_url" }
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
This uses ActiveJob to send notification in the background, so [make sure you configure](https://guides.rubyonrails.org/active_job_basics.html#setting-the-backend) it with the adapter you are using, if not notifications will be sent synchronously.
|
134
|
+
|
135
|
+
## Async Logging
|
136
|
+
|
137
|
+
You can configure ExceptionHunter to log async when an error occurs.
|
138
|
+
You have to do the following:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
config.async_logging = true;
|
142
|
+
```
|
143
|
+
|
144
|
+
This uses ActiveJob to log the error in the background, so [make sure you configure](https://guides.rubyonrails.org/active_job_basics.html#setting-the-backend) it with the adapter you are using, if not the error will be logged synchronously.
|
145
|
+
|
146
|
+
Note: Errors from jobs will still be logged synchronously to not queue a job from a job (which sound like a bad idea)
|
147
|
+
|
65
148
|
## License
|
149
|
+
|
66
150
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
67
151
|
|
68
152
|
## Credits
|
@@ -153,6 +153,31 @@ li.errors-tab {
|
|
153
153
|
border-color: var(--focused-grey);
|
154
154
|
}
|
155
155
|
|
156
|
+
.button_to .button.button-outlined.ignore-button {
|
157
|
+
color: var(--highlight-color);
|
158
|
+
border-color: var(--highlight-color);
|
159
|
+
font-size: 10px;
|
160
|
+
padding: 0 1rem;
|
161
|
+
height: 3rem;
|
162
|
+
line-height: 3rem;
|
163
|
+
}
|
164
|
+
|
165
|
+
.button_to .button.ignore-button {
|
166
|
+
color: #FFF;
|
167
|
+
background-color: var(--highlight-color);
|
168
|
+
border-color: var(--highlight-color);
|
169
|
+
font-size: 10px;
|
170
|
+
padding: 0 1rem;
|
171
|
+
height: 3rem;
|
172
|
+
line-height: 3rem;
|
173
|
+
margin-bottom: 0;
|
174
|
+
}
|
175
|
+
|
176
|
+
.button.ignore-button:hover, .button.ignore-button:focus {
|
177
|
+
color: var(--focused-grey);
|
178
|
+
border-color: var(--focused-grey);
|
179
|
+
}
|
180
|
+
|
156
181
|
.error-occurred_at {
|
157
182
|
color: var(--highlight-color);
|
158
183
|
font-size: 14px;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_dependency 'exception_hunter/application_controller'
|
2
|
+
|
3
|
+
module ExceptionHunter
|
4
|
+
class IgnoredErrorsController < ApplicationController
|
5
|
+
def create
|
6
|
+
error_group.ignored!
|
7
|
+
redirect_to errors_path, notice: 'Error ignored successfully'
|
8
|
+
end
|
9
|
+
|
10
|
+
def reopen
|
11
|
+
error_group.active!
|
12
|
+
redirect_to errors_path, notice: 'Error re-opened successfully'
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def error_group
|
18
|
+
@error_group ||= ErrorGroup.find(error_group_params[:id])
|
19
|
+
end
|
20
|
+
|
21
|
+
def error_group_params
|
22
|
+
params.require(:error_group).permit(:id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,5 +1,27 @@
|
|
1
1
|
module ExceptionHunter
|
2
2
|
module ApplicationHelper
|
3
3
|
include Pagy::Frontend
|
4
|
+
|
5
|
+
def application_name
|
6
|
+
if defined? Rails.application.class.module_parent_name
|
7
|
+
Rails.application.class.module_parent_name
|
8
|
+
else
|
9
|
+
Rails.application.class.parent_name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def display_action_button(title, error)
|
14
|
+
button_to(title.to_s, route_for_button(title, error),
|
15
|
+
class: "button button-outline #{title}-button",
|
16
|
+
data: { confirm: "Are you sure you want to #{title} this error?" }).to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def route_for_button(title, error)
|
20
|
+
if title.eql?('ignore')
|
21
|
+
ignored_errors_path(error_group: { id: error.id })
|
22
|
+
else
|
23
|
+
resolved_errors_path(error_group: { id: error.id })
|
24
|
+
end
|
25
|
+
end
|
4
26
|
end
|
5
27
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class AsyncLoggingJob < ApplicationJob
|
3
|
+
queue_as :default
|
4
|
+
|
5
|
+
def perform(tag, error_attrs)
|
6
|
+
error_attrs = error_attrs.merge(occurred_at: Time.at(error_attrs[:occurred_at])) if error_attrs[:occurred_at]
|
7
|
+
ErrorCreator.call(async_logging: false, tag: tag, **error_attrs)
|
8
|
+
rescue Exception
|
9
|
+
# Suppress all exceptions to avoid loop as this would create a new error in EH.
|
10
|
+
false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class SendNotificationJob < ApplicationJob
|
3
|
+
queue_as :default
|
4
|
+
|
5
|
+
def perform(serialized_notifier)
|
6
|
+
# Use SlackNotifierSerializer as it's the only one for now.
|
7
|
+
serializer = ExceptionHunter::Notifiers::SlackNotifierSerializer
|
8
|
+
deserialized_notifier = serializer.deserialize(serialized_notifier)
|
9
|
+
deserialized_notifier.notify
|
10
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
11
|
+
# Suppress all exceptions to avoid loop as this would create a new error in EH.
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -6,7 +6,7 @@ module ExceptionHunter
|
|
6
6
|
belongs_to :error_group, touch: true
|
7
7
|
|
8
8
|
before_validation :set_occurred_at, on: :create
|
9
|
-
after_create :unresolve_error_group,
|
9
|
+
after_create :unresolve_error_group, if: -> { error_group.resolved? }
|
10
10
|
|
11
11
|
scope :most_recent, lambda { |error_group_id|
|
12
12
|
where(error_group_id: error_group_id).order(occurred_at: :desc)
|
@@ -26,6 +26,10 @@ module ExceptionHunter
|
|
26
26
|
joins(:error_group).where(error_group: ErrorGroup.resolved)
|
27
27
|
}
|
28
28
|
|
29
|
+
scope :from_ignored_error_groups, lambda {
|
30
|
+
joins(:error_group).where(error_group: ErrorGroup.ignored)
|
31
|
+
}
|
32
|
+
|
29
33
|
private
|
30
34
|
|
31
35
|
def set_occurred_at
|
@@ -6,7 +6,7 @@ module ExceptionHunter
|
|
6
6
|
|
7
7
|
has_many :grouped_errors, class_name: 'ExceptionHunter::Error', dependent: :destroy
|
8
8
|
|
9
|
-
enum status: { active: 0, resolved: 1 }
|
9
|
+
enum status: { active: 0, resolved: 1, ignored: 2 }
|
10
10
|
|
11
11
|
scope :most_similar, lambda { |message|
|
12
12
|
message_similarity = sql_similarity(ErrorGroup[:message], message)
|
@@ -4,7 +4,8 @@ module ExceptionHunter
|
|
4
4
|
CURRENT_MONTH_TAB = 'current_month'.freeze
|
5
5
|
TOTAL_ERRORS_TAB = 'total_errors'.freeze
|
6
6
|
RESOLVED_ERRORS_TAB = 'resolved'.freeze
|
7
|
-
|
7
|
+
IGNORED_ERRORS_TAB = 'ignored'.freeze
|
8
|
+
TABS = [LAST_7_DAYS_TAB, CURRENT_MONTH_TAB, TOTAL_ERRORS_TAB, RESOLVED_ERRORS_TAB, IGNORED_ERRORS_TAB].freeze
|
8
9
|
DEFAULT_TAB = LAST_7_DAYS_TAB
|
9
10
|
|
10
11
|
attr_reader :current_tab
|
@@ -22,7 +23,7 @@ module ExceptionHunter
|
|
22
23
|
case current_tab
|
23
24
|
when LAST_7_DAYS_TAB
|
24
25
|
'exception_hunter/errors/last_7_days_errors_table'
|
25
|
-
when CURRENT_MONTH_TAB, TOTAL_ERRORS_TAB, RESOLVED_ERRORS_TAB
|
26
|
+
when CURRENT_MONTH_TAB, TOTAL_ERRORS_TAB, RESOLVED_ERRORS_TAB, IGNORED_ERRORS_TAB
|
26
27
|
'exception_hunter/errors/errors_table'
|
27
28
|
end
|
28
29
|
end
|
@@ -47,7 +48,8 @@ module ExceptionHunter
|
|
47
48
|
LAST_7_DAYS_TAB => active_errors.in_last_7_days.count,
|
48
49
|
CURRENT_MONTH_TAB => active_errors.in_current_month.count,
|
49
50
|
TOTAL_ERRORS_TAB => active_errors.count,
|
50
|
-
RESOLVED_ERRORS_TAB => Error.from_resolved_error_groups.count
|
51
|
+
RESOLVED_ERRORS_TAB => Error.from_resolved_error_groups.count,
|
52
|
+
IGNORED_ERRORS_TAB => Error.from_ignored_error_groups.count
|
51
53
|
}
|
52
54
|
end
|
53
55
|
end
|
@@ -6,7 +6,7 @@
|
|
6
6
|
</div>
|
7
7
|
<% end %>
|
8
8
|
</div>
|
9
|
-
<div class="column column-
|
9
|
+
<div class="column column-33 error-cell error-cell__message">
|
10
10
|
<%= link_to error.message, error_path(error.id), class: %w[error-message] %>
|
11
11
|
</div>
|
12
12
|
|
@@ -26,19 +26,27 @@
|
|
26
26
|
<% end %>
|
27
27
|
</div>
|
28
28
|
|
29
|
-
<div class="column column-
|
29
|
+
<div class="column column-8 error-cell">
|
30
30
|
<%= error.total_occurrences %>
|
31
31
|
</div>
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
<div class="column column-18 error-cell">
|
34
|
+
<div class="row">
|
35
|
+
<% if error.active? %>
|
36
|
+
<div class="column mr-5">
|
37
|
+
<%= display_action_button('resolve', error) %>
|
38
|
+
</div>
|
39
|
+
<div class="column">
|
40
|
+
<%= display_action_button('ignore', error) %>
|
41
|
+
</div>
|
42
|
+
<% elsif error.ignored? %>
|
43
|
+
<div class="column">
|
44
|
+
<%= button_to('Reopen', reopen_path(error_group: { id: error.id }),
|
38
45
|
class: %w[button button-outline resolve-button],
|
39
|
-
data: { confirm: 'Are you sure you want to
|
40
|
-
|
41
|
-
|
42
|
-
|
46
|
+
data: { confirm: 'Are you sure you want to reopen this error?' }) %>
|
47
|
+
</div>
|
48
|
+
<% end %>
|
49
|
+
</div>
|
50
|
+
</div>
|
43
51
|
</div>
|
44
52
|
|
@@ -1,11 +1,7 @@
|
|
1
1
|
<% today_errors = errors.select { |error| error.show_for_day?(Date.current) } %>
|
2
2
|
<%= render partial: 'exception_hunter/errors/error_row', collection: today_errors, as: :error %>
|
3
3
|
|
4
|
-
<%
|
5
|
-
<div class="errors-date-group">Yesterday</div>
|
6
|
-
<%= render partial: 'exception_hunter/errors/error_row', collection: yesterday_errors, as: :error %>
|
7
|
-
|
8
|
-
<% (2..6).each do |i| %>
|
4
|
+
<% (1..6).each do |i| %>
|
9
5
|
<% errors_on_day = errors.select { |error| error.show_for_day?(i.days.ago) } %>
|
10
6
|
<div class="errors-date-group"><%= ExceptionHunter::ErrorGroupPresenter.format_occurrence_day(i.days.ago) %></div>
|
11
7
|
<%= render partial: 'exception_hunter/errors/error_row', collection: errors_on_day, as: :error %>
|
@@ -52,6 +52,19 @@
|
|
52
52
|
</div>
|
53
53
|
<% end %>
|
54
54
|
</div>
|
55
|
+
|
56
|
+
<div class="errors-tab errors-tab__ignored <%= 'errors-tab--active' if @dashboard.tab_active?(@dashboard.class::IGNORED_ERRORS_TAB) %>">
|
57
|
+
<%= link_to errors_path(tab: @dashboard.class::IGNORED_ERRORS_TAB) do %>
|
58
|
+
<div class="errors-tab__content">
|
59
|
+
<div class="errors-tab__badge">
|
60
|
+
<%= @dashboard.errors_count(@dashboard.class::IGNORED_ERRORS_TAB) || '-' %>
|
61
|
+
</div>
|
62
|
+
<div class="errors-tab__description">
|
63
|
+
Ignored
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
<% end %>
|
67
|
+
</div>
|
55
68
|
</div>
|
56
69
|
</div>
|
57
70
|
|
@@ -66,11 +79,12 @@
|
|
66
79
|
<div class="errors__container">
|
67
80
|
<div class="row error-row error-row--header">
|
68
81
|
<div class="column column-10">Tags</div>
|
69
|
-
<div class="column column-
|
82
|
+
<div class="column column-33">Message</div>
|
70
83
|
<div class="column column-15">First Occurrence</div>
|
71
84
|
<div class="column column-15">Last Occurrence</div>
|
72
|
-
<div class="column column-
|
73
|
-
<div class="column column-
|
85
|
+
<div class="column column-8">Total</div>
|
86
|
+
<div class="column column-18"></div>
|
87
|
+
|
74
88
|
</div>
|
75
89
|
|
76
90
|
<%= render partial: @dashboard.partial_for_tab, locals: { errors: @errors } %>
|
@@ -24,7 +24,9 @@
|
|
24
24
|
<div class="row">
|
25
25
|
<div class="column column-40">
|
26
26
|
<%= link_to errors_path do %>
|
27
|
-
<div class="nav__title">
|
27
|
+
<div class="nav__title">
|
28
|
+
Exception Hunter / <%= application_name %>
|
29
|
+
</div>
|
28
30
|
<% end %>
|
29
31
|
</div>
|
30
32
|
<div class="column column-10 column-offset-50">
|
@@ -13,7 +13,7 @@ MoveCodeIntoControllerCheck: { }
|
|
13
13
|
MoveCodeIntoHelperCheck: { array_count: 3 }
|
14
14
|
MoveCodeIntoModelCheck: { use_count: 2 }
|
15
15
|
MoveFinderToNamedScopeCheck: { }
|
16
|
-
MoveModelLogicIntoModelCheck: { use_count: 4 }
|
16
|
+
# MoveModelLogicIntoModelCheck: { use_count: 4 }
|
17
17
|
NeedlessDeepNestingCheck: { nested_count: 2 }
|
18
18
|
NotUseDefaultRouteCheck: { }
|
19
19
|
#NotUseTimeAgoInWordsCheck: { ignored_files: ['index.html.erb'] }
|
data/config/routes.rb
CHANGED
data/lib/exception_hunter.rb
CHANGED
@@ -7,19 +7,52 @@ require 'exception_hunter/error_creator'
|
|
7
7
|
require 'exception_hunter/error_reaper'
|
8
8
|
require 'exception_hunter/tracking'
|
9
9
|
require 'exception_hunter/user_attributes_collector'
|
10
|
+
require 'exception_hunter/notifiers/slack_notifier'
|
11
|
+
require 'exception_hunter/notifiers/slack_notifier_serializer'
|
12
|
+
require 'exception_hunter/notifiers/misconfigured_notifiers'
|
13
|
+
require 'exception_hunter/data_redacter'
|
10
14
|
|
15
|
+
# @api public
|
11
16
|
module ExceptionHunter
|
12
17
|
autoload :Devise, 'exception_hunter/devise'
|
13
18
|
|
14
19
|
extend ::ExceptionHunter::Tracking
|
15
20
|
|
21
|
+
# Used to setup ExceptionHunter's configuration
|
22
|
+
# it receives a block with the {ExceptionHunter::Config} singleton
|
23
|
+
# class.
|
24
|
+
#
|
25
|
+
# @return [void]
|
16
26
|
def self.setup(&block)
|
17
27
|
block.call(Config)
|
28
|
+
validate_config!
|
18
29
|
end
|
19
30
|
|
31
|
+
# Mounts the ExceptionHunter dashboard at /exception_hunter
|
32
|
+
# if it's enabled on the current environment.
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# Rails.application.routes.draw do
|
36
|
+
# ExceptionHunter.routes(self)
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# @param [ActionDispatch::Routing::Mapper] router to mount to
|
40
|
+
# @return [void]
|
20
41
|
def self.routes(router)
|
21
42
|
return unless Config.enabled
|
22
43
|
|
23
44
|
router.mount(ExceptionHunter::Engine, at: 'exception_hunter')
|
24
45
|
end
|
46
|
+
|
47
|
+
# @private
|
48
|
+
def self.validate_config!
|
49
|
+
notifiers = Config.notifiers
|
50
|
+
return if notifiers.blank?
|
51
|
+
|
52
|
+
notifiers.each do |notifier|
|
53
|
+
next if notifier[:name] == :slack && notifier.dig(:options, :webhook).present?
|
54
|
+
|
55
|
+
raise ExceptionHunter::Notifiers::MisconfiguredNotifiers, notifier
|
56
|
+
end
|
57
|
+
end
|
25
58
|
end
|
@@ -1,10 +1,30 @@
|
|
1
1
|
module ExceptionHunter
|
2
|
+
# Config singleton class used to customize ExceptionHunter
|
2
3
|
class Config
|
3
|
-
|
4
|
-
|
4
|
+
# @!attribute
|
5
|
+
# @return [Boolean] whether ExceptionHunter is active or not
|
5
6
|
cattr_accessor :enabled, default: true
|
7
|
+
# @!attribute
|
8
|
+
# @return [String] the name of the admin class (generally AdminUser)
|
9
|
+
cattr_accessor :admin_user_class
|
10
|
+
# @!attribute
|
11
|
+
# @return [Symbol] the name of the current user method provided by Devise
|
12
|
+
cattr_accessor :current_user_method
|
13
|
+
# @return [Array<Symbol>] attributes to whitelist on the user (see {ExceptionHunter::UserAttributesCollector})
|
14
|
+
cattr_accessor :user_attributes
|
15
|
+
# @return [Numeric] number of days until an error is considered stale
|
6
16
|
cattr_accessor :errors_stale_time, default: 45.days
|
17
|
+
# @return [Array<Hash>] configured notifiers for the application (see {ExceptionHunter::Notifiers})
|
18
|
+
cattr_accessor :notifiers, default: []
|
19
|
+
cattr_accessor :sensitive_fields, default: []
|
20
|
+
# @!attribute
|
21
|
+
# @return [Boolean] whether ExceptionHunter should log async or not
|
22
|
+
cattr_accessor :async_logging, default: false
|
7
23
|
|
24
|
+
# Returns true if there's an admin user class configured to
|
25
|
+
# authenticate against.
|
26
|
+
#
|
27
|
+
# @return Boolean
|
8
28
|
def self.auth_enabled?
|
9
29
|
admin_user_class.present? && admin_user_class.try(:underscore)
|
10
30
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class DataRedacter
|
3
|
+
attr_reader :params, :params_to_filter
|
4
|
+
|
5
|
+
def initialize(params, params_to_filter)
|
6
|
+
@params = params
|
7
|
+
@params_to_filter = params_to_filter
|
8
|
+
end
|
9
|
+
|
10
|
+
def redact
|
11
|
+
return params if params.blank?
|
12
|
+
|
13
|
+
parameter_filter = params_filter.new(params_to_filter)
|
14
|
+
parameter_filter.filter(params)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def params_filter
|
20
|
+
if defined?(::ActiveSupport::ParameterFilter)
|
21
|
+
::ActiveSupport::ParameterFilter
|
22
|
+
else
|
23
|
+
::ActionDispatch::Http::ParameterFilter
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module ExceptionHunter
|
2
2
|
module Devise
|
3
|
+
# Used so we can integrate with {https://github.com/heartcombo/devise Devise} and
|
4
|
+
# provide a custom login on the dashboard.
|
3
5
|
class SessionsController < ::Devise::SessionsController
|
4
6
|
skip_before_action :verify_authenticity_token
|
5
7
|
|
@@ -1,28 +1,48 @@
|
|
1
1
|
module ExceptionHunter
|
2
|
+
# Core class in charge of the actual persistence of errors and notifications.
|
2
3
|
class ErrorCreator
|
3
4
|
HTTP_TAG = 'HTTP'.freeze
|
4
5
|
WORKER_TAG = 'Worker'.freeze
|
5
6
|
MANUAL_TAG = 'Manual'.freeze
|
7
|
+
NOTIFICATION_DELAY = 1.minute
|
6
8
|
|
7
9
|
class << self
|
8
|
-
|
10
|
+
# Creates an error with the given attributes and persists it to
|
11
|
+
# the database.
|
12
|
+
#
|
13
|
+
# @param [HTTP_TAG, WORKER_TAG, MANUAL_TAG] tag to append to the error if any
|
14
|
+
# @return [ExceptionHunter::Error, false] the error or false if it was not possible to create it
|
15
|
+
def call(async_logging: Config.async_logging, tag: nil, **error_attrs)
|
9
16
|
return unless should_create?
|
10
17
|
|
18
|
+
if async_logging
|
19
|
+
# Time is sent in unix format and then converted to Time to avoid ActiveJob::SerializationError
|
20
|
+
::ExceptionHunter::AsyncLoggingJob.perform_later(tag, error_attrs.merge(occurred_at: Time.now.to_i))
|
21
|
+
else
|
22
|
+
create_error(tag, error_attrs)
|
23
|
+
end
|
24
|
+
rescue ActiveRecord::RecordInvalid
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def create_error(tag, error_attrs)
|
11
31
|
ActiveRecord::Base.transaction do
|
12
32
|
error_attrs = extract_user_data(error_attrs)
|
33
|
+
error_attrs = hide_sensitive_values(error_attrs)
|
13
34
|
error = ::ExceptionHunter::Error.new(error_attrs)
|
14
35
|
error_group = ::ExceptionHunter::ErrorGroup.find_matching_group(error) || ::ExceptionHunter::ErrorGroup.new
|
15
36
|
update_error_group(error_group, error, tag)
|
16
37
|
error.error_group = error_group
|
17
38
|
error.save!
|
39
|
+
return if error_group.ignored?
|
40
|
+
|
41
|
+
notify(error)
|
18
42
|
error
|
19
43
|
end
|
20
|
-
rescue ActiveRecord::RecordInvalid
|
21
|
-
false
|
22
44
|
end
|
23
45
|
|
24
|
-
private
|
25
|
-
|
26
46
|
def should_create?
|
27
47
|
Config.enabled
|
28
48
|
end
|
@@ -38,16 +58,27 @@ module ExceptionHunter
|
|
38
58
|
|
39
59
|
def extract_user_data(**error_attrs)
|
40
60
|
user = error_attrs[:user]
|
41
|
-
error_attrs[:user_data] =
|
42
|
-
if user.nil?
|
43
|
-
{}
|
44
|
-
else
|
45
|
-
UserAttributesCollector.collect_attributes(user)
|
46
|
-
end
|
61
|
+
error_attrs[:user_data] = UserAttributesCollector.collect_attributes(user)
|
47
62
|
|
48
63
|
error_attrs.delete(:user)
|
49
64
|
error_attrs
|
50
65
|
end
|
66
|
+
|
67
|
+
def notify(error)
|
68
|
+
ExceptionHunter::Config.notifiers.each do |notifier|
|
69
|
+
slack_notifier = ExceptionHunter::Notifiers::SlackNotifier.new(error, notifier)
|
70
|
+
serializer = ExceptionHunter::Notifiers::SlackNotifierSerializer
|
71
|
+
serialized_slack_notifier = serializer.serialize(slack_notifier)
|
72
|
+
ExceptionHunter::SendNotificationJob.set(
|
73
|
+
wait: NOTIFICATION_DELAY
|
74
|
+
).perform_later(serialized_slack_notifier)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def hide_sensitive_values(error_attrs)
|
79
|
+
sensitive_fields = ExceptionHunter::Config.sensitive_fields
|
80
|
+
ExceptionHunter::DataRedacter.new(error_attrs, sensitive_fields).redact
|
81
|
+
end
|
51
82
|
end
|
52
83
|
end
|
53
84
|
end
|
@@ -1,6 +1,14 @@
|
|
1
1
|
module ExceptionHunter
|
2
|
+
# Class in charge of disposing of stale errors as specified in the {ExceptionHunter::Config}.
|
2
3
|
class ErrorReaper
|
3
4
|
class << self
|
5
|
+
# Destroys all stale errors.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# ErrorReaper.purge(stale_time: 30.days)
|
9
|
+
#
|
10
|
+
# @param [Numeric] stale_time considered when destroying errors
|
11
|
+
# @return [void]
|
4
12
|
def purge(stale_time: Config.errors_stale_time)
|
5
13
|
ActiveRecord::Base.transaction do
|
6
14
|
Error.with_occurrences_before(Date.today - stale_time).destroy_all
|
@@ -2,6 +2,7 @@ require 'delayed_job'
|
|
2
2
|
|
3
3
|
module ExceptionHunter
|
4
4
|
module Middleware
|
5
|
+
# DelayedJob plugin to track exceptions on apps using DelayedJob.
|
5
6
|
class DelayedJobHunter < ::Delayed::Plugin
|
6
7
|
TRACK_AT_RETRY = [0, 3, 6, 10].freeze
|
7
8
|
JOB_TRACKED_DATA = %w[
|
@@ -30,6 +31,7 @@ module ExceptionHunter
|
|
30
31
|
return unless should_track?(job.attempts)
|
31
32
|
|
32
33
|
ErrorCreator.call(
|
34
|
+
async_logging: false,
|
33
35
|
tag: ErrorCreator::WORKER_TAG,
|
34
36
|
class_name: exception.class.to_s,
|
35
37
|
message: exception.message,
|
@@ -40,7 +42,7 @@ module ExceptionHunter
|
|
40
42
|
|
41
43
|
def self.environment_data(job)
|
42
44
|
job_data =
|
43
|
-
JOB_TRACKED_DATA.
|
45
|
+
JOB_TRACKED_DATA.reduce({}) do |dict, data_param|
|
44
46
|
dict.merge(data_param => job.try(data_param))
|
45
47
|
end
|
46
48
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module ExceptionHunter
|
2
2
|
module Middleware
|
3
|
+
# {https://www.rubyguides.com/2018/09/rack-middleware Rack Middleware} used to
|
4
|
+
# rescue from exceptions track them and then re-raise them.
|
3
5
|
class RequestHunter
|
4
6
|
ENVIRONMENT_KEYS =
|
5
7
|
%w[PATH_INFO
|
@@ -53,14 +55,14 @@ module ExceptionHunter
|
|
53
55
|
|
54
56
|
def filtered_sensitive_params(env)
|
55
57
|
params = env['action_dispatch.request.parameters']
|
56
|
-
|
57
|
-
parameter_filter.filter(params || {})
|
58
|
+
ExceptionHunter::DataRedacter.new(params, FILTERED_PARAMS).redact
|
58
59
|
end
|
59
60
|
end
|
60
61
|
end
|
61
62
|
end
|
62
63
|
|
63
64
|
module ExceptionHunter
|
65
|
+
# @private
|
64
66
|
class Railtie < Rails::Railtie
|
65
67
|
initializer 'exception_hunter.add_middleware', after: :load_config_initializers do |app|
|
66
68
|
app.config.middleware.insert_after(
|
@@ -2,7 +2,6 @@ module ExceptionHunter
|
|
2
2
|
module Middleware
|
3
3
|
# Middleware to report errors
|
4
4
|
# when a Sidekiq worker fails
|
5
|
-
#
|
6
5
|
class SidekiqHunter
|
7
6
|
TRACK_AT_RETRY = [0, 3, 6, 10].freeze
|
8
7
|
JOB_TRACKED_DATA = %w[
|
@@ -29,6 +28,7 @@ module ExceptionHunter
|
|
29
28
|
return unless should_track?(context)
|
30
29
|
|
31
30
|
ErrorCreator.call(
|
31
|
+
async_logging: false,
|
32
32
|
tag: ErrorCreator::WORKER_TAG,
|
33
33
|
class_name: exception.class.to_s,
|
34
34
|
message: exception.message,
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
module Notifiers
|
3
|
+
# Error raised when there's a malformed notifier.
|
4
|
+
class MisconfiguredNotifiers < StandardError
|
5
|
+
def initialize(notifier)
|
6
|
+
super("Notifier has incorrect configuration: #{notifier.inspect}")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'slack-notifier'
|
2
|
+
|
3
|
+
module ExceptionHunter
|
4
|
+
module Notifiers
|
5
|
+
# Notifier that sends a message to a Slack channel every time an
|
6
|
+
# exception is tracked.
|
7
|
+
class SlackNotifier
|
8
|
+
attr_reader :error, :notifier
|
9
|
+
|
10
|
+
def initialize(error, notifier)
|
11
|
+
@error = error
|
12
|
+
@notifier = notifier
|
13
|
+
end
|
14
|
+
|
15
|
+
def notify
|
16
|
+
slack_notifier = Slack::Notifier.new(notifier[:options][:webhook])
|
17
|
+
slack_notifier.ping(slack_notification_message)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def slack_notification_message
|
23
|
+
{
|
24
|
+
blocks: [
|
25
|
+
{
|
26
|
+
type: 'section',
|
27
|
+
text: {
|
28
|
+
type: 'mrkdwn',
|
29
|
+
text: error_message
|
30
|
+
}
|
31
|
+
}
|
32
|
+
]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def error_message
|
37
|
+
"*#{error.class_name}*: #{error.message}. \n" \
|
38
|
+
"<#{ExceptionHunter::Engine.routes.url_helpers.error_url(error.error_group)}|Click to see the error>"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
module Notifiers
|
3
|
+
# @private
|
4
|
+
class SlackNotifierSerializer
|
5
|
+
def self.serialize(slack_notifier)
|
6
|
+
{
|
7
|
+
error: slack_notifier.error,
|
8
|
+
notifier: slack_notifier.notifier.as_json
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.deserialize(hash)
|
13
|
+
ExceptionHunter::Notifiers::SlackNotifier.new(
|
14
|
+
hash[:error],
|
15
|
+
hash[:notifier].deep_symbolize_keys
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,6 +1,44 @@
|
|
1
1
|
module ExceptionHunter
|
2
|
+
# Mixin used to track manual exceptions.
|
2
3
|
module Tracking
|
4
|
+
# Used to manually track errors in cases where raising might
|
5
|
+
# not be adequate and but some insight is desired.
|
6
|
+
#
|
7
|
+
# @example Track the else clause on a case
|
8
|
+
# case user.status
|
9
|
+
# when :active then do_something()
|
10
|
+
# when :inactive then do_something_else()
|
11
|
+
# else
|
12
|
+
# ExceptionHunter.track(StandardError.new("User with unknown status"),
|
13
|
+
# custom_data: { status: user.status },
|
14
|
+
# user: user)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @param [Exception] exception to track.
|
18
|
+
# @param [Hash] custom_data to include and help debug the error. (optional)
|
19
|
+
# @param [User] user in the current session. (optional)
|
20
|
+
# @return [void]
|
3
21
|
def track(exception, custom_data: {}, user: nil)
|
22
|
+
if open_transactions?
|
23
|
+
create_error_within_new_thread(exception, custom_data, user)
|
24
|
+
else
|
25
|
+
create_error(exception, custom_data, user)
|
26
|
+
end
|
27
|
+
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def create_error_within_new_thread(exception, custom_data, user)
|
34
|
+
Thread.new {
|
35
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
36
|
+
create_error(exception, custom_data, user)
|
37
|
+
end
|
38
|
+
}.join
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_error(exception, custom_data, user)
|
4
42
|
ErrorCreator.call(
|
5
43
|
tag: ErrorCreator::MANUAL_TAG,
|
6
44
|
class_name: exception.class.to_s,
|
@@ -10,8 +48,10 @@ module ExceptionHunter
|
|
10
48
|
user: user,
|
11
49
|
environment_data: {}
|
12
50
|
)
|
51
|
+
end
|
13
52
|
|
14
|
-
|
53
|
+
def open_transactions?
|
54
|
+
ActiveRecord::Base.connection.open_transactions.positive?
|
15
55
|
end
|
16
56
|
end
|
17
57
|
end
|
@@ -1,9 +1,26 @@
|
|
1
1
|
module ExceptionHunter
|
2
|
+
# Utility module used to whitelist the user's attributes.
|
3
|
+
# Can be configured in {ExceptionHunter.setup ExceptionHunter.setup} to extract
|
4
|
+
# custom attributes.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# ExceptionHunter.setup do |config|
|
8
|
+
# config.user_attributes = [:id, :email, :role, :active?]
|
9
|
+
# end
|
10
|
+
#
|
2
11
|
module UserAttributesCollector
|
3
12
|
extend self
|
4
13
|
|
14
|
+
# Gets the attributes configured for the user.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# UserAttributesCollector.collect_attributes(current_user)
|
18
|
+
# # => { id: 42, email: "example@user.com" }
|
19
|
+
#
|
20
|
+
# @param user instance in your application
|
21
|
+
# @return [Hash] the whitelisted attributes from the user
|
5
22
|
def collect_attributes(user)
|
6
|
-
return unless user
|
23
|
+
return {} unless user
|
7
24
|
|
8
25
|
attributes.reduce({}) do |data, attribute|
|
9
26
|
data.merge(attribute => user.try(attribute))
|
@@ -38,4 +38,33 @@ ExceptionHunter.setup do |config|
|
|
38
38
|
# happen automatically.
|
39
39
|
#
|
40
40
|
# config.errors_stale_time = 45.days
|
41
|
+
|
42
|
+
# == Slack notifications
|
43
|
+
#
|
44
|
+
# You can configure if you want to send notifications to slack for each error occurrence.
|
45
|
+
# You can enter multiple webhook urls.
|
46
|
+
# Default: []
|
47
|
+
#
|
48
|
+
# config.notifiers << {
|
49
|
+
# name: :slack,
|
50
|
+
# options: {
|
51
|
+
# webhook: 'SLACK_WEBHOOK_URL_1'
|
52
|
+
# }
|
53
|
+
# }
|
54
|
+
#
|
55
|
+
# config.notifiers << {
|
56
|
+
# name: :slack,
|
57
|
+
# options: {
|
58
|
+
# webhook: SLACK_WEBHOOK_URL_2'
|
59
|
+
# }
|
60
|
+
# }
|
61
|
+
|
62
|
+
# == Filter sensitive parameters
|
63
|
+
#
|
64
|
+
# You can configure if you want to filter some fields on the error's data for security or privacy issues.
|
65
|
+
# We use ActiveSupport::ParameterFilter for this, any accepted pattern will work.
|
66
|
+
# https://api.rubyonrails.org/classes/ActiveSupport/ParameterFilter.html
|
67
|
+
# Default: []
|
68
|
+
#
|
69
|
+
# config.sensitive_parameters = [:id, :name]
|
41
70
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: exception_hunter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bruno Vezoli
|
8
8
|
- Tiziana Romani
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-07-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pagy
|
@@ -25,6 +25,20 @@ dependencies:
|
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '3'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: slack-notifier
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2.3'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '2.3'
|
28
42
|
- !ruby/object:Gem::Dependency
|
29
43
|
name: brakeman
|
30
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -123,7 +137,7 @@ dependencies:
|
|
123
137
|
- - "~>"
|
124
138
|
- !ruby/object:Gem::Version
|
125
139
|
version: 0.17.1
|
126
|
-
description:
|
140
|
+
description:
|
127
141
|
email:
|
128
142
|
- bruno.vezoli@rootstrap.com
|
129
143
|
executables: []
|
@@ -143,11 +157,14 @@ files:
|
|
143
157
|
- app/controllers/concerns/exception_hunter/authorization.rb
|
144
158
|
- app/controllers/exception_hunter/application_controller.rb
|
145
159
|
- app/controllers/exception_hunter/errors_controller.rb
|
160
|
+
- app/controllers/exception_hunter/ignored_errors_controller.rb
|
146
161
|
- app/controllers/exception_hunter/resolved_errors_controller.rb
|
147
162
|
- app/helpers/exception_hunter/application_helper.rb
|
148
163
|
- app/helpers/exception_hunter/errors_helper.rb
|
149
164
|
- app/helpers/exception_hunter/sessions_helper.rb
|
150
165
|
- app/jobs/exception_hunter/application_job.rb
|
166
|
+
- app/jobs/exception_hunter/async_logging_job.rb
|
167
|
+
- app/jobs/exception_hunter/send_notification_job.rb
|
151
168
|
- app/mailers/exception_hunter/application_mailer.rb
|
152
169
|
- app/models/exception_hunter/application_record.rb
|
153
170
|
- app/models/exception_hunter/error.rb
|
@@ -171,6 +188,7 @@ files:
|
|
171
188
|
- config/routes.rb
|
172
189
|
- lib/exception_hunter.rb
|
173
190
|
- lib/exception_hunter/config.rb
|
191
|
+
- lib/exception_hunter/data_redacter.rb
|
174
192
|
- lib/exception_hunter/devise.rb
|
175
193
|
- lib/exception_hunter/engine.rb
|
176
194
|
- lib/exception_hunter/error_creator.rb
|
@@ -178,6 +196,9 @@ files:
|
|
178
196
|
- lib/exception_hunter/middleware/delayed_job_hunter.rb
|
179
197
|
- lib/exception_hunter/middleware/request_hunter.rb
|
180
198
|
- lib/exception_hunter/middleware/sidekiq_hunter.rb
|
199
|
+
- lib/exception_hunter/notifiers/misconfigured_notifiers.rb
|
200
|
+
- lib/exception_hunter/notifiers/slack_notifier.rb
|
201
|
+
- lib/exception_hunter/notifiers/slack_notifier_serializer.rb
|
181
202
|
- lib/exception_hunter/tracking.rb
|
182
203
|
- lib/exception_hunter/user_attributes_collector.rb
|
183
204
|
- lib/exception_hunter/version.rb
|
@@ -187,13 +208,12 @@ files:
|
|
187
208
|
- lib/generators/exception_hunter/install/templates/create_exception_hunter_error_groups.rb.erb
|
188
209
|
- lib/generators/exception_hunter/install/templates/create_exception_hunter_errors.rb.erb
|
189
210
|
- lib/generators/exception_hunter/install/templates/exception_hunter.rb.erb
|
190
|
-
- lib/tasks/code_analysis.rake
|
191
211
|
- lib/tasks/exception_hunter_tasks.rake
|
192
212
|
homepage: https://github.com/rootstrap/exception_hunter
|
193
213
|
licenses:
|
194
214
|
- MIT
|
195
215
|
metadata: {}
|
196
|
-
post_install_message:
|
216
|
+
post_install_message:
|
197
217
|
rdoc_options: []
|
198
218
|
require_paths:
|
199
219
|
- lib
|
@@ -209,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
209
229
|
version: '0'
|
210
230
|
requirements: []
|
211
231
|
rubygems_version: 3.0.8
|
212
|
-
signing_key:
|
232
|
+
signing_key:
|
213
233
|
specification_version: 4
|
214
234
|
summary: Exception tracking engine
|
215
235
|
test_files: []
|