exception_hunter 0.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +22 -0
- data/app/assets/config/exception_hunter_manifest.js +3 -0
- data/app/assets/images/exception_hunter/logo.png +0 -0
- data/app/assets/stylesheets/exception_hunter/application.css +15 -0
- data/app/assets/stylesheets/exception_hunter/base.css +19 -0
- data/app/assets/stylesheets/exception_hunter/errors.css +84 -0
- data/app/assets/stylesheets/exception_hunter/navigation.css +21 -0
- data/app/controllers/exception_hunter/application_controller.rb +5 -0
- data/app/controllers/exception_hunter/errors_controller.rb +24 -0
- data/app/helpers/exception_hunter/application_helper.rb +5 -0
- data/app/jobs/exception_hunter/application_job.rb +4 -0
- data/app/mailers/exception_hunter/application_mailer.rb +6 -0
- data/app/models/exception_hunter/application_record.rb +5 -0
- data/app/models/exception_hunter/error.rb +26 -0
- data/app/models/exception_hunter/error_group.rb +29 -0
- data/app/presenters/exception_hunter/error_presenter.rb +34 -0
- data/app/services/exception_hunter/error_creator.rb +41 -0
- data/app/views/exception_hunter/errors/_error_backtrace.erb +20 -0
- data/app/views/exception_hunter/errors/_error_summary.erb +24 -0
- data/app/views/exception_hunter/errors/_error_user_data.erb +11 -0
- data/app/views/exception_hunter/errors/index.html.erb +36 -0
- data/app/views/exception_hunter/errors/pagy/_pagy_nav.html.erb +17 -0
- data/app/views/exception_hunter/errors/show.html.erb +36 -0
- data/app/views/layouts/exception_hunter/application.html.erb +39 -0
- data/config/initializers/exception_hunter.rb +16 -0
- data/config/rails_best_practices.yml +42 -0
- data/config/routes.rb +3 -0
- data/lib/exception_hunter.rb +15 -0
- data/lib/exception_hunter/config.rb +5 -0
- data/lib/exception_hunter/engine.rb +16 -0
- data/lib/exception_hunter/railtie.rb +11 -0
- data/lib/exception_hunter/request_hunter.rb +41 -0
- data/lib/exception_hunter/user_attributes_collector.rb +15 -0
- data/lib/exception_hunter/version.rb +3 -0
- data/lib/generators/exception_hunter/create_users/create_users_generator.rb +28 -0
- data/lib/generators/exception_hunter/install/USAGE +6 -0
- data/lib/generators/exception_hunter/install/install_generator.rb +30 -0
- data/lib/generators/exception_hunter/install/templates/create_exception_hunter_error_groups.rb.erb +14 -0
- data/lib/generators/exception_hunter/install/templates/create_exception_hunter_errors.rb.erb +19 -0
- data/lib/generators/exception_hunter/install/templates/exception_hunter.rb.erb +18 -0
- data/lib/tasks/code_analysis.rake +7 -0
- data/lib/tasks/exception_hunter_tasks.rake +4 -0
- metadata +200 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 11f6f14e231b447819eaa8d2f6b107a44ef3f7060e3485381efd5ba3206d8a01
|
4
|
+
data.tar.gz: 26d9b864d2a728317009f1c097e97a849660667e678386cfec6f79cb46b9373a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 51ced2ec4f0fd66cdc29b71ec0b8214966742848ee926cfd70aeeeee19b9b8f94e4fd03b5193676dcb27d38d2634e75fc13be070c27f6efec82a8271445fd072
|
7
|
+
data.tar.gz: 8b274f7de5f265208f3e142b33ef176f62e9586425628f6b9af1e8fae76493ac5f0b0c07aa43b4ef65355319185065c4123973adacf8f5da6b3aab671f5d92ec
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2020 Bruno Vezoli
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# ExceptionHunter
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'exception_hunter'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install exception_hunter
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'ExceptionHunter'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
load 'lib/tasks/code_analysis.rake'
|
Binary file
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,19 @@
|
|
1
|
+
:root {
|
2
|
+
--main-color: #C8193C;
|
3
|
+
--secondary-color: #F4F5F6;
|
4
|
+
--link-color: #158FEF;
|
5
|
+
--border-color: #D1D1D1;
|
6
|
+
--file-name-color: #16AF90
|
7
|
+
}
|
8
|
+
|
9
|
+
.wrapper {
|
10
|
+
margin: 5.5rem auto auto;
|
11
|
+
}
|
12
|
+
|
13
|
+
a {
|
14
|
+
color: var(--link-color);
|
15
|
+
}
|
16
|
+
|
17
|
+
a:hover, a:focus, a:active {
|
18
|
+
color: var(--main-color);
|
19
|
+
}
|
@@ -0,0 +1,84 @@
|
|
1
|
+
/*
|
2
|
+
Place all the styles related to the matching controller here.
|
3
|
+
They will automatically be included in application.css.
|
4
|
+
*/
|
5
|
+
|
6
|
+
.row.statistics-row {
|
7
|
+
padding-top: 1rem;
|
8
|
+
}
|
9
|
+
|
10
|
+
.statistics__cell {
|
11
|
+
color: var(--main-color);
|
12
|
+
background-color: var(--secondary-color);
|
13
|
+
border: 1px solid var(--border-color);
|
14
|
+
height: 5rem;
|
15
|
+
border-radius: 10px;
|
16
|
+
display: flex;
|
17
|
+
align-items: center;
|
18
|
+
justify-content: center;
|
19
|
+
font-size: 2.2rem;
|
20
|
+
}
|
21
|
+
|
22
|
+
.row.error-row {
|
23
|
+
padding-top: 1rem;
|
24
|
+
padding-bottom: 1rem;
|
25
|
+
border-bottom: 1px solid;
|
26
|
+
}
|
27
|
+
|
28
|
+
.row.error-row-no-border {
|
29
|
+
padding-top: 1rem;
|
30
|
+
padding-bottom: 1rem;
|
31
|
+
}
|
32
|
+
|
33
|
+
.error-row.error-row--header {
|
34
|
+
font-weight: bold;
|
35
|
+
}
|
36
|
+
|
37
|
+
.error-cell.error-cell--highlight {
|
38
|
+
color: var(--main-color);
|
39
|
+
}
|
40
|
+
|
41
|
+
.error-title {
|
42
|
+
font-size: 20px;
|
43
|
+
border-bottom: 1px solid var(--border-color);
|
44
|
+
}
|
45
|
+
|
46
|
+
.error-occurred_at {
|
47
|
+
color: var(--main-color);
|
48
|
+
font-size: 14px;
|
49
|
+
margin-top: 0.5em;
|
50
|
+
margin-bottom: 2em;
|
51
|
+
}
|
52
|
+
|
53
|
+
.tab-content {
|
54
|
+
padding: 1em 0.5em;
|
55
|
+
}
|
56
|
+
|
57
|
+
.data-title {
|
58
|
+
font-weight: bold;
|
59
|
+
color: var(--main-color);
|
60
|
+
}
|
61
|
+
|
62
|
+
.backtrace {
|
63
|
+
padding-top: 1em;
|
64
|
+
padding-bottom: 1em;
|
65
|
+
overflow-x: scroll;
|
66
|
+
}
|
67
|
+
|
68
|
+
.backtrace-line {
|
69
|
+
white-space: nowrap;
|
70
|
+
margin-top: .3em;
|
71
|
+
margin-bottom: .3em;
|
72
|
+
display: flex;
|
73
|
+
font-family: "Courier New", Courier, monospace;
|
74
|
+
font-size: 14px;
|
75
|
+
}
|
76
|
+
|
77
|
+
.backtrace-line__line-number {
|
78
|
+
margin-right: 5px;
|
79
|
+
color: var(--main-color);
|
80
|
+
}
|
81
|
+
|
82
|
+
.backtrace-line__file-name {
|
83
|
+
color: var(--file-name-color);
|
84
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
.nav {
|
2
|
+
background: var(--secondary-color);
|
3
|
+
border-bottom: .1rem solid var(--border-color);
|
4
|
+
display: block;
|
5
|
+
height: 5.2rem;
|
6
|
+
left: 0;
|
7
|
+
max-width: 100%;
|
8
|
+
position: fixed;
|
9
|
+
right: 0;
|
10
|
+
top: 0;
|
11
|
+
width: 100%;
|
12
|
+
z-index: 1;
|
13
|
+
}
|
14
|
+
.container__nav {
|
15
|
+
display: flex;
|
16
|
+
height: 100%;
|
17
|
+
}
|
18
|
+
|
19
|
+
.nav__logo img {
|
20
|
+
height: 100%;
|
21
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_dependency 'exception_hunter/application_controller'
|
2
|
+
|
3
|
+
module ExceptionHunter
|
4
|
+
class ErrorsController < ApplicationController
|
5
|
+
include Pagy::Backend
|
6
|
+
|
7
|
+
def index
|
8
|
+
@errors = ErrorGroup.all.order(created_at: :desc)
|
9
|
+
@errors_count = Error.count
|
10
|
+
@month_errors = Error.in_current_month.count
|
11
|
+
end
|
12
|
+
|
13
|
+
def show
|
14
|
+
@pagy, errors = pagy(most_recent_errors, items: 1)
|
15
|
+
@error = ErrorPresenter.new(errors.first)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def most_recent_errors
|
21
|
+
Error.most_recent(params[:id])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class Error < ApplicationRecord
|
3
|
+
validates :class_name, presence: true
|
4
|
+
validates :occurred_at, presence: true
|
5
|
+
|
6
|
+
belongs_to :error_group
|
7
|
+
|
8
|
+
before_validation :set_occurred_at, on: :create
|
9
|
+
|
10
|
+
scope :most_recent, lambda { |error_group_id|
|
11
|
+
where(error_group_id: error_group_id).order(occurred_at: :desc)
|
12
|
+
}
|
13
|
+
|
14
|
+
def self.in_current_month
|
15
|
+
current_month = Date.today.beginning_of_month..Date.today.end_of_month
|
16
|
+
|
17
|
+
where(occurred_at: current_month)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def set_occurred_at
|
23
|
+
self.occurred_at ||= Time.now
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class ErrorGroup < ApplicationRecord
|
3
|
+
SIMILARITY_THRESHOLD = 0.75
|
4
|
+
|
5
|
+
validates :error_class_name, presence: true
|
6
|
+
|
7
|
+
has_many :grouped_errors, class_name: 'ExceptionHunter::Error'
|
8
|
+
|
9
|
+
scope :most_similar, lambda { |message|
|
10
|
+
quoted_message = ActiveRecord::Base.connection.quote_string(message)
|
11
|
+
where("similarity(exception_hunter_error_groups.message, :message) >= #{SIMILARITY_THRESHOLD}", message: message)
|
12
|
+
.order(Arel.sql("similarity(exception_hunter_error_groups.message, '#{quoted_message}') DESC"))
|
13
|
+
}
|
14
|
+
|
15
|
+
def self.find_matching_group(error)
|
16
|
+
where(error_class_name: error.class_name)
|
17
|
+
.most_similar(error.message.to_s)
|
18
|
+
.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def last_occurrence
|
22
|
+
@last_occurrence ||= grouped_errors.maximum(:occurred_at)
|
23
|
+
end
|
24
|
+
|
25
|
+
def total_occurrences
|
26
|
+
@total_occurrences ||= grouped_errors.count
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class ErrorPresenter
|
3
|
+
delegate_missing_to :error
|
4
|
+
|
5
|
+
BacktraceLine = Struct.new(:path, :file_name, :line_number, :method_call)
|
6
|
+
|
7
|
+
def initialize(error)
|
8
|
+
@error = error
|
9
|
+
end
|
10
|
+
|
11
|
+
def backtrace
|
12
|
+
error.backtrace.map do |line|
|
13
|
+
format_backtrace_line(line)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :error
|
20
|
+
|
21
|
+
def format_backtrace_line(line)
|
22
|
+
matches = line.match(%r{(?<path>.*)/(?<file_name>[^:]*):(?<line_number>\d*).*`(?<method_call>.*)'})
|
23
|
+
|
24
|
+
if matches.nil?
|
25
|
+
line
|
26
|
+
else
|
27
|
+
BacktraceLine.new(matches[:path],
|
28
|
+
matches[:file_name],
|
29
|
+
matches[:line_number],
|
30
|
+
matches[:method_call])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
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
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<% if error.backtrace.empty? %>
|
2
|
+
Unfortunately, no backtrace has been registered for this error.
|
3
|
+
<% else %>
|
4
|
+
<div class="backtrace">
|
5
|
+
<% error.backtrace.each do |line| %>
|
6
|
+
<div class="backtrace-line">
|
7
|
+
<% if line.is_a?(String) %>
|
8
|
+
<%= line %>
|
9
|
+
<% else %>
|
10
|
+
<div class="backtrace-line__path"><%= line.path %></div>
|
11
|
+
/
|
12
|
+
<div class="backtrace-line__file-name"><%= line.file_name %></div>
|
13
|
+
:
|
14
|
+
<div class="backtrace-line__line-number"><%= line.line_number %></div>
|
15
|
+
<div class="backtrace-line__method-call"><%= line.method_call %></div>
|
16
|
+
<% end %>
|
17
|
+
</div>
|
18
|
+
<% end %>
|
19
|
+
</div>
|
20
|
+
<% end %>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<% if error.environment_data.empty? %>
|
2
|
+
<div class="row error-row-no-border data-title">
|
3
|
+
Unfortunately, no environment information has been registered for this error.
|
4
|
+
</div>
|
5
|
+
<% else %>
|
6
|
+
<div class="row error-row-no-border data-title">
|
7
|
+
Environment Data
|
8
|
+
</div>
|
9
|
+
<% error.environment_data.each do |key, value| %>
|
10
|
+
<b><%= key %></b>: <%= value %><br>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
<% if error.custom_data.nil? %>
|
14
|
+
<div class="row error-row-no-border data-title">
|
15
|
+
No custom data included.
|
16
|
+
</div>
|
17
|
+
<% else %>
|
18
|
+
<div class="row error-row-no-border data-title">
|
19
|
+
Custom Data
|
20
|
+
</div>
|
21
|
+
<% error.custom_data.each do |key, value| %>
|
22
|
+
<b><%= key %></b>: <%= value || 'None' %> <br>
|
23
|
+
<% end %>
|
24
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% if error.user_data.empty? %>
|
2
|
+
<div class="data-title">
|
3
|
+
Unfortunately, no user information has been registered for this error.
|
4
|
+
</div>
|
5
|
+
<% else %>
|
6
|
+
<% error.user_data.transform_keys(&:to_sym).each do |key, value| %>
|
7
|
+
<div class="column column-15">
|
8
|
+
<b><%= key %></b>: <%= value %>
|
9
|
+
</div>
|
10
|
+
<% end %>
|
11
|
+
<% end %>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<div class="row statistics-row">
|
2
|
+
<div class="column column-offset-50 column-25">
|
3
|
+
<div class="statistics__cell">
|
4
|
+
<%= number_with_delimiter(@errors_count) %> Total errors
|
5
|
+
</div>
|
6
|
+
</div>
|
7
|
+
<div class="column column-offset-50 column-25">
|
8
|
+
<div class="statistics__cell">
|
9
|
+
<%= number_with_delimiter(@month_errors) %> Errors this month
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div class="row error-row error-row--header">
|
15
|
+
<div class="column column-75">Message</div>
|
16
|
+
<div class="column column-15">Last Occurrence</div>
|
17
|
+
<div class="column column-10">Occurrences</div>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<% @errors.each do |error| %>
|
21
|
+
<div class="row error-row">
|
22
|
+
<div class="column column-75 error-cell">
|
23
|
+
<%= link_to error.message, error_path(error.id) %>
|
24
|
+
</div>
|
25
|
+
<div class="column column-15 error-cell error-cell--highlight">
|
26
|
+
<% if error.last_occurrence.present? %>
|
27
|
+
<%= time_ago_in_words(error.last_occurrence) %> ago
|
28
|
+
<% else %>
|
29
|
+
Never
|
30
|
+
<% end %>
|
31
|
+
</div>
|
32
|
+
<div class="column column-10 error-cell error-cell--highlight">
|
33
|
+
<%= error.total_occurrences %>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
<% end %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<% link = pagy_link_proc(pagy) %>
|
2
|
+
<nav aria-label="pager" class="pagy_nav pagination" role="navigation">
|
3
|
+
<% if pagy.prev %>
|
4
|
+
<span class="page prev"><%== link.call(pagy.prev, '◄', 'aria-label="previous"') %></span>
|
5
|
+
<% else %>
|
6
|
+
<span class="page prev disabled">
|
7
|
+
◄
|
8
|
+
</span>
|
9
|
+
<% end %>
|
10
|
+
<% if pagy.next %>
|
11
|
+
<span class="page next"><%== link.call(pagy.next, '►', 'aria-label="next"') %></span>
|
12
|
+
<% else %>
|
13
|
+
<span class="page next disabled">
|
14
|
+
►
|
15
|
+
</span>
|
16
|
+
<% end %>
|
17
|
+
</nav>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<div class="row">
|
2
|
+
<div class="column column-offset-80 column-20">
|
3
|
+
<%= render partial: 'exception_hunter/errors/pagy/pagy_nav', locals: { pagy: @pagy } %>
|
4
|
+
</div>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<div class="error-title">
|
8
|
+
<%= @error.class_name %>: <%= @error.message %>
|
9
|
+
</div>
|
10
|
+
<div class="error-occurred_at">
|
11
|
+
<%= @error.occurred_at %>
|
12
|
+
</div>
|
13
|
+
|
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
|
+
|
20
|
+
<div class="tab-content">
|
21
|
+
<div id="summary">
|
22
|
+
<%= render partial: 'exception_hunter/errors/error_summary', locals: { error: @error } %>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div id="backtrace">
|
26
|
+
<%= render partial: 'exception_hunter/errors/error_backtrace', locals: { error: @error } %>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div id="user-data">
|
30
|
+
<%= render partial: 'exception_hunter/errors/error_user_data', locals: { error: @error } %>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<script type="text/javascript" charset="utf-8">
|
35
|
+
const tabs = new Tabby('[data-tabs]')
|
36
|
+
</script>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Exception Hunter</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
|
8
|
+
<%= favicon_link_tag 'exception_hunter/logo.png' %>
|
9
|
+
|
10
|
+
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
|
11
|
+
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css">
|
12
|
+
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.css">
|
13
|
+
|
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
|
+
<script src="https://cdn.jsdelivr.net/gh/cferdinandi/tabby@12.0/dist/js/tabby.polyfills.min.js"></script>
|
17
|
+
|
18
|
+
<%= stylesheet_link_tag "exception_hunter/application", media: "all" %>
|
19
|
+
</head>
|
20
|
+
<body>
|
21
|
+
|
22
|
+
<div class="wrapper">
|
23
|
+
<nav class="nav">
|
24
|
+
<div class="container container__nav">
|
25
|
+
<div class="nav__logo">
|
26
|
+
<%= link_to errors_path do %>
|
27
|
+
<%= image_tag 'exception_hunter/logo.png', alt: 'Logo' %>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</nav>
|
32
|
+
|
33
|
+
<div class="container">
|
34
|
+
<%= yield %>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
</body>
|
39
|
+
</html>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
ExceptionHunter.setup do |config|
|
2
|
+
# == Current User
|
3
|
+
#
|
4
|
+
# Exception Hunter will include the user as part of the environment
|
5
|
+
# data, if it was to be available. The default configuration uses devise
|
6
|
+
# :current_user method. You can change it in case
|
7
|
+
#
|
8
|
+
config.current_user_method = :current_user
|
9
|
+
|
10
|
+
# == Current User Attributes
|
11
|
+
#
|
12
|
+
# Exception Hunter will try to include the attributes defined here
|
13
|
+
# as part of the user information that is kept from the request.
|
14
|
+
#
|
15
|
+
config.user_attributes = [:id, :email]
|
16
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
AddModelVirtualAttributeCheck: { }
|
2
|
+
AlwaysAddDbIndexCheck: { }
|
3
|
+
#CheckSaveReturnValueCheck: { }
|
4
|
+
#CheckDestroyReturnValueCheck: { }
|
5
|
+
DefaultScopeIsEvilCheck: { }
|
6
|
+
DryBundlerInCapistranoCheck: { }
|
7
|
+
#HashSyntaxCheck: { }
|
8
|
+
IsolateSeedDataCheck: { }
|
9
|
+
KeepFindersOnTheirOwnModelCheck: { }
|
10
|
+
LawOfDemeterCheck: { }
|
11
|
+
#LongLineCheck: { max_line_length: 80 }
|
12
|
+
MoveCodeIntoControllerCheck: { }
|
13
|
+
MoveCodeIntoHelperCheck: { array_count: 3 }
|
14
|
+
MoveCodeIntoModelCheck: { use_count: 2 }
|
15
|
+
MoveFinderToNamedScopeCheck: { }
|
16
|
+
MoveModelLogicIntoModelCheck: { use_count: 4 }
|
17
|
+
NeedlessDeepNestingCheck: { nested_count: 2 }
|
18
|
+
NotRescueExceptionCheck: { ignored_files: 'request_hunter.rb' }
|
19
|
+
NotUseDefaultRouteCheck: { }
|
20
|
+
NotUseTimeAgoInWordsCheck: { ignored_files: ['index.html.erb'] }
|
21
|
+
OveruseRouteCustomizationsCheck: { customize_count: 3 }
|
22
|
+
ProtectMassAssignmentCheck: { }
|
23
|
+
RemoveEmptyHelpersCheck: { }
|
24
|
+
#RemoveTabCheck: { }
|
25
|
+
RemoveTrailingWhitespaceCheck: { }
|
26
|
+
RemoveUnusedMethodsInControllersCheck: { except_methods: [] }
|
27
|
+
RemoveUnusedMethodsInHelpersCheck: { except_methods: [] }
|
28
|
+
RemoveUnusedMethodsInModelsCheck: { except_methods: [] }
|
29
|
+
ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
|
30
|
+
ReplaceInstanceVariableWithLocalVariableCheck: { }
|
31
|
+
RestrictAutoGeneratedRoutesCheck: { }
|
32
|
+
SimplifyRenderInControllersCheck: { }
|
33
|
+
SimplifyRenderInViewsCheck: { }
|
34
|
+
#UseBeforeFilterCheck: { customize_count: 2 }
|
35
|
+
UseModelAssociationCheck: { }
|
36
|
+
UseMultipartAlternativeAsContentTypeOfEmailCheck: { }
|
37
|
+
#UseParenthesesInMethodDefCheck: { }
|
38
|
+
UseObserverCheck: { }
|
39
|
+
UseQueryAttributeCheck: { }
|
40
|
+
UseSayWithTimeInMigrationsCheck: { }
|
41
|
+
UseScopeAccessCheck: { }
|
42
|
+
UseTurboSprocketsRails3Check: { }
|
data/config/routes.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'exception_hunter/engine'
|
2
|
+
require 'exception_hunter/railtie'
|
3
|
+
require 'exception_hunter/config'
|
4
|
+
require 'exception_hunter/user_attributes_collector'
|
5
|
+
require 'pagy'
|
6
|
+
|
7
|
+
module ExceptionHunter
|
8
|
+
def self.setup(&block)
|
9
|
+
block.call(Config)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.routes(router)
|
13
|
+
router.mount(ExceptionHunter::Engine, at: 'exception_hunter')
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace ExceptionHunter
|
4
|
+
|
5
|
+
config.generators do |gen|
|
6
|
+
gen.test_framework :rspec
|
7
|
+
gen.fixture_replacement :factory_bot
|
8
|
+
gen.factory_bot dir: 'spec/factories'
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer 'exception_hunter.precompile', group: :all do |app|
|
12
|
+
app.config.assets.precompile << 'exception_hunter/application.css'
|
13
|
+
app.config.assets.precompile << 'exception_hunter/logo.png'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_dependency 'exception_hunter/request_hunter'
|
2
|
+
|
3
|
+
module ExceptionHunter
|
4
|
+
class Railtie < Rails::Railtie
|
5
|
+
initializer 'exception_hunter.add_middleware', after: :load_config_initializers do |app|
|
6
|
+
app.config.middleware.insert_after(
|
7
|
+
ActionDispatch::DebugExceptions, ExceptionHunter::RequestHunter
|
8
|
+
)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class RequestHunter
|
3
|
+
ENVIRONMENT_KEYS =
|
4
|
+
%w[PATH_INFO QUERY_STRING REMOTE_HOST REQUEST_METHOD REQUEST_URI
|
5
|
+
SERVER_PROTOCOL HTTP_HOST HTTP_USER_AGENT].freeze
|
6
|
+
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
@app.call(env)
|
13
|
+
rescue Exception => exception
|
14
|
+
catch_prey(env, exception)
|
15
|
+
raise exception
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def catch_prey(env, exception)
|
21
|
+
user = user_from_env(env)
|
22
|
+
ErrorCreator.call(
|
23
|
+
class_name: exception.class.to_s,
|
24
|
+
message: exception.message,
|
25
|
+
environment_data: environment_data(env),
|
26
|
+
backtrace: exception.backtrace,
|
27
|
+
user: user
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def environment_data(env)
|
32
|
+
env.select { |key, _value| ENVIRONMENT_KEYS.include?(key) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def user_from_env(env)
|
36
|
+
current_user_method = Config.current_user_method
|
37
|
+
controller = env['action_controller.instance']
|
38
|
+
controller.try(current_user_method)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
module UserAttributesCollector
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def collect_attributes(user)
|
6
|
+
attributes.reduce({}) do |data, attribute|
|
7
|
+
data.merge(attribute => user.try(attribute))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def attributes
|
12
|
+
Config.user_attributes
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ExceptionHunter
|
2
|
+
class CreateUsersGenerator < Rails::Generators::NamedBase
|
3
|
+
argument :name, type: :string, default: 'AdminUser'
|
4
|
+
|
5
|
+
def install_devise
|
6
|
+
begin
|
7
|
+
require 'devise'
|
8
|
+
rescue LoadError
|
9
|
+
log :error, 'Please install devise and require add it to your gemfile or run with --skip-users'
|
10
|
+
exit(false)
|
11
|
+
end
|
12
|
+
|
13
|
+
initializer_file =
|
14
|
+
File.join(destination_root, 'config', 'initializers', 'devise.rb')
|
15
|
+
|
16
|
+
if File.exist?(initializer_file)
|
17
|
+
log :generate, 'No need to install devise, already done.'
|
18
|
+
else
|
19
|
+
log :generate, 'devise:install'
|
20
|
+
invoke 'devise:install'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_admin_user
|
25
|
+
invoke 'devise', [name]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
module ExceptionHunter
|
4
|
+
class InstallGenerator < ActiveRecord::Generators::Base
|
5
|
+
source_root File.expand_path('templates', __dir__)
|
6
|
+
|
7
|
+
argument :name, type: :string, default: 'AdminUser'
|
8
|
+
hook_for :users, default: 'create_users', desc: 'Admin user generator to run. Skip with --skip-users'
|
9
|
+
|
10
|
+
def copy_initializer
|
11
|
+
@underscored_user_name = name.underscore.gsub('/', '_')
|
12
|
+
@use_authentication_method = options[:users].present?
|
13
|
+
template 'exception_hunter.rb.erb', 'config/initializers/exception_hunter.rb'
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_routes
|
17
|
+
if options[:users]
|
18
|
+
inject_into_file 'config/routes.rb', "\n ExceptionHunter.routes(self)", after: /devise_for .*/
|
19
|
+
else
|
20
|
+
route 'ExceptionHunter.routes(self)'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_migrations
|
25
|
+
migration_template 'create_exception_hunter_error_groups.rb.erb',
|
26
|
+
'db/migrate/create_exception_hunter_error_groups.rb'
|
27
|
+
migration_template 'create_exception_hunter_errors.rb.erb', 'db/migrate/create_exception_hunter_errors.rb'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/generators/exception_hunter/install/templates/create_exception_hunter_error_groups.rb.erb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateExceptionHunterErrorGroups < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version.to_s %>]
|
2
|
+
def change
|
3
|
+
enable_extension :pg_trgm
|
4
|
+
|
5
|
+
create_table :exception_hunter_error_groups do |t|
|
6
|
+
t.string :error_class_name, null: false
|
7
|
+
t.string :message
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
|
11
|
+
t.index :message, opclass: :gin_trgm_ops, using: :gin
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateExceptionHunterErrors < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version.to_s %>]
|
2
|
+
def change
|
3
|
+
create_table :exception_hunter_errors do |t|
|
4
|
+
t.string :class_name, null: false
|
5
|
+
t.string :message
|
6
|
+
t.timestamp :occurred_at, null: false
|
7
|
+
t.json :environment_data
|
8
|
+
t.json :custom_data
|
9
|
+
t.json :user_data
|
10
|
+
t.string :backtrace, array: true, default: []
|
11
|
+
|
12
|
+
t.belongs_to :error_group,
|
13
|
+
index: true,
|
14
|
+
foreign_key: { to_table: :exception_hunter_error_groups }
|
15
|
+
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
ExceptionHunter.setup do |config|
|
2
|
+
# == Current User
|
3
|
+
#
|
4
|
+
# Exception Hunter will include the user as part of the environment
|
5
|
+
# data, if it was to be available. The default configuration uses devise
|
6
|
+
# :current_user method. You can change it in case you named your user model
|
7
|
+
# in some other way (i.e. Member). You can also remove the configuration if
|
8
|
+
# you don't wish to track user data.
|
9
|
+
#
|
10
|
+
config.current_user_method = :current_user
|
11
|
+
|
12
|
+
# == Current User Attributes
|
13
|
+
#
|
14
|
+
# Exception Hunter will try to include the attributes defined here
|
15
|
+
# as part of the user information that is kept from the request.
|
16
|
+
#
|
17
|
+
config.user_attributes = [:id, :email]
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: exception_hunter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bruno Vezoli
|
8
|
+
- Tiziana Romani
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2020-05-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: pagy
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '3.8'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '3.8'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: brakeman
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '4.8'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '4.8'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: factory_bot_rails
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: pg
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rails_best_practices
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '1.20'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '1.20'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: reek
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '5.6'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '5.6'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rubocop
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 0.80.1
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 0.80.1
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: simplecov
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 0.18.5
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.18.5
|
126
|
+
description:
|
127
|
+
email:
|
128
|
+
- bruno.vezoli@rootstrap.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- MIT-LICENSE
|
134
|
+
- README.md
|
135
|
+
- Rakefile
|
136
|
+
- app/assets/config/exception_hunter_manifest.js
|
137
|
+
- app/assets/images/exception_hunter/logo.png
|
138
|
+
- app/assets/stylesheets/exception_hunter/application.css
|
139
|
+
- app/assets/stylesheets/exception_hunter/base.css
|
140
|
+
- app/assets/stylesheets/exception_hunter/errors.css
|
141
|
+
- app/assets/stylesheets/exception_hunter/navigation.css
|
142
|
+
- app/controllers/exception_hunter/application_controller.rb
|
143
|
+
- app/controllers/exception_hunter/errors_controller.rb
|
144
|
+
- app/helpers/exception_hunter/application_helper.rb
|
145
|
+
- app/jobs/exception_hunter/application_job.rb
|
146
|
+
- app/mailers/exception_hunter/application_mailer.rb
|
147
|
+
- app/models/exception_hunter/application_record.rb
|
148
|
+
- app/models/exception_hunter/error.rb
|
149
|
+
- app/models/exception_hunter/error_group.rb
|
150
|
+
- app/presenters/exception_hunter/error_presenter.rb
|
151
|
+
- app/services/exception_hunter/error_creator.rb
|
152
|
+
- app/views/exception_hunter/errors/_error_backtrace.erb
|
153
|
+
- app/views/exception_hunter/errors/_error_summary.erb
|
154
|
+
- app/views/exception_hunter/errors/_error_user_data.erb
|
155
|
+
- app/views/exception_hunter/errors/index.html.erb
|
156
|
+
- app/views/exception_hunter/errors/pagy/_pagy_nav.html.erb
|
157
|
+
- app/views/exception_hunter/errors/show.html.erb
|
158
|
+
- app/views/layouts/exception_hunter/application.html.erb
|
159
|
+
- config/initializers/exception_hunter.rb
|
160
|
+
- config/rails_best_practices.yml
|
161
|
+
- config/routes.rb
|
162
|
+
- lib/exception_hunter.rb
|
163
|
+
- lib/exception_hunter/config.rb
|
164
|
+
- lib/exception_hunter/engine.rb
|
165
|
+
- lib/exception_hunter/railtie.rb
|
166
|
+
- lib/exception_hunter/request_hunter.rb
|
167
|
+
- lib/exception_hunter/user_attributes_collector.rb
|
168
|
+
- lib/exception_hunter/version.rb
|
169
|
+
- lib/generators/exception_hunter/create_users/create_users_generator.rb
|
170
|
+
- lib/generators/exception_hunter/install/USAGE
|
171
|
+
- lib/generators/exception_hunter/install/install_generator.rb
|
172
|
+
- lib/generators/exception_hunter/install/templates/create_exception_hunter_error_groups.rb.erb
|
173
|
+
- lib/generators/exception_hunter/install/templates/create_exception_hunter_errors.rb.erb
|
174
|
+
- lib/generators/exception_hunter/install/templates/exception_hunter.rb.erb
|
175
|
+
- lib/tasks/code_analysis.rake
|
176
|
+
- lib/tasks/exception_hunter_tasks.rake
|
177
|
+
homepage: https://github.com/rootstrap/exception_hunter
|
178
|
+
licenses:
|
179
|
+
- MIT
|
180
|
+
metadata: {}
|
181
|
+
post_install_message:
|
182
|
+
rdoc_options: []
|
183
|
+
require_paths:
|
184
|
+
- lib
|
185
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: 2.5.5
|
190
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
requirements: []
|
196
|
+
rubygems_version: 3.0.8
|
197
|
+
signing_key:
|
198
|
+
specification_version: 4
|
199
|
+
summary: Exception tracking engine
|
200
|
+
test_files: []
|