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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +103 -6
  3. data/app/assets/stylesheets/exception_hunter/base.css +66 -8
  4. data/app/assets/stylesheets/exception_hunter/errors.css +188 -25
  5. data/app/assets/stylesheets/exception_hunter/navigation.css +20 -5
  6. data/app/assets/stylesheets/exception_hunter/sessions.css +71 -0
  7. data/app/controllers/concerns/exception_hunter/authorization.rb +23 -0
  8. data/app/controllers/exception_hunter/application_controller.rb +2 -0
  9. data/app/controllers/exception_hunter/errors_controller.rb +29 -4
  10. data/app/controllers/exception_hunter/ignored_errors_controller.rb +25 -0
  11. data/app/controllers/exception_hunter/resolved_errors_controller.rb +11 -0
  12. data/app/helpers/exception_hunter/application_helper.rb +22 -0
  13. data/app/helpers/exception_hunter/sessions_helper.rb +16 -0
  14. data/app/jobs/exception_hunter/send_notification_job.rb +15 -0
  15. data/app/models/exception_hunter/application_record.rb +8 -0
  16. data/app/models/exception_hunter/error.rb +24 -7
  17. data/app/models/exception_hunter/error_group.rb +24 -5
  18. data/app/presenters/exception_hunter/dashboard_presenter.rb +56 -0
  19. data/app/presenters/exception_hunter/error_group_presenter.rb +25 -0
  20. data/app/presenters/exception_hunter/error_presenter.rb +3 -2
  21. data/app/views/exception_hunter/devise/sessions/new.html.erb +24 -0
  22. data/app/views/exception_hunter/errors/_error_row.erb +52 -0
  23. data/app/views/exception_hunter/errors/_error_summary.erb +5 -5
  24. data/app/views/exception_hunter/errors/_errors_table.erb +1 -0
  25. data/app/views/exception_hunter/errors/_last_7_days_errors_table.erb +12 -0
  26. data/app/views/exception_hunter/errors/index.html.erb +84 -29
  27. data/app/views/exception_hunter/errors/pagy/_pagy_nav.html.erb +15 -15
  28. data/app/views/exception_hunter/errors/show.html.erb +58 -22
  29. data/app/views/layouts/exception_hunter/application.html.erb +67 -6
  30. data/app/views/layouts/exception_hunter/exception_hunter_logged_out.html.erb +24 -0
  31. data/config/rails_best_practices.yml +3 -3
  32. data/config/routes.rb +21 -1
  33. data/lib/exception_hunter.rb +44 -2
  34. data/lib/exception_hunter/config.rb +25 -1
  35. data/lib/exception_hunter/data_redacter.rb +27 -0
  36. data/lib/exception_hunter/devise.rb +19 -0
  37. data/lib/exception_hunter/engine.rb +6 -0
  38. data/lib/exception_hunter/error_creator.rb +72 -0
  39. data/lib/exception_hunter/error_reaper.rb +20 -0
  40. data/lib/exception_hunter/middleware/delayed_job_hunter.rb +70 -0
  41. data/lib/exception_hunter/middleware/request_hunter.rb +5 -2
  42. data/lib/exception_hunter/middleware/sidekiq_hunter.rb +1 -1
  43. data/lib/exception_hunter/notifiers/misconfigured_notifiers.rb +10 -0
  44. data/lib/exception_hunter/notifiers/slack_notifier.rb +42 -0
  45. data/lib/exception_hunter/notifiers/slack_notifier_serializer.rb +20 -0
  46. data/lib/exception_hunter/tracking.rb +35 -0
  47. data/lib/exception_hunter/user_attributes_collector.rb +21 -0
  48. data/lib/exception_hunter/version.rb +1 -1
  49. data/lib/generators/exception_hunter/create_users/create_users_generator.rb +8 -1
  50. data/lib/generators/exception_hunter/install/install_generator.rb +3 -1
  51. data/lib/generators/exception_hunter/install/templates/create_exception_hunter_error_groups.rb.erb +3 -0
  52. data/lib/generators/exception_hunter/install/templates/exception_hunter.rb.erb +52 -0
  53. data/lib/tasks/exception_hunter_tasks.rake +6 -4
  54. metadata +46 -12
  55. data/app/services/exception_hunter/error_creator.rb +0 -41
  56. 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
@@ -1,3 +1,3 @@
1
1
  module ExceptionHunter
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  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
- inject_into_file 'config/routes.rb', "\n ExceptionHunter.routes(self)", after: /devise_for .*/
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
@@ -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
@@ -1,4 +1,6 @@
1
- # desc "Explaining what the task does"
2
- # task :exception_hunter do
3
- # # Task goes here
4
- # end
1
+ namespace :exception_hunter do
2
+ desc 'Purges old errors'
3
+ task purge_errors: [:environment] do
4
+ ::ExceptionHunter::ErrorReaper.call
5
+ end
6
+ 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.2.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-18 00:00:00.000000000 Z
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.8'
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.8'
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.18.5
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.18.5
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/services/exception_hunter/error_creator.rb
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
- - config/initializers/exception_hunter.rb
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