exception_hunter 0.2.0 → 1.0.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 +103 -6
- data/app/assets/stylesheets/exception_hunter/base.css +66 -8
- data/app/assets/stylesheets/exception_hunter/errors.css +188 -25
- data/app/assets/stylesheets/exception_hunter/navigation.css +20 -5
- data/app/assets/stylesheets/exception_hunter/sessions.css +71 -0
- data/app/controllers/concerns/exception_hunter/authorization.rb +23 -0
- data/app/controllers/exception_hunter/application_controller.rb +2 -0
- data/app/controllers/exception_hunter/errors_controller.rb +29 -4
- data/app/controllers/exception_hunter/ignored_errors_controller.rb +25 -0
- data/app/controllers/exception_hunter/resolved_errors_controller.rb +11 -0
- data/app/helpers/exception_hunter/application_helper.rb +22 -0
- data/app/helpers/exception_hunter/sessions_helper.rb +16 -0
- data/app/jobs/exception_hunter/send_notification_job.rb +15 -0
- data/app/models/exception_hunter/application_record.rb +8 -0
- data/app/models/exception_hunter/error.rb +24 -7
- data/app/models/exception_hunter/error_group.rb +24 -5
- data/app/presenters/exception_hunter/dashboard_presenter.rb +56 -0
- data/app/presenters/exception_hunter/error_group_presenter.rb +25 -0
- data/app/presenters/exception_hunter/error_presenter.rb +3 -2
- data/app/views/exception_hunter/devise/sessions/new.html.erb +24 -0
- data/app/views/exception_hunter/errors/_error_row.erb +52 -0
- data/app/views/exception_hunter/errors/_error_summary.erb +5 -5
- data/app/views/exception_hunter/errors/_errors_table.erb +1 -0
- data/app/views/exception_hunter/errors/_last_7_days_errors_table.erb +12 -0
- data/app/views/exception_hunter/errors/index.html.erb +84 -29
- data/app/views/exception_hunter/errors/pagy/_pagy_nav.html.erb +15 -15
- data/app/views/exception_hunter/errors/show.html.erb +58 -22
- data/app/views/layouts/exception_hunter/application.html.erb +67 -6
- data/app/views/layouts/exception_hunter/exception_hunter_logged_out.html.erb +24 -0
- data/config/rails_best_practices.yml +3 -3
- data/config/routes.rb +21 -1
- data/lib/exception_hunter.rb +44 -2
- data/lib/exception_hunter/config.rb +25 -1
- data/lib/exception_hunter/data_redacter.rb +27 -0
- data/lib/exception_hunter/devise.rb +19 -0
- data/lib/exception_hunter/engine.rb +6 -0
- data/lib/exception_hunter/error_creator.rb +72 -0
- data/lib/exception_hunter/error_reaper.rb +20 -0
- data/lib/exception_hunter/middleware/delayed_job_hunter.rb +70 -0
- data/lib/exception_hunter/middleware/request_hunter.rb +5 -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 +35 -0
- data/lib/exception_hunter/user_attributes_collector.rb +21 -0
- data/lib/exception_hunter/version.rb +1 -1
- data/lib/generators/exception_hunter/create_users/create_users_generator.rb +8 -1
- data/lib/generators/exception_hunter/install/install_generator.rb +3 -1
- data/lib/generators/exception_hunter/install/templates/create_exception_hunter_error_groups.rb.erb +3 -0
- data/lib/generators/exception_hunter/install/templates/exception_hunter.rb.erb +52 -0
- data/lib/tasks/exception_hunter_tasks.rake +6 -4
- metadata +46 -12
- data/app/services/exception_hunter/error_creator.rb +0 -41
- data/config/initializers/exception_hunter.rb +0 -16
@@ -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
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
# Mixin used to track manual exceptions.
|
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]
|
21
|
+
def track(exception, custom_data: {}, user: nil)
|
22
|
+
ErrorCreator.call(
|
23
|
+
tag: ErrorCreator::MANUAL_TAG,
|
24
|
+
class_name: exception.class.to_s,
|
25
|
+
message: exception.message,
|
26
|
+
backtrace: exception.backtrace,
|
27
|
+
custom_data: custom_data,
|
28
|
+
user: user,
|
29
|
+
environment_data: {}
|
30
|
+
)
|
31
|
+
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,13 +1,34 @@
|
|
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)
|
23
|
+
return {} unless user
|
24
|
+
|
6
25
|
attributes.reduce({}) do |data, attribute|
|
7
26
|
data.merge(attribute => user.try(attribute))
|
8
27
|
end
|
9
28
|
end
|
10
29
|
|
30
|
+
private
|
31
|
+
|
11
32
|
def attributes
|
12
33
|
Config.user_attributes
|
13
34
|
end
|
@@ -22,7 +22,14 @@ module ExceptionHunter
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def create_admin_user
|
25
|
-
invoke 'devise', [name]
|
25
|
+
invoke 'devise', [name], routes: false
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove_registerable_from_model
|
29
|
+
return if options[:registerable]
|
30
|
+
|
31
|
+
model_file = File.join(destination_root, 'app', 'models', "#{file_path}.rb")
|
32
|
+
gsub_file model_file, /\:registerable([.]*,)?/, ''
|
26
33
|
end
|
27
34
|
end
|
28
35
|
end
|
@@ -15,7 +15,9 @@ module ExceptionHunter
|
|
15
15
|
|
16
16
|
def setup_routes
|
17
17
|
if options[:users]
|
18
|
-
|
18
|
+
gsub_file 'config/routes.rb',
|
19
|
+
"\n devise_for :#{plural_table_name}, skip: :all",
|
20
|
+
"\n ExceptionHunter.routes(self)"
|
19
21
|
else
|
20
22
|
route 'ExceptionHunter.routes(self)'
|
21
23
|
end
|
data/lib/generators/exception_hunter/install/templates/create_exception_hunter_error_groups.rb.erb
CHANGED
@@ -5,10 +5,13 @@ class CreateExceptionHunterErrorGroups < ActiveRecord::Migration[<%= ActiveRecor
|
|
5
5
|
create_table :exception_hunter_error_groups do |t|
|
6
6
|
t.string :error_class_name, null: false
|
7
7
|
t.string :message
|
8
|
+
t.integer :status, default: 0
|
9
|
+
t.text :tags, array: true, default: []
|
8
10
|
|
9
11
|
t.timestamps
|
10
12
|
|
11
13
|
t.index :message, opclass: :gin_trgm_ops, using: :gin
|
14
|
+
t.index :status
|
12
15
|
end
|
13
16
|
end
|
14
17
|
end
|
@@ -1,4 +1,19 @@
|
|
1
1
|
ExceptionHunter.setup do |config|
|
2
|
+
# == Enabling
|
3
|
+
#
|
4
|
+
# This flag allows disabling error tracking, it's set to track in
|
5
|
+
# any environment but development or test by default
|
6
|
+
#
|
7
|
+
config.enabled = !(Rails.env.development? || Rails.env.test?)
|
8
|
+
|
9
|
+
# == Dashboard User
|
10
|
+
# Exception Hunter allows you to restrict users who can see the dashboard
|
11
|
+
# to the ones included in the database. You can change the table name in
|
12
|
+
# case you are not satisfied with the default one. You can also remove the
|
13
|
+
# configuration if you wish to have no access restrictions for the dashboard.
|
14
|
+
#
|
15
|
+
<%= @use_authentication_method ? "config.admin_user_class = '#{name}'" : "# config.admin_user_class = '#{name}'" %>
|
16
|
+
|
2
17
|
# == Current User
|
3
18
|
#
|
4
19
|
# Exception Hunter will include the user as part of the environment
|
@@ -15,4 +30,41 @@ ExceptionHunter.setup do |config|
|
|
15
30
|
# as part of the user information that is kept from the request.
|
16
31
|
#
|
17
32
|
config.user_attributes = [:id, :email]
|
33
|
+
|
34
|
+
# == Stale errors
|
35
|
+
#
|
36
|
+
# You can configure how long it takes for errors to go stale. This is
|
37
|
+
# taken into account when purging old error messages but nothing will
|
38
|
+
# happen automatically.
|
39
|
+
#
|
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]
|
18
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: 0.
|
4
|
+
version: 1.0.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: 2020-05
|
12
|
+
date: 2020-11-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pagy
|
@@ -17,14 +17,28 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '3
|
20
|
+
version: '3'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '3
|
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
|
@@ -115,15 +129,15 @@ dependencies:
|
|
115
129
|
requirements:
|
116
130
|
- - "~>"
|
117
131
|
- !ruby/object:Gem::Version
|
118
|
-
version: 0.
|
132
|
+
version: 0.17.1
|
119
133
|
type: :development
|
120
134
|
prerelease: false
|
121
135
|
version_requirements: !ruby/object:Gem::Requirement
|
122
136
|
requirements:
|
123
137
|
- - "~>"
|
124
138
|
- !ruby/object:Gem::Version
|
125
|
-
version: 0.
|
126
|
-
description:
|
139
|
+
version: 0.17.1
|
140
|
+
description:
|
127
141
|
email:
|
128
142
|
- bruno.vezoli@rootstrap.com
|
129
143
|
executables: []
|
@@ -139,32 +153,52 @@ files:
|
|
139
153
|
- app/assets/stylesheets/exception_hunter/base.css
|
140
154
|
- app/assets/stylesheets/exception_hunter/errors.css
|
141
155
|
- app/assets/stylesheets/exception_hunter/navigation.css
|
156
|
+
- app/assets/stylesheets/exception_hunter/sessions.css
|
157
|
+
- app/controllers/concerns/exception_hunter/authorization.rb
|
142
158
|
- app/controllers/exception_hunter/application_controller.rb
|
143
159
|
- app/controllers/exception_hunter/errors_controller.rb
|
160
|
+
- app/controllers/exception_hunter/ignored_errors_controller.rb
|
161
|
+
- app/controllers/exception_hunter/resolved_errors_controller.rb
|
144
162
|
- app/helpers/exception_hunter/application_helper.rb
|
145
163
|
- app/helpers/exception_hunter/errors_helper.rb
|
164
|
+
- app/helpers/exception_hunter/sessions_helper.rb
|
146
165
|
- app/jobs/exception_hunter/application_job.rb
|
166
|
+
- app/jobs/exception_hunter/send_notification_job.rb
|
147
167
|
- app/mailers/exception_hunter/application_mailer.rb
|
148
168
|
- app/models/exception_hunter/application_record.rb
|
149
169
|
- app/models/exception_hunter/error.rb
|
150
170
|
- app/models/exception_hunter/error_group.rb
|
171
|
+
- app/presenters/exception_hunter/dashboard_presenter.rb
|
172
|
+
- app/presenters/exception_hunter/error_group_presenter.rb
|
151
173
|
- app/presenters/exception_hunter/error_presenter.rb
|
152
|
-
- app/
|
174
|
+
- app/views/exception_hunter/devise/sessions/new.html.erb
|
153
175
|
- app/views/exception_hunter/errors/_error_backtrace.erb
|
176
|
+
- app/views/exception_hunter/errors/_error_row.erb
|
154
177
|
- app/views/exception_hunter/errors/_error_summary.erb
|
155
178
|
- app/views/exception_hunter/errors/_error_user_data.erb
|
179
|
+
- app/views/exception_hunter/errors/_errors_table.erb
|
180
|
+
- app/views/exception_hunter/errors/_last_7_days_errors_table.erb
|
156
181
|
- app/views/exception_hunter/errors/index.html.erb
|
157
182
|
- app/views/exception_hunter/errors/pagy/_pagy_nav.html.erb
|
158
183
|
- app/views/exception_hunter/errors/show.html.erb
|
159
184
|
- app/views/layouts/exception_hunter/application.html.erb
|
160
|
-
-
|
185
|
+
- app/views/layouts/exception_hunter/exception_hunter_logged_out.html.erb
|
161
186
|
- config/rails_best_practices.yml
|
162
187
|
- config/routes.rb
|
163
188
|
- lib/exception_hunter.rb
|
164
189
|
- lib/exception_hunter/config.rb
|
190
|
+
- lib/exception_hunter/data_redacter.rb
|
191
|
+
- lib/exception_hunter/devise.rb
|
165
192
|
- lib/exception_hunter/engine.rb
|
193
|
+
- lib/exception_hunter/error_creator.rb
|
194
|
+
- lib/exception_hunter/error_reaper.rb
|
195
|
+
- lib/exception_hunter/middleware/delayed_job_hunter.rb
|
166
196
|
- lib/exception_hunter/middleware/request_hunter.rb
|
167
197
|
- lib/exception_hunter/middleware/sidekiq_hunter.rb
|
198
|
+
- lib/exception_hunter/notifiers/misconfigured_notifiers.rb
|
199
|
+
- lib/exception_hunter/notifiers/slack_notifier.rb
|
200
|
+
- lib/exception_hunter/notifiers/slack_notifier_serializer.rb
|
201
|
+
- lib/exception_hunter/tracking.rb
|
168
202
|
- lib/exception_hunter/user_attributes_collector.rb
|
169
203
|
- lib/exception_hunter/version.rb
|
170
204
|
- lib/generators/exception_hunter/create_users/create_users_generator.rb
|
@@ -179,7 +213,7 @@ homepage: https://github.com/rootstrap/exception_hunter
|
|
179
213
|
licenses:
|
180
214
|
- MIT
|
181
215
|
metadata: {}
|
182
|
-
post_install_message:
|
216
|
+
post_install_message:
|
183
217
|
rdoc_options: []
|
184
218
|
require_paths:
|
185
219
|
- lib
|
@@ -195,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
195
229
|
version: '0'
|
196
230
|
requirements: []
|
197
231
|
rubygems_version: 3.0.8
|
198
|
-
signing_key:
|
232
|
+
signing_key:
|
199
233
|
specification_version: 4
|
200
234
|
summary: Exception tracking engine
|
201
235
|
test_files: []
|
@@ -1,41 +0,0 @@
|
|
1
|
-
module ExceptionHunter
|
2
|
-
class ErrorCreator
|
3
|
-
class << self
|
4
|
-
def call(**error_attrs)
|
5
|
-
ActiveRecord::Base.transaction do
|
6
|
-
error_attrs = extract_user_data(error_attrs)
|
7
|
-
error = Error.new(error_attrs)
|
8
|
-
error_group = ErrorGroup.find_matching_group(error) || ErrorGroup.new
|
9
|
-
update_error_group(error_group, error)
|
10
|
-
error.error_group = error_group
|
11
|
-
error.save!
|
12
|
-
error
|
13
|
-
end
|
14
|
-
rescue ActiveRecord::RecordInvalid
|
15
|
-
false
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def update_error_group(error_group, error)
|
21
|
-
error_group.error_class_name = error.class_name
|
22
|
-
error_group.message = error.message
|
23
|
-
|
24
|
-
error_group.save!
|
25
|
-
end
|
26
|
-
|
27
|
-
def extract_user_data(**error_attrs)
|
28
|
-
user = error_attrs[:user]
|
29
|
-
error_attrs[:user_data] =
|
30
|
-
if user.nil?
|
31
|
-
{}
|
32
|
-
else
|
33
|
-
UserAttributesCollector.collect_attributes(user)
|
34
|
-
end
|
35
|
-
|
36
|
-
error_attrs.delete(:user)
|
37
|
-
error_attrs
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|