solid_errors 0.2.17 → 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 +111 -14
- data/app/controllers/solid_errors/application_controller.rb +3 -0
- data/app/controllers/solid_errors/errors_controller.rb +13 -12
- data/app/models/solid_errors/backtrace.rb +3 -3
- data/app/models/solid_errors/backtrace_line.rb +17 -16
- data/app/models/solid_errors/error.rb +1 -1
- data/app/models/solid_errors/occurrence.rb +3 -3
- data/app/models/solid_errors/record.rb +1 -1
- data/app/views/layouts/solid_errors/application.html.erb +136 -127
- data/app/views/solid_errors/errors/_error.html.erb +1 -1
- data/app/views/solid_errors/errors/show.html.erb +2 -1
- data/app/views/solid_errors/occurrences/_occurrence.html.erb +2 -2
- data/config/routes.rb +1 -1
- data/lib/generators/solid_errors/install/install_generator.rb +4 -4
- data/lib/solid_errors/sanitizer.rb +30 -29
- data/lib/solid_errors/subscriber.rb +20 -21
- data/lib/solid_errors/version.rb +1 -1
- data/lib/solid_errors.rb +16 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dabfc29afbf8683a9f79db87fc193679fa3074415e3a68b2d0d0088bfd0d99d5
|
4
|
+
data.tar.gz: 6124021da4ef6ea9823138cc264d2bcdd07d2496c2638ea3ff7ceee852975b2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7859b1b2ced0cb2e44f2351d9645bb6ffa3cc88451d5bccd6d6ec74eee10c698dfd39eeccc1e5efeed5505b173a85417d3008977dad0c12afccb5a8ba3039e04
|
7
|
+
data.tar.gz: 87100e0396f8b6630a41889516e32da08f7cbd94ee7da6b45e17c67a0726bd49aa4f77a59df05fbbbcc5c916105cb4911f99c2400d34c4e89f5c9e76a7db2f93
|
data/README.md
CHANGED
@@ -1,28 +1,125 @@
|
|
1
|
-
#
|
1
|
+
# Solid Errors
|
2
|
+
|
3
|
+
<p>
|
4
|
+
<a href="https://rubygems.org/gems/solid_errors">
|
5
|
+
<img alt="GEM Version" src="https://img.shields.io/gem/v/solid_errors?color=168AFE&include_prereleases&logo=ruby&logoColor=FE1616">
|
6
|
+
</a>
|
7
|
+
<a href="https://rubygems.org/gems/solid_errors">
|
8
|
+
<img alt="GEM Downloads" src="https://img.shields.io/gem/dt/solid_errors?color=168AFE&logo=ruby&logoColor=FE1616">
|
9
|
+
</a>
|
10
|
+
<a href="https://github.com/testdouble/standard">
|
11
|
+
<img alt="Ruby Style" src="https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616" />
|
12
|
+
</a>
|
13
|
+
<a href="https://github.com/fractaledmind/solid_errors/actions/workflows/main.yml">
|
14
|
+
<img alt="Tests" src="https://github.com/fractaledmind/solid_errors/actions/workflows/main.yml/badge.svg" />
|
15
|
+
</a>
|
16
|
+
<a href="https://github.com/sponsors/fractaledmind">
|
17
|
+
<img alt="Sponsors" src="https://img.shields.io/github/sponsors/fractaledmind?color=eb4aaa&logo=GitHub%20Sponsors" />
|
18
|
+
</a>
|
19
|
+
<a href="https://ruby.social/@fractaledmind">
|
20
|
+
<img alt="Ruby.Social Follow" src="https://img.shields.io/mastodon/follow/109291299520066427?domain=https%3A%2F%2Fruby.social&label=%40fractaledmind&style=social">
|
21
|
+
</a>
|
22
|
+
<a href="https://twitter.com/fractaledmind">
|
23
|
+
<img alt="Twitter Follow" src="https://img.shields.io/twitter/url?label=%40fractaledmind&style=social&url=https%3A%2F%2Ftwitter.com%2Ffractaledmind">
|
24
|
+
</a>
|
25
|
+
</p>
|
26
|
+
|
27
|
+
|
28
|
+
Solid Errors is a DB-based, app-internal exception tracker for Rails applications, designed with simplicity and performance in mind. It uses the new [Rails error reporting API](https://guides.rubyonrails.org/error_reporting.html) to store uncaught exceptions in the database, and provides a simple UI for viewing and managing exceptions.
|
2
29
|
|
3
|
-
|
30
|
+
## Installation
|
4
31
|
|
5
|
-
|
32
|
+
Install the gem and add to the application's Gemfile by executing:
|
6
33
|
|
7
|
-
|
34
|
+
$ bundle add solid_errors
|
35
|
+
|
36
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
37
|
+
|
38
|
+
$ gem install solid_errors
|
39
|
+
|
40
|
+
After installing the gem, run the installer:
|
41
|
+
|
42
|
+
$ rails generate solid_errors:install
|
43
|
+
|
44
|
+
This will copy the required migration over to your app.
|
45
|
+
|
46
|
+
Then mount the engine in your config/routes.rb file
|
47
|
+
|
48
|
+
mount SolidErrors::Engine, at: "/solid_errors"
|
49
|
+
|
50
|
+
> [!NOTE]
|
51
|
+
> Be sure to [secure the dashboard](#authentication) in production.
|
52
|
+
|
53
|
+
## Usage
|
54
|
+
|
55
|
+
All exceptions are recorded automatically. No additional code required.
|
56
|
+
|
57
|
+
Please consult the [official guides](https://guides.rubyonrails.org/error_reporting.html) for an introduction to the error reporting API.
|
58
|
+
|
59
|
+
### Configuration
|
60
|
+
|
61
|
+
You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, only 3 configuration options are available:
|
62
|
+
|
63
|
+
* `connects_to` - The database configuration to use for the Solid Errors database. See [Database Configuration](#database-configuration) for more information.
|
64
|
+
* `username` - The username to use for HTTP authentication. See [Authentication](#authentication) for more information.
|
65
|
+
* `password` - The password to use for HTTP authentication. See [Authentication](#authentication) for more information.
|
66
|
+
|
67
|
+
#### Database Configuration
|
68
|
+
|
69
|
+
`config.solid_errors.connects_to` takes a custom database configuration hash that will be used in the abstract `SolidErrors::Record` Active Record model. This is required to use a different database than the main app. For example:
|
8
70
|
|
9
|
-
|
71
|
+
```ruby
|
72
|
+
# Use a single separate DB for Solid Errors
|
73
|
+
config.solid_errors.connects_to = { database: { writing: :solid_errors, reading: :solid_errors } }
|
74
|
+
```
|
75
|
+
|
76
|
+
or
|
10
77
|
|
11
78
|
```ruby
|
12
|
-
|
79
|
+
# Use a separate primary/replica pair for Solid Errors
|
80
|
+
config.solid_errors.connects_to = { database: { writing: :solid_errors_primary, reading: :solid_errors_replica } }
|
13
81
|
```
|
14
82
|
|
15
|
-
|
83
|
+
#### Authentication
|
16
84
|
|
17
|
-
|
85
|
+
Solid Errors does not restrict access out of the box. You must secure the dashboard yourself. However, it does provide basic HTTP authentication that can be used with basic authentication or Devise. All you need to do is setup a username and password.
|
18
86
|
|
19
|
-
|
87
|
+
There are two ways to setup a username and password. First, you can use the `SOLIDERRORS_USERNAME` and `SOLIDERRORS_PASSWORD` environment variables:
|
20
88
|
|
21
|
-
|
89
|
+
```ruby
|
90
|
+
ENV["SOLIDERRORS_USERNAME"] = "frodo"
|
91
|
+
ENV["SOLIDERRORS_PASSWORD"] = "ikeptmysecrets"
|
92
|
+
```
|
22
93
|
|
23
|
-
|
94
|
+
Second, you can set the `SolidErrors.username` and `SolidErrors.password` variables in an initializer:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# Set authentication credentials for Solid Errors
|
98
|
+
config.solid_errors.username = Rails.application.credentials.solid_errors.username
|
99
|
+
config.solid_errors.password = Rails.application.credentials.solid_errors.password
|
100
|
+
```
|
101
|
+
|
102
|
+
Either way, if you have set a username and password, Solid Errors will use basic HTTP authentication. If you have not set a username and password, Solid Errors will not require any authentication to view the dashboard.
|
103
|
+
|
104
|
+
If you use Devise for authenctication in your app, you can also restrict access to the dashboard by using their `authenticate` contraint in your routes file:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
authenticate :user, -> (user) { user.admin? } do
|
108
|
+
mount SolidErrors::Engine, at: "/solid_errors"
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### Examples
|
113
|
+
|
114
|
+
There are only two screens in the dashboard.
|
115
|
+
|
116
|
+
* the index view of all unresolved errors:
|
117
|
+
|
118
|
+
![image description](images/index-screenshot.png)
|
119
|
+
|
120
|
+
* and the show view of a particular error:
|
24
121
|
|
25
|
-
|
122
|
+
![image description](images/show-screenshot.png)
|
26
123
|
|
27
124
|
## Development
|
28
125
|
|
@@ -32,7 +129,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
129
|
|
33
130
|
## Contributing
|
34
131
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
132
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fractaledmind/solid_errors. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/fractaledmind/solid_errors/blob/main/CODE_OF_CONDUCT.md).
|
36
133
|
|
37
134
|
## License
|
38
135
|
|
@@ -40,4 +137,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
40
137
|
|
41
138
|
## Code of Conduct
|
42
139
|
|
43
|
-
Everyone interacting in the SolidErrors project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
140
|
+
Everyone interacting in the SolidErrors project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/fractaledmind/solid_errors/blob/main/CODE_OF_CONDUCT.md).
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module SolidErrors
|
2
2
|
class ErrorsController < ApplicationController
|
3
|
-
before_action :set_error, only: %i[
|
3
|
+
before_action :set_error, only: %i[show update]
|
4
4
|
|
5
5
|
# GET /errors
|
6
6
|
def index
|
@@ -8,10 +8,10 @@ module SolidErrors
|
|
8
8
|
occurrences_table = Occurrence.arel_table
|
9
9
|
|
10
10
|
@errors = Error.unresolved
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
.joins(:occurrences)
|
12
|
+
.select(errors_table[Arel.star], occurrences_table[:created_at].maximum.as("recent_occurrence"))
|
13
|
+
.group(errors_table[:id])
|
14
|
+
.order(recent_occurrence: :desc)
|
15
15
|
end
|
16
16
|
|
17
17
|
# GET /errors/1
|
@@ -25,13 +25,14 @@ module SolidErrors
|
|
25
25
|
end
|
26
26
|
|
27
27
|
private
|
28
|
-
# Only allow a list of trusted parameters through.
|
29
|
-
def error_params
|
30
|
-
params.require(:error).permit(:resolved_at)
|
31
|
-
end
|
32
28
|
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
# Only allow a list of trusted parameters through.
|
30
|
+
def error_params
|
31
|
+
params.require(:error).permit(:resolved_at)
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_error
|
35
|
+
@error = Error.find(params[:id])
|
36
|
+
end
|
36
37
|
end
|
37
38
|
end
|
@@ -10,7 +10,7 @@ module SolidErrors
|
|
10
10
|
BacktraceLine.parse(unparsed_line.to_s, opts)
|
11
11
|
end.compact
|
12
12
|
|
13
|
-
|
13
|
+
new(lines)
|
14
14
|
end
|
15
15
|
|
16
16
|
def initialize(lines)
|
@@ -22,9 +22,9 @@ module SolidErrors
|
|
22
22
|
#
|
23
23
|
# Returns array containing backtrace lines.
|
24
24
|
def to_ary
|
25
|
-
lines.take(1000).map { |l| {
|
25
|
+
lines.take(1000).map { |l| {number: l.filtered_number, file: l.filtered_file, method: l.filtered_method, source: l.source} }
|
26
26
|
end
|
27
|
-
|
27
|
+
alias_method :to_a, :to_ary
|
28
28
|
|
29
29
|
# JSON support.
|
30
30
|
#
|
@@ -2,14 +2,14 @@ module SolidErrors
|
|
2
2
|
class BacktraceLine
|
3
3
|
# Backtrace line regexp (optionally allowing leading X: for windows support).
|
4
4
|
INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
|
5
|
-
STRING_EMPTY =
|
6
|
-
GEM_ROOT =
|
7
|
-
PROJECT_ROOT =
|
5
|
+
STRING_EMPTY = "".freeze
|
6
|
+
GEM_ROOT = "[GEM_ROOT]".freeze
|
7
|
+
PROJECT_ROOT = "[PROJECT_ROOT]".freeze
|
8
8
|
PROJECT_ROOT_CACHE = {}
|
9
9
|
GEM_ROOT_CACHE = {}
|
10
10
|
RELATIVE_ROOT = Regexp.new('^\.\/').freeze
|
11
11
|
RAILS_ROOT = ::Rails.root.to_s.dup.freeze
|
12
|
-
ROOT_REGEXP = Regexp.new("^#{
|
12
|
+
ROOT_REGEXP = Regexp.new("^#{Regexp.escape(RAILS_ROOT)}").freeze
|
13
13
|
BACKTRACE_FILTERS = [
|
14
14
|
lambda { |line|
|
15
15
|
return line unless defined?(Gem)
|
@@ -47,21 +47,19 @@ module SolidErrors
|
|
47
47
|
file, number, method = match[1], match[2], match[3]
|
48
48
|
filtered_args = [fmatch[1], fmatch[2], fmatch[3]]
|
49
49
|
new(file, number, method, *filtered_args, opts.fetch(:source_radius, 2))
|
50
|
-
else
|
51
|
-
nil
|
52
50
|
end
|
53
51
|
end
|
54
52
|
|
55
53
|
def initialize(file, number, method, filtered_file = file,
|
56
|
-
|
57
|
-
|
58
|
-
self.filtered_file
|
54
|
+
filtered_number = number, filtered_method = method,
|
55
|
+
source_radius = 2)
|
56
|
+
self.filtered_file = filtered_file
|
59
57
|
self.filtered_number = filtered_number
|
60
58
|
self.filtered_method = filtered_method
|
61
|
-
self.file
|
62
|
-
self.number
|
63
|
-
self.method
|
64
|
-
self.source_radius
|
59
|
+
self.file = file
|
60
|
+
self.number = number
|
61
|
+
self.method = method
|
62
|
+
self.source_radius = source_radius
|
65
63
|
end
|
66
64
|
|
67
65
|
# Reconstructs the line in a readable fashion.
|
@@ -74,7 +72,7 @@ module SolidErrors
|
|
74
72
|
end
|
75
73
|
|
76
74
|
def inspect
|
77
|
-
"<Line:#{
|
75
|
+
"<Line:#{self}>"
|
78
76
|
end
|
79
77
|
|
80
78
|
# Determines if this line is part of the application trace or not.
|
@@ -105,8 +103,11 @@ module SolidErrors
|
|
105
103
|
|
106
104
|
l = 0
|
107
105
|
File.open(file) do |f|
|
108
|
-
start.times {
|
109
|
-
|
106
|
+
start.times {
|
107
|
+
f.gets
|
108
|
+
l += 1
|
109
|
+
}
|
110
|
+
return duration.times.map { (line = f.gets) ? [(l += 1), line] : nil }.compact.to_h
|
110
111
|
end
|
111
112
|
end
|
112
113
|
end
|
@@ -27,7 +27,7 @@ module SolidErrors
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def badge_classes
|
30
|
-
"px-2 inline-flex text-
|
30
|
+
"px-2 inline-flex text-sm font-semibold rounded-md #{SEVERITY_TO_BADGE_CLASSES[severity.to_sym]}"
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -95,8 +95,8 @@
|
|
95
95
|
*/
|
96
96
|
|
97
97
|
abbr:where([title]) {
|
98
|
-
|
99
|
-
text-decoration:
|
98
|
+
text-decoration-line: underline;
|
99
|
+
text-decoration-style: dotted;
|
100
100
|
}
|
101
101
|
|
102
102
|
/*
|
@@ -578,12 +578,6 @@
|
|
578
578
|
}
|
579
579
|
}
|
580
580
|
|
581
|
-
.bi {
|
582
|
-
fill: currentColor;
|
583
|
-
stroke-width: 0.5;
|
584
|
-
stroke: currentColor;
|
585
|
-
}
|
586
|
-
|
587
581
|
.sr-only{
|
588
582
|
position: absolute;
|
589
583
|
width: 1px;
|
@@ -596,14 +590,26 @@
|
|
596
590
|
border-width: 0
|
597
591
|
}
|
598
592
|
|
599
|
-
.
|
600
|
-
position:
|
593
|
+
.fixed{
|
594
|
+
position: fixed
|
601
595
|
}
|
602
596
|
|
603
597
|
.relative{
|
604
598
|
position: relative
|
605
599
|
}
|
606
600
|
|
601
|
+
.left-0{
|
602
|
+
left: 0px
|
603
|
+
}
|
604
|
+
|
605
|
+
.right-0{
|
606
|
+
right: 0px
|
607
|
+
}
|
608
|
+
|
609
|
+
.top-0{
|
610
|
+
top: 0px
|
611
|
+
}
|
612
|
+
|
607
613
|
.-mx-2{
|
608
614
|
margin-left: -0.5rem;
|
609
615
|
margin-right: -0.5rem
|
@@ -618,10 +624,6 @@
|
|
618
624
|
margin-bottom: 0.75rem
|
619
625
|
}
|
620
626
|
|
621
|
-
.mb-36{
|
622
|
-
margin-bottom: 9rem
|
623
|
-
}
|
624
|
-
|
625
627
|
.ml-6{
|
626
628
|
margin-left: 1.5rem
|
627
629
|
}
|
@@ -634,37 +636,8 @@
|
|
634
636
|
margin-top: 1rem
|
635
637
|
}
|
636
638
|
|
637
|
-
.
|
638
|
-
|
639
|
-
}
|
640
|
-
|
641
|
-
.select-none{
|
642
|
-
user-select: none
|
643
|
-
}
|
644
|
-
|
645
|
-
.overflow-auto{
|
646
|
-
overflow: auto
|
647
|
-
}
|
648
|
-
|
649
|
-
.rounded-b-lg{
|
650
|
-
border-bottom-right-radius: 0.5rem;
|
651
|
-
border-bottom-left-radius: 0.5rem
|
652
|
-
}
|
653
|
-
|
654
|
-
.leading-normal{
|
655
|
-
line-height: 1.5
|
656
|
-
}
|
657
|
-
|
658
|
-
.mt-4{
|
659
|
-
margin-top: 1rem
|
660
|
-
}
|
661
|
-
|
662
|
-
.\!block{
|
663
|
-
display: block !important
|
664
|
-
}
|
665
|
-
|
666
|
-
.block{
|
667
|
-
display: block
|
639
|
+
.inline-block{
|
640
|
+
display: inline-block
|
668
641
|
}
|
669
642
|
|
670
643
|
.flex{
|
@@ -675,10 +648,6 @@
|
|
675
648
|
display: inline-flex
|
676
649
|
}
|
677
650
|
|
678
|
-
.flex-col{
|
679
|
-
flex-direction: column
|
680
|
-
}
|
681
|
-
|
682
651
|
.table{
|
683
652
|
display: table
|
684
653
|
}
|
@@ -687,12 +656,8 @@
|
|
687
656
|
display: grid
|
688
657
|
}
|
689
658
|
|
690
|
-
.
|
691
|
-
|
692
|
-
}
|
693
|
-
|
694
|
-
.w-full{
|
695
|
-
width: 100%
|
659
|
+
.min-h-full{
|
660
|
+
min-height: 100%
|
696
661
|
}
|
697
662
|
|
698
663
|
.min-w-full{
|
@@ -719,10 +684,18 @@
|
|
719
684
|
grid-template-columns: repeat(2, minmax(0, 1fr))
|
720
685
|
}
|
721
686
|
|
687
|
+
.flex-col{
|
688
|
+
flex-direction: column
|
689
|
+
}
|
690
|
+
|
722
691
|
.flex-wrap{
|
723
692
|
flex-wrap: wrap
|
724
693
|
}
|
725
694
|
|
695
|
+
.items-start{
|
696
|
+
align-items: flex-start
|
697
|
+
}
|
698
|
+
|
726
699
|
.items-center{
|
727
700
|
align-items: center
|
728
701
|
}
|
@@ -780,10 +753,18 @@
|
|
780
753
|
border-color: rgb(209 213 219 / var(--tw-divide-opacity))
|
781
754
|
}
|
782
755
|
|
756
|
+
.overflow-auto{
|
757
|
+
overflow: auto
|
758
|
+
}
|
759
|
+
|
783
760
|
.whitespace-nowrap{
|
784
761
|
white-space: nowrap
|
785
762
|
}
|
786
763
|
|
764
|
+
.whitespace-pre-wrap{
|
765
|
+
white-space: pre-wrap
|
766
|
+
}
|
767
|
+
|
787
768
|
.rounded{
|
788
769
|
border-radius: 0.25rem
|
789
770
|
}
|
@@ -792,8 +773,9 @@
|
|
792
773
|
border-radius: 0.5rem
|
793
774
|
}
|
794
775
|
|
795
|
-
.rounded-
|
796
|
-
border-radius: 0.
|
776
|
+
.rounded-b-lg{
|
777
|
+
border-bottom-right-radius: 0.5rem;
|
778
|
+
border-bottom-left-radius: 0.5rem
|
797
779
|
}
|
798
780
|
|
799
781
|
.border{
|
@@ -804,14 +786,14 @@
|
|
804
786
|
border-bottom-width: 1px
|
805
787
|
}
|
806
788
|
|
807
|
-
.border-
|
789
|
+
.border-blue-500{
|
808
790
|
--tw-border-opacity: 1;
|
809
|
-
border-color: rgb(
|
791
|
+
border-color: rgb(59 130 246 / var(--tw-border-opacity))
|
810
792
|
}
|
811
793
|
|
812
|
-
.border-
|
794
|
+
.border-gray-300{
|
813
795
|
--tw-border-opacity: 1;
|
814
|
-
border-color: rgb(
|
796
|
+
border-color: rgb(209 213 219 / var(--tw-border-opacity))
|
815
797
|
}
|
816
798
|
|
817
799
|
.bg-gray-100{
|
@@ -819,18 +801,14 @@
|
|
819
801
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity))
|
820
802
|
}
|
821
803
|
|
822
|
-
.bg-
|
804
|
+
.bg-green-50{
|
823
805
|
--tw-bg-opacity: 1;
|
824
|
-
background-color: rgb(
|
806
|
+
background-color: rgb(240 253 244 / var(--tw-bg-opacity))
|
825
807
|
}
|
826
808
|
|
827
|
-
.bg-
|
828
|
-
background-color: transparent
|
829
|
-
}
|
830
|
-
|
831
|
-
.bg-white{
|
809
|
+
.bg-red-50{
|
832
810
|
--tw-bg-opacity: 1;
|
833
|
-
background-color: rgb(
|
811
|
+
background-color: rgb(254 242 242 / var(--tw-bg-opacity))
|
834
812
|
}
|
835
813
|
|
836
814
|
.bg-slate-800{
|
@@ -838,34 +816,17 @@
|
|
838
816
|
background-color: rgb(30 41 59 / var(--tw-bg-opacity))
|
839
817
|
}
|
840
818
|
|
841
|
-
.bg-
|
842
|
-
|
843
|
-
background-color: rgb(219 234 254 / var(--tw-bg-opacity))
|
844
|
-
}
|
845
|
-
|
846
|
-
.bg-red-100{
|
847
|
-
--tw-bg-opacity: 1;
|
848
|
-
background-color: rgb(254 226 226 / var(--tw-bg-opacity))
|
819
|
+
.bg-transparent{
|
820
|
+
background-color: transparent
|
849
821
|
}
|
850
822
|
|
851
|
-
.bg-
|
823
|
+
.bg-white{
|
852
824
|
--tw-bg-opacity: 1;
|
853
|
-
background-color: rgb(
|
854
|
-
}
|
855
|
-
|
856
|
-
.text-blue-800{
|
857
|
-
--tw-text-opacity: 1;
|
858
|
-
color: rgb(30 64 175 / var(--tw-text-opacity))
|
859
|
-
}
|
860
|
-
|
861
|
-
.text-red-800{
|
862
|
-
--tw-text-opacity: 1;
|
863
|
-
color: rgb(153 27 27 / var(--tw-text-opacity))
|
825
|
+
background-color: rgb(255 255 255 / var(--tw-bg-opacity))
|
864
826
|
}
|
865
827
|
|
866
|
-
.
|
867
|
-
|
868
|
-
color: rgb(133 77 14 / var(--tw-text-opacity))
|
828
|
+
.p-2{
|
829
|
+
padding: 0.5rem
|
869
830
|
}
|
870
831
|
|
871
832
|
.p-4{
|
@@ -877,10 +838,6 @@
|
|
877
838
|
padding-right: 0px
|
878
839
|
}
|
879
840
|
|
880
|
-
.p-2{
|
881
|
-
padding: 0.5rem
|
882
|
-
}
|
883
|
-
|
884
841
|
.px-2{
|
885
842
|
padding-left: 0.5rem;
|
886
843
|
padding-right: 0.5rem
|
@@ -901,6 +858,11 @@
|
|
901
858
|
padding-bottom: 0.25rem
|
902
859
|
}
|
903
860
|
|
861
|
+
.py-2{
|
862
|
+
padding-top: 0.5rem;
|
863
|
+
padding-bottom: 0.5rem
|
864
|
+
}
|
865
|
+
|
904
866
|
.py-3{
|
905
867
|
padding-top: 0.75rem;
|
906
868
|
padding-bottom: 0.75rem
|
@@ -940,10 +902,18 @@
|
|
940
902
|
padding-top: 1.75rem
|
941
903
|
}
|
942
904
|
|
905
|
+
.rounded-md{
|
906
|
+
border-radius: 0.375rem
|
907
|
+
}
|
908
|
+
|
943
909
|
.text-left{
|
944
910
|
text-align: left
|
945
911
|
}
|
946
912
|
|
913
|
+
.text-center{
|
914
|
+
text-align: center
|
915
|
+
}
|
916
|
+
|
947
917
|
.text-right{
|
948
918
|
text-align: right
|
949
919
|
}
|
@@ -957,10 +927,6 @@
|
|
957
927
|
line-height: 2rem
|
958
928
|
}
|
959
929
|
|
960
|
-
.text-\[\.75em\]{
|
961
|
-
font-size: .75em
|
962
|
-
}
|
963
|
-
|
964
930
|
.text-base{
|
965
931
|
font-size: 1rem;
|
966
932
|
line-height: 1.5rem
|
@@ -983,9 +949,23 @@
|
|
983
949
|
font-weight: 600
|
984
950
|
}
|
985
951
|
|
986
|
-
.
|
987
|
-
|
988
|
-
|
952
|
+
.leading-normal{
|
953
|
+
line-height: 1.5
|
954
|
+
}
|
955
|
+
|
956
|
+
.bg-blue-100{
|
957
|
+
--tw-bg-opacity: 1;
|
958
|
+
background-color: rgb(219 234 254 / var(--tw-bg-opacity))
|
959
|
+
}
|
960
|
+
|
961
|
+
.bg-red-100{
|
962
|
+
--tw-bg-opacity: 1;
|
963
|
+
background-color: rgb(254 226 226 / var(--tw-bg-opacity))
|
964
|
+
}
|
965
|
+
|
966
|
+
.bg-yellow-100{
|
967
|
+
--tw-bg-opacity: 1;
|
968
|
+
background-color: rgb(254 249 195 / var(--tw-bg-opacity))
|
989
969
|
}
|
990
970
|
|
991
971
|
.text-blue-400{
|
@@ -1008,11 +988,6 @@
|
|
1008
988
|
color: rgb(75 85 99 / var(--tw-text-opacity))
|
1009
989
|
}
|
1010
990
|
|
1011
|
-
.text-gray-800{
|
1012
|
-
--tw-text-opacity: 1;
|
1013
|
-
color: rgb(31 41 55 / var(--tw-text-opacity))
|
1014
|
-
}
|
1015
|
-
|
1016
991
|
.text-gray-900{
|
1017
992
|
--tw-text-opacity: 1;
|
1018
993
|
color: rgb(17 24 39 / var(--tw-text-opacity))
|
@@ -1023,10 +998,45 @@
|
|
1023
998
|
color: rgb(34 197 94 / var(--tw-text-opacity))
|
1024
999
|
}
|
1025
1000
|
|
1001
|
+
.text-red-500{
|
1002
|
+
--tw-text-opacity: 1;
|
1003
|
+
color: rgb(239 68 68 / var(--tw-text-opacity))
|
1004
|
+
}
|
1005
|
+
|
1006
|
+
.text-blue-800{
|
1007
|
+
--tw-text-opacity: 1;
|
1008
|
+
color: rgb(30 64 175 / var(--tw-text-opacity))
|
1009
|
+
}
|
1010
|
+
|
1011
|
+
.text-red-800{
|
1012
|
+
--tw-text-opacity: 1;
|
1013
|
+
color: rgb(153 27 27 / var(--tw-text-opacity))
|
1014
|
+
}
|
1015
|
+
|
1016
|
+
.text-yellow-800{
|
1017
|
+
--tw-text-opacity: 1;
|
1018
|
+
color: rgb(133 77 14 / var(--tw-text-opacity))
|
1019
|
+
}
|
1020
|
+
|
1021
|
+
.text-white{
|
1022
|
+
--tw-text-opacity: 1;
|
1023
|
+
color: rgb(255 255 255 / var(--tw-text-opacity))
|
1024
|
+
}
|
1025
|
+
|
1026
1026
|
.underline{
|
1027
1027
|
text-decoration-line: underline
|
1028
1028
|
}
|
1029
1029
|
|
1030
|
+
.transition-opacity{
|
1031
|
+
transition-property: opacity;
|
1032
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
1033
|
+
transition-duration: 150ms
|
1034
|
+
}
|
1035
|
+
|
1036
|
+
.opacity-0{
|
1037
|
+
opacity: 0
|
1038
|
+
}
|
1039
|
+
|
1030
1040
|
.even\:bg-gray-50:nth-child(even){
|
1031
1041
|
--tw-bg-opacity: 1;
|
1032
1042
|
background-color: rgb(249 250 251 / var(--tw-bg-opacity))
|
@@ -1043,14 +1053,14 @@
|
|
1043
1053
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)
|
1044
1054
|
}
|
1045
1055
|
|
1046
|
-
.hover\:ring-
|
1056
|
+
.hover\:ring-blue-200:hover{
|
1047
1057
|
--tw-ring-opacity: 1;
|
1048
|
-
--tw-ring-color: rgb(
|
1058
|
+
--tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity))
|
1049
1059
|
}
|
1050
1060
|
|
1051
|
-
.hover\:ring-
|
1061
|
+
.hover\:ring-gray-200:hover{
|
1052
1062
|
--tw-ring-opacity: 1;
|
1053
|
-
--tw-ring-color: rgb(
|
1063
|
+
--tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity))
|
1054
1064
|
}
|
1055
1065
|
|
1056
1066
|
@media (min-width: 640px){
|
@@ -1101,21 +1111,20 @@
|
|
1101
1111
|
<% end %>
|
1102
1112
|
</div>
|
1103
1113
|
|
1104
|
-
<script>
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
}
|
1118
|
-
fadeOut(element);
|
1114
|
+
<script nonce="<%= content_security_policy_nonce %>">
|
1115
|
+
function fadeOut(element) {
|
1116
|
+
element.classList.add('transition-opacity')
|
1117
|
+
setTimeout(
|
1118
|
+
() => {
|
1119
|
+
element.classList.add('opacity-0')
|
1120
|
+
element.remove()
|
1121
|
+
},
|
1122
|
+
2000
|
1123
|
+
)
|
1124
|
+
}
|
1125
|
+
document.querySelectorAll('[data-controller="fade"]').forEach(element => {
|
1126
|
+
fadeOut(element);
|
1127
|
+
});
|
1119
1128
|
</script>
|
1120
1129
|
</body>
|
1121
1130
|
</html>
|
@@ -8,7 +8,7 @@
|
|
8
8
|
from
|
9
9
|
<em><code><%= error.source %></code></em>
|
10
10
|
</div>
|
11
|
-
<pre class="ml-6 mt-4"><%= error.message %></pre>
|
11
|
+
<pre class="whitespace-pre-wrap ml-6 mt-4"><%= error.message %></pre>
|
12
12
|
</td>
|
13
13
|
<td scope="col" class="whitespace-nowrap px-3 py-4 pt-7 text-gray-500 text-right">
|
14
14
|
<%= error.occurrences.size %>
|
@@ -10,12 +10,12 @@
|
|
10
10
|
<div class="">
|
11
11
|
<dl class="ml-6">
|
12
12
|
<% occurrence.context&.each do |key, value| %>
|
13
|
-
<div class="flex items-center justify-
|
13
|
+
<div class="flex items-center justify-start flex-wrap gap-x-2">
|
14
14
|
<dt class="font-bold">
|
15
15
|
<%= SolidErrors::Occurrence.human_attribute_name(key) %>
|
16
16
|
</dt>
|
17
17
|
<dd class="">
|
18
|
-
|
18
|
+
<code><%= value %></code>
|
19
19
|
</dd>
|
20
20
|
</div>
|
21
21
|
<% end %>
|
data/config/routes.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "rails/generators"
|
4
|
+
require "rails/generators/active_record"
|
5
5
|
|
6
6
|
module SolidErrors
|
7
7
|
#
|
@@ -13,14 +13,14 @@ module SolidErrors
|
|
13
13
|
|
14
14
|
source_root File.expand_path("templates", __dir__)
|
15
15
|
|
16
|
-
class_option :database, type: :string, aliases: %i
|
16
|
+
class_option :database, type: :string, aliases: %i[--db], desc: "The database for your migration. By default, the current environment's primary database is used."
|
17
17
|
class_option :skip_migrations, type: :boolean, default: nil, desc: "Skip migrations"
|
18
18
|
|
19
19
|
# Generates monolithic migration file that contains all database changes.
|
20
20
|
def create_migration_file
|
21
21
|
return if options[:skip_migrations]
|
22
22
|
|
23
|
-
migration_template
|
23
|
+
migration_template "create_solid_errors_tables.rb.erb", File.join(db_migrate_path, "create_solid_errors_tables.rb")
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module SolidErrors
|
2
2
|
# adapted from: https://github.com/honeybadger-io/honeybadger-ruby/blob/master/lib/honeybadger/util/sanitizer.rb
|
3
3
|
class Sanitizer
|
4
|
-
BASIC_OBJECT =
|
5
|
-
DEPTH =
|
6
|
-
RAISED =
|
7
|
-
RECURSION =
|
8
|
-
TRUNCATED =
|
4
|
+
BASIC_OBJECT = "#<BasicObject>".freeze
|
5
|
+
DEPTH = "[DEPTH]".freeze
|
6
|
+
RAISED = "[RAISED]".freeze
|
7
|
+
RECURSION = "[RECURSION]".freeze
|
8
|
+
TRUNCATED = "[TRUNCATED]".freeze
|
9
9
|
MAX_STRING_SIZE = 65536
|
10
10
|
|
11
11
|
def self.sanitize(data)
|
@@ -21,7 +21,7 @@ module SolidErrors
|
|
21
21
|
return BASIC_OBJECT if basic_object?(data)
|
22
22
|
|
23
23
|
if recursive?(data)
|
24
|
-
return RECURSION if stack
|
24
|
+
return RECURSION if stack&.include?(data.object_id)
|
25
25
|
|
26
26
|
stack = stack ? stack.dup : Set.new
|
27
27
|
stack << data.object_id
|
@@ -33,8 +33,8 @@ module SolidErrors
|
|
33
33
|
|
34
34
|
new_hash = {}
|
35
35
|
data.each do |key, value|
|
36
|
-
key = key.
|
37
|
-
value = sanitize(value, depth+1, stack)
|
36
|
+
key = key.is_a?(Symbol) ? key : sanitize(key, depth + 1, stack)
|
37
|
+
value = sanitize(value, depth + 1, stack)
|
38
38
|
new_hash[key] = value
|
39
39
|
end
|
40
40
|
new_hash
|
@@ -42,7 +42,7 @@ module SolidErrors
|
|
42
42
|
return DEPTH if depth >= max_depth
|
43
43
|
|
44
44
|
data.to_a.map do |value|
|
45
|
-
sanitize(value, depth+1, stack)
|
45
|
+
sanitize(value, depth + 1, stack)
|
46
46
|
end
|
47
47
|
when Numeric, TrueClass, FalseClass, NilClass
|
48
48
|
data
|
@@ -64,28 +64,29 @@ module SolidErrors
|
|
64
64
|
end
|
65
65
|
|
66
66
|
private
|
67
|
-
attr_reader :max_depth
|
68
|
-
|
69
|
-
def basic_object?(object)
|
70
|
-
object.respond_to?(:to_s)
|
71
|
-
false
|
72
|
-
rescue
|
73
|
-
# BasicObject doesn't respond to `#respond_to?`.
|
74
|
-
true
|
75
|
-
end
|
76
67
|
|
77
|
-
|
78
|
-
data.is_a?(Hash) || data.is_a?(Array) || data.is_a?(Set)
|
79
|
-
end
|
68
|
+
attr_reader :max_depth
|
80
69
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
70
|
+
def basic_object?(object)
|
71
|
+
object.respond_to?(:to_s)
|
72
|
+
false
|
73
|
+
rescue
|
74
|
+
# BasicObject doesn't respond to `#respond_to?`.
|
75
|
+
true
|
76
|
+
end
|
86
77
|
|
87
|
-
|
88
|
-
|
89
|
-
|
78
|
+
def recursive?(data)
|
79
|
+
data.is_a?(Hash) || data.is_a?(Array) || data.is_a?(Set)
|
80
|
+
end
|
81
|
+
|
82
|
+
def sanitize_string(string)
|
83
|
+
string.gsub!(/#<(.*?):0x.*?>/, '#<\1>') # remove object_id
|
84
|
+
return string unless string.respond_to?(:size) && string.size > MAX_STRING_SIZE
|
85
|
+
string[0...MAX_STRING_SIZE] + TRUNCATED
|
86
|
+
end
|
87
|
+
|
88
|
+
def inspected?(string)
|
89
|
+
String(string) =~ /#<.*>/
|
90
|
+
end
|
90
91
|
end
|
91
92
|
end
|
@@ -1,26 +1,25 @@
|
|
1
1
|
module SolidErrors
|
2
2
|
class Subscriber
|
3
|
-
IGNORED_ERRORS = [
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
3
|
+
IGNORED_ERRORS = ["ActionController::RoutingError",
|
4
|
+
"AbstractController::ActionNotFound",
|
5
|
+
"ActionController::MethodNotAllowed",
|
6
|
+
"ActionController::UnknownHttpMethod",
|
7
|
+
"ActionController::NotImplemented",
|
8
|
+
"ActionController::UnknownFormat",
|
9
|
+
"ActionController::InvalidAuthenticityToken",
|
10
|
+
"ActionController::InvalidCrossOriginRequest",
|
11
|
+
"ActionDispatch::Http::Parameters::ParseError",
|
12
|
+
"ActionController::BadRequest",
|
13
|
+
"ActionController::ParameterMissing",
|
14
|
+
"ActiveRecord::RecordNotFound",
|
15
|
+
"ActionController::UnknownAction",
|
16
|
+
"ActionDispatch::Http::MimeNegotiation::InvalidType",
|
17
|
+
"Rack::QueryParser::ParameterTypeError",
|
18
|
+
"Rack::QueryParser::InvalidParameterError",
|
19
|
+
"CGI::Session::CookieStore::TamperedWithCookie",
|
20
|
+
"Mongoid::Errors::DocumentNotFound",
|
21
|
+
"Sinatra::NotFound",
|
22
|
+
"Sidekiq::JobRetry::Skip"].map(&:freeze).freeze
|
24
23
|
|
25
24
|
def report(error, handled:, severity:, context:, source: nil)
|
26
25
|
return if ignore_by_class?(error.class.name)
|
data/lib/solid_errors/version.rb
CHANGED
data/lib/solid_errors.rb
CHANGED
@@ -7,4 +7,20 @@ require_relative "solid_errors/engine"
|
|
7
7
|
|
8
8
|
module SolidErrors
|
9
9
|
mattr_accessor :connects_to
|
10
|
+
mattr_accessor :username
|
11
|
+
mattr_accessor :password
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# use method instead of attr_accessor to ensure
|
15
|
+
# this works if variable set after SolidErrors is loaded
|
16
|
+
def username
|
17
|
+
@username ||= ENV["SOLIDERRORS_USERNAME"]
|
18
|
+
end
|
19
|
+
|
20
|
+
# use method instead of attr_accessor to ensure
|
21
|
+
# this works if variable set after SolidErrors is loaded
|
22
|
+
def password
|
23
|
+
@password ||= ENV["SOLIDERRORS_PASSWORD"]
|
24
|
+
end
|
25
|
+
end
|
10
26
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_errors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Margheim
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '7.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sqlite3
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
description:
|
28
42
|
email:
|
29
43
|
- stephen.margheim@gmail.com
|