exception_hunter 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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