exception_hunter 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +29 -6
- data/app/assets/stylesheets/exception_hunter/base.css +62 -8
- data/app/assets/stylesheets/exception_hunter/errors.css +163 -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 +27 -4
- data/app/controllers/exception_hunter/resolved_errors_controller.rb +11 -0
- data/app/helpers/exception_hunter/sessions_helper.rb +16 -0
- data/app/models/exception_hunter/application_record.rb +8 -0
- data/app/models/exception_hunter/error.rb +20 -7
- data/app/models/exception_hunter/error_group.rb +23 -4
- data/app/presenters/exception_hunter/dashboard_presenter.rb +54 -0
- data/app/presenters/exception_hunter/error_group_presenter.rb +25 -0
- data/app/presenters/exception_hunter/error_presenter.rb +1 -0
- data/app/views/exception_hunter/devise/sessions/new.html.erb +24 -0
- data/app/views/exception_hunter/errors/_error_row.erb +44 -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 +71 -30
- 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 +65 -6
- data/app/views/layouts/exception_hunter/exception_hunter_logged_out.html.erb +24 -0
- data/config/rails_best_practices.yml +2 -2
- data/config/routes.rb +19 -1
- data/lib/exception_hunter.rb +11 -1
- data/lib/exception_hunter/config.rb +8 -1
- data/lib/exception_hunter/devise.rb +17 -0
- data/{app/services → lib}/exception_hunter/error_creator.rb +15 -3
- data/lib/exception_hunter/error_reaper.rb +12 -0
- data/lib/exception_hunter/middleware/request_hunter.rb +1 -0
- data/lib/exception_hunter/middleware/sidekiq_hunter.rb +1 -0
- data/lib/exception_hunter/tracking.rb +16 -0
- data/lib/exception_hunter/user_attributes_collector.rb +4 -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 +23 -0
- data/lib/tasks/exception_hunter_tasks.rake +6 -4
- metadata +18 -5
- data/config/initializers/exception_hunter.rb +0 -16
@@ -1,17 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
<span class="page prev"><%== link.call(pagy.prev, '◄', 'aria-label="previous"') %></span>
|
5
|
-
<% else %>
|
6
|
-
<span class="page prev disabled">
|
7
|
-
◄
|
8
|
-
</span>
|
1
|
+
<div class="error-occurrences__nav">
|
2
|
+
<%= link_to pagy_url_for(1, pagy) do %>
|
3
|
+
<%= button_tag 'First', class: %w[button button-outline error-occurrences__nav-link], disabled: pagy.prev.nil? %>
|
9
4
|
<% end %>
|
10
|
-
|
11
|
-
<
|
12
|
-
<% else %>
|
13
|
-
<span class="page next disabled">
|
14
|
-
►
|
15
|
-
</span>
|
5
|
+
<%= link_to pagy_url_for(pagy.prev, pagy) do %>
|
6
|
+
<%= button_tag '<', class: %w[button button-outline error-occurrences__nav-link], disabled: pagy.prev.nil? %>
|
16
7
|
<% end %>
|
17
|
-
|
8
|
+
<div class="error-occurrences__nav-current">
|
9
|
+
<%= occurred_at %> (<%= pagy.page %>/<%= pagy.last %>)
|
10
|
+
</div>
|
11
|
+
<%= link_to pagy_url_for(pagy.next, pagy) do %>
|
12
|
+
<%= button_tag '>', class: %w[button button-outline error-occurrences__nav-link], disabled: pagy.next.nil? %>
|
13
|
+
<% end %>
|
14
|
+
<%= link_to pagy_url_for(pagy.last, pagy) do %>
|
15
|
+
<%= button_tag 'Last', class: %w[button button-outline error-occurrences__nav-link], disabled: pagy.next.nil? %>
|
16
|
+
<% end %>
|
17
|
+
</div>
|
@@ -1,33 +1,69 @@
|
|
1
|
-
<div class="row">
|
2
|
-
<div class="column column-
|
3
|
-
|
1
|
+
<div class="row error-occurrence__header">
|
2
|
+
<div class="column column-10">
|
3
|
+
<% @error.tags.each do|tag| %>
|
4
|
+
<div class="error-cell__tags">
|
5
|
+
<span class="error-tag"><%= tag %></span>
|
6
|
+
</div>
|
7
|
+
<% end %>
|
8
|
+
</div>
|
9
|
+
<div class="column column-80">
|
10
|
+
<div class="error-title">
|
11
|
+
<%= @error.class_name %>: <%= @error.message %>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
<div class="column column-10">
|
15
|
+
<%= button_to('Resolve', resolved_errors_path(error_group: { id: @error.error_group_id }),
|
16
|
+
method: :post,
|
17
|
+
class: %w[button resolve-button],
|
18
|
+
data: { confirm: 'Are you sure you want to resolve this error?' }) %>
|
4
19
|
</div>
|
5
20
|
</div>
|
6
21
|
|
7
|
-
<div class="
|
8
|
-
|
9
|
-
|
10
|
-
<
|
11
|
-
|
22
|
+
<div class="row">
|
23
|
+
<div class="column column-50">
|
24
|
+
<ul data-tabs class="errors-tabs">
|
25
|
+
<li class="errors-tab">
|
26
|
+
<a data-tabby-default href="#summary">
|
27
|
+
<div class="errors-tab__content">
|
28
|
+
Summary
|
29
|
+
</div>
|
30
|
+
</a>
|
31
|
+
</li>
|
32
|
+
<li class="errors-tab">
|
33
|
+
<a href="#backtrace">
|
34
|
+
<div class="errors-tab__content">
|
35
|
+
Backtrace
|
36
|
+
</div>
|
37
|
+
</a>
|
38
|
+
</li>
|
39
|
+
<li class="errors-tab">
|
40
|
+
<a href="#user-data">
|
41
|
+
<div class="errors-tab__content">
|
42
|
+
User Data
|
43
|
+
</div>
|
44
|
+
</a>
|
45
|
+
</li>
|
46
|
+
</ul>
|
47
|
+
</div>
|
48
|
+
<div class="column column-50">
|
49
|
+
<%= render partial: 'exception_hunter/errors/pagy/pagy_nav', locals: { pagy: @pagy, occurred_at: @error.occurred_at } %>
|
50
|
+
</div>
|
12
51
|
</div>
|
13
52
|
|
14
|
-
<ul data-tabs>
|
15
|
-
<li><a data-tabby-default href="#summary">Summary</a></li>
|
16
|
-
<li><a href="#backtrace">Backtrace</a></li>
|
17
|
-
<li><a href="#user-data">User Data</a></li>
|
18
|
-
</ul>
|
19
53
|
|
20
|
-
<div class="
|
21
|
-
<div
|
22
|
-
|
23
|
-
|
54
|
+
<div class="errors__container">
|
55
|
+
<div class="tab-content">
|
56
|
+
<div id="summary">
|
57
|
+
<%= render partial: 'exception_hunter/errors/error_summary', locals: { error: @error } %>
|
58
|
+
</div>
|
24
59
|
|
25
|
-
|
26
|
-
|
27
|
-
|
60
|
+
<div id="backtrace">
|
61
|
+
<%= render partial: 'exception_hunter/errors/error_backtrace', locals: { error: @error } %>
|
62
|
+
</div>
|
28
63
|
|
29
|
-
|
30
|
-
|
64
|
+
<div id="user-data">
|
65
|
+
<%= render partial: 'exception_hunter/errors/error_user_data', locals: { error: @error } %>
|
66
|
+
</div>
|
31
67
|
</div>
|
32
68
|
</div>
|
33
69
|
|
@@ -7,12 +7,11 @@
|
|
7
7
|
|
8
8
|
<%= favicon_link_tag 'exception_hunter/logo.png' %>
|
9
9
|
|
10
|
-
<link rel="stylesheet" href="
|
10
|
+
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
11
11
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css">
|
12
12
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.css">
|
13
13
|
|
14
14
|
<!-- Get patch fixes within a minor version -->
|
15
|
-
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/cferdinandi/tabby@12.0/dist/css/tabby-ui.min.css">
|
16
15
|
<script src="https://cdn.jsdelivr.net/gh/cferdinandi/tabby@12.0/dist/js/tabby.polyfills.min.js"></script>
|
17
16
|
|
18
17
|
<%= stylesheet_link_tag "exception_hunter/application", media: "all" %>
|
@@ -22,18 +21,78 @@
|
|
22
21
|
<div class="wrapper">
|
23
22
|
<nav class="nav">
|
24
23
|
<div class="container container__nav">
|
25
|
-
<div class="
|
26
|
-
|
27
|
-
<%=
|
28
|
-
|
24
|
+
<div class="row">
|
25
|
+
<div class="column column-40">
|
26
|
+
<%= link_to errors_path do %>
|
27
|
+
<div class="nav__title">Exception Hunter</div>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
<div class="column column-10 column-offset-50">
|
31
|
+
<div class="logout">
|
32
|
+
<% if current_admin_user? %>
|
33
|
+
<div class="sign_out">
|
34
|
+
<%= link_to exception_hunter_logout_path do %>
|
35
|
+
Sign out
|
36
|
+
<% end %>
|
37
|
+
</div>
|
38
|
+
<% end %>
|
39
|
+
</div>
|
40
|
+
</div>
|
29
41
|
</div>
|
30
42
|
</div>
|
31
43
|
</nav>
|
32
44
|
|
33
45
|
<div class="container">
|
46
|
+
<% if flash[:notice] %>
|
47
|
+
<div id="flash-notice" class="flash flash--notice row">
|
48
|
+
<div class="column column-80">
|
49
|
+
<%= flash[:notice] %>
|
50
|
+
</div>
|
51
|
+
<div class="column column-20">
|
52
|
+
<div class="button button-clear button-dismiss" data-dismiss="#flash-notice">
|
53
|
+
<%= ['Cool!', 'Nice!', 'Ok', 'Dismiss', 'Fine. Whatever.', 'I know that'].sample %>
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
<% end %>
|
58
|
+
|
34
59
|
<%= yield %>
|
35
60
|
</div>
|
61
|
+
|
62
|
+
<div class="footer">
|
63
|
+
Made with ♥ at <a href="https://www.rootstrap.com/" class="text--underline">Rootstrap</a>
|
64
|
+
</div>
|
36
65
|
</div>
|
37
66
|
|
67
|
+
<script type="text/javascript" charset="utf-8">
|
68
|
+
function confirmable(element) {
|
69
|
+
const message = element.getAttribute('data-confirm')
|
70
|
+
element.form.addEventListener('submit', confirm(message))
|
71
|
+
|
72
|
+
function confirm(message) {
|
73
|
+
return function (event) {
|
74
|
+
if (!window.confirm(message)) {
|
75
|
+
event.preventDefault()
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
function dismissible(element) {
|
82
|
+
element.addEventListener('click', dismiss(element))
|
83
|
+
|
84
|
+
function dismiss(element) {
|
85
|
+
return function () {
|
86
|
+
const selector = element.getAttribute('data-dismiss')
|
87
|
+
const elementToDismiss = document.querySelector(selector)
|
88
|
+
elementToDismiss.remove()
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
Array.from(document.querySelectorAll('[data-confirm]')).forEach(confirmable)
|
94
|
+
Array.from(document.querySelectorAll('[data-dismiss]')).forEach(dismissible)
|
95
|
+
</script>
|
96
|
+
|
38
97
|
</body>
|
39
98
|
</html>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Exception Hunter</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
|
8
|
+
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
|
9
|
+
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css">
|
10
|
+
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.css">
|
11
|
+
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
12
|
+
|
13
|
+
<!-- Get patch fixes within a minor version -->
|
14
|
+
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/cferdinandi/tabby@12.0/dist/css/tabby-ui.min.css">
|
15
|
+
<script src="https://cdn.jsdelivr.net/gh/cferdinandi/tabby@12.0/dist/js/tabby.polyfills.min.js"></script>
|
16
|
+
|
17
|
+
<%= stylesheet_link_tag "exception_hunter/application", media: "all" %>
|
18
|
+
</head>
|
19
|
+
<body style="background-color: #E5E5E5;">
|
20
|
+
<div class="container">
|
21
|
+
<%= yield %>
|
22
|
+
</div>
|
23
|
+
</body>
|
24
|
+
</html>
|
@@ -16,7 +16,7 @@ MoveFinderToNamedScopeCheck: { }
|
|
16
16
|
MoveModelLogicIntoModelCheck: { use_count: 4 }
|
17
17
|
NeedlessDeepNestingCheck: { nested_count: 2 }
|
18
18
|
NotUseDefaultRouteCheck: { }
|
19
|
-
NotUseTimeAgoInWordsCheck: { ignored_files: ['index.html.erb'] }
|
19
|
+
#NotUseTimeAgoInWordsCheck: { ignored_files: ['index.html.erb'] }
|
20
20
|
OveruseRouteCustomizationsCheck: { customize_count: 3 }
|
21
21
|
ProtectMassAssignmentCheck: { }
|
22
22
|
RemoveEmptyHelpersCheck: { }
|
@@ -29,7 +29,7 @@ ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
|
|
29
29
|
ReplaceInstanceVariableWithLocalVariableCheck: { }
|
30
30
|
RestrictAutoGeneratedRoutesCheck: { }
|
31
31
|
SimplifyRenderInControllersCheck: { }
|
32
|
-
SimplifyRenderInViewsCheck: { }
|
32
|
+
#SimplifyRenderInViewsCheck: { }
|
33
33
|
#UseBeforeFilterCheck: { customize_count: 2 }
|
34
34
|
UseModelAssociationCheck: { }
|
35
35
|
UseMultipartAlternativeAsContentTypeOfEmailCheck: { }
|
data/config/routes.rb
CHANGED
@@ -1,3 +1,21 @@
|
|
1
1
|
ExceptionHunter::Engine.routes.draw do
|
2
|
-
resources :errors, only: %i[index show]
|
2
|
+
resources :errors, only: %i[index show] do
|
3
|
+
delete 'purge', on: :collection, to: 'errors#destroy', as: :purge
|
4
|
+
end
|
5
|
+
|
6
|
+
resources :resolved_errors, only: %i[create]
|
7
|
+
|
8
|
+
get '/', to: redirect('/exception_hunter/errors')
|
9
|
+
|
10
|
+
if ExceptionHunter::Config.auth_enabled?
|
11
|
+
admin_user_class = ExceptionHunter::Config.admin_user_class.underscore.to_sym
|
12
|
+
|
13
|
+
devise_scope admin_user_class do
|
14
|
+
get '/login', to: 'devise/sessions#new', as: :exception_hunter_login
|
15
|
+
post '/login', to: 'devise/sessions#create', as: :exception_hunter_create_session
|
16
|
+
get '/logout', to: 'devise/sessions#destroy', as: :exception_hunter_logout
|
17
|
+
end
|
18
|
+
|
19
|
+
devise_for admin_user_class, only: []
|
20
|
+
end
|
3
21
|
end
|
data/lib/exception_hunter.rb
CHANGED
@@ -1,16 +1,26 @@
|
|
1
|
+
require 'pagy'
|
2
|
+
|
1
3
|
require 'exception_hunter/engine'
|
2
4
|
require 'exception_hunter/middleware/request_hunter'
|
3
5
|
require 'exception_hunter/middleware/sidekiq_hunter' if defined?(Sidekiq)
|
4
6
|
require 'exception_hunter/config'
|
7
|
+
require 'exception_hunter/error_creator'
|
8
|
+
require 'exception_hunter/error_reaper'
|
9
|
+
require 'exception_hunter/tracking'
|
5
10
|
require 'exception_hunter/user_attributes_collector'
|
6
|
-
require 'pagy'
|
7
11
|
|
8
12
|
module ExceptionHunter
|
13
|
+
autoload :Devise, 'exception_hunter/devise'
|
14
|
+
|
15
|
+
extend ::ExceptionHunter::Tracking
|
16
|
+
|
9
17
|
def self.setup(&block)
|
10
18
|
block.call(Config)
|
11
19
|
end
|
12
20
|
|
13
21
|
def self.routes(router)
|
22
|
+
return unless Config.enabled
|
23
|
+
|
14
24
|
router.mount(ExceptionHunter::Engine, at: 'exception_hunter')
|
15
25
|
end
|
16
26
|
end
|
@@ -1,5 +1,12 @@
|
|
1
1
|
module ExceptionHunter
|
2
2
|
class Config
|
3
|
-
cattr_accessor :
|
3
|
+
cattr_accessor :admin_user_class,
|
4
|
+
:current_user_method, :user_attributes
|
5
|
+
cattr_accessor :enabled, default: true
|
6
|
+
cattr_accessor :errors_stale_time, default: 45.days
|
7
|
+
|
8
|
+
def self.auth_enabled?
|
9
|
+
admin_user_class.present? && admin_user_class.try(:underscore)
|
10
|
+
end
|
4
11
|
end
|
5
12
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
module Devise
|
3
|
+
class SessionsController < ::Devise::SessionsController
|
4
|
+
skip_before_action :verify_authenticity_token
|
5
|
+
|
6
|
+
layout 'exception_hunter/exception_hunter_logged_out'
|
7
|
+
|
8
|
+
def after_sign_out_path_for(*)
|
9
|
+
'/exception_hunter/login'
|
10
|
+
end
|
11
|
+
|
12
|
+
def after_sign_in_path_for(*)
|
13
|
+
'/exception_hunter'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,12 +1,18 @@
|
|
1
1
|
module ExceptionHunter
|
2
2
|
class ErrorCreator
|
3
|
+
HTTP_TAG = 'HTTP'.freeze
|
4
|
+
WORKER_TAG = 'Worker'.freeze
|
5
|
+
MANUAL_TAG = 'Manual'.freeze
|
6
|
+
|
3
7
|
class << self
|
4
|
-
def call(**error_attrs)
|
8
|
+
def call(tag: nil, **error_attrs)
|
9
|
+
return unless should_create?
|
10
|
+
|
5
11
|
ActiveRecord::Base.transaction do
|
6
12
|
error_attrs = extract_user_data(error_attrs)
|
7
13
|
error = Error.new(error_attrs)
|
8
14
|
error_group = ErrorGroup.find_matching_group(error) || ErrorGroup.new
|
9
|
-
update_error_group(error_group, error)
|
15
|
+
update_error_group(error_group, error, tag)
|
10
16
|
error.error_group = error_group
|
11
17
|
error.save!
|
12
18
|
error
|
@@ -17,9 +23,15 @@ module ExceptionHunter
|
|
17
23
|
|
18
24
|
private
|
19
25
|
|
20
|
-
def
|
26
|
+
def should_create?
|
27
|
+
Config.enabled
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_error_group(error_group, error, tag)
|
21
31
|
error_group.error_class_name = error.class_name
|
22
32
|
error_group.message = error.message
|
33
|
+
error_group.tags << tag unless tag.nil?
|
34
|
+
error_group.tags.uniq!
|
23
35
|
|
24
36
|
error_group.save!
|
25
37
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class ErrorReaper
|
3
|
+
class << self
|
4
|
+
def purge(stale_time: Config.errors_stale_time)
|
5
|
+
ActiveRecord::Base.transaction do
|
6
|
+
Error.with_occurrences_before(Date.today - stale_time).destroy_all
|
7
|
+
ErrorGroup.without_errors.destroy_all
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
module Tracking
|
3
|
+
def track(exception, custom_data: {}, user: nil)
|
4
|
+
ErrorCreator.call(
|
5
|
+
tag: ErrorCreator::MANUAL_TAG,
|
6
|
+
class_name: exception.class.to_s,
|
7
|
+
message: exception.message,
|
8
|
+
backtrace: exception.backtrace,
|
9
|
+
custom_data: custom_data,
|
10
|
+
user: user
|
11
|
+
)
|
12
|
+
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -3,11 +3,15 @@ module ExceptionHunter
|
|
3
3
|
extend self
|
4
4
|
|
5
5
|
def collect_attributes(user)
|
6
|
+
return unless user
|
7
|
+
|
6
8
|
attributes.reduce({}) do |data, attribute|
|
7
9
|
data.merge(attribute => user.try(attribute))
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
13
|
+
private
|
14
|
+
|
11
15
|
def attributes
|
12
16
|
Config.user_attributes
|
13
17
|
end
|