solid_errors 0.4.3 → 0.6.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 +82 -1
- data/app/controllers/solid_errors/errors_controller.rb +19 -3
- data/app/mailers/solid_errors/error_mailer.rb +1 -1
- data/app/models/solid_errors/error.rb +26 -2
- data/app/views/layouts/solid_errors/_style.html +80 -5
- data/app/views/solid_errors/error_mailer/error_occurred.html.erb +1 -1
- data/app/views/solid_errors/errors/_actions.html.erb +5 -8
- data/app/views/solid_errors/errors/_delete_button.html.erb +7 -0
- data/app/views/solid_errors/errors/_error.html.erb +24 -21
- data/app/views/solid_errors/errors/_resolve_button.html.erb +8 -0
- data/app/views/solid_errors/errors/_row.html.erb +6 -7
- data/app/views/solid_errors/errors/index.html.erb +22 -0
- data/config/routes.rb +2 -2
- data/lib/generators/solid_errors/install/USAGE +1 -1
- data/lib/generators/solid_errors/install/install_generator.rb +15 -17
- data/lib/generators/solid_errors/install/templates/errors_schema.rb +27 -0
- data/lib/solid_errors/version.rb +1 -1
- metadata +18 -16
- data/lib/generators/solid_errors/install/templates/create_solid_errors_tables.rb.erb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb0e5daf78d8b51d88fd8e153e81c76cf7714fb622ab1257f32ae6e129e09114
|
4
|
+
data.tar.gz: 8ef8871cfa529fd24a008f0d6d9c013a741c412d731b103a4bc6e181515305c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f20c5fda185ac458cdcabd3a7d451f049144fe570c0c5c3fa5594296a832e5f217199167bbc94abfa2d5a0e00915eb86e03bff674d999ab242108d5451194591
|
7
|
+
data.tar.gz: 969d7c226e9be6f9ab75ca98713ba241430fae9a6c713c7834034d37ae3076ecc65af2becef30b29cef52a74fab9b6aff8682d21d0a8d875d97433c96ed86dfd
|
data/README.md
CHANGED
@@ -49,7 +49,40 @@ After installing the gem, run the installer:
|
|
49
49
|
$ rails generate solid_errors:install
|
50
50
|
```
|
51
51
|
|
52
|
-
This will
|
52
|
+
This will create the `db/errors_schema.rb` file.
|
53
|
+
|
54
|
+
You will then have to add the configuration for the errors database in `config/database.yml`. If you're using sqlite, it'll look something like this:
|
55
|
+
|
56
|
+
```yaml
|
57
|
+
production:
|
58
|
+
primary:
|
59
|
+
<<: *default
|
60
|
+
database: storage/production.sqlite3
|
61
|
+
errors:
|
62
|
+
<<: *default
|
63
|
+
database: storage/production_errors.sqlite3
|
64
|
+
migrations_paths: db/errors_migrate
|
65
|
+
```
|
66
|
+
|
67
|
+
...or if you're using MySQL/PostgreSQL/Trilogy:
|
68
|
+
|
69
|
+
```yaml
|
70
|
+
production:
|
71
|
+
primary: &primary_production
|
72
|
+
<<: *default
|
73
|
+
database: app_production
|
74
|
+
username: app
|
75
|
+
password: <%= ENV["APP_DATABASE_PASSWORD"] %>
|
76
|
+
errors:
|
77
|
+
<<: *primary_production
|
78
|
+
database: app_production_errors
|
79
|
+
migrations_paths: db/errors_migrate
|
80
|
+
```
|
81
|
+
|
82
|
+
> [!NOTE]
|
83
|
+
> Calling `bin/rails solid_errors:install` will automatically add `config.solid_errors.connects_to = { database: { writing: :errors } }` to `config/environments/production.rb`, so no additional configuration is needed there (although you must make sure that you use the errors name in database.yml for this to match!). But if you want to use Solid Queue in a different environment (like staging or even development), you'll have to manually add that `config.solid_errors.connects_to` line to the respective environment file. And, as always, make sure that the name you're using for the database in `config/database.yml` matches the name you use in `config.solid_errors.connects_to`.
|
84
|
+
|
85
|
+
Then run `db:prepare` in production to ensure the database is created and the schema is loaded.
|
53
86
|
|
54
87
|
Then mount the engine in your `config/routes.rb` file:
|
55
88
|
```ruby
|
@@ -69,6 +102,54 @@ Please consult the [official guides](https://guides.rubyonrails.org/error_report
|
|
69
102
|
|
70
103
|
There are intentionally few features; you can view and resolve errors. That’s it. The goal is to provide a simple, lightweight, and performant solution for tracking exceptions in your Rails application. If you need more features, you should probably use a 3rd party service like [Honeybadger](https://www.honeybadger.io/), whose MIT-licensed [Ruby agent gem](https://github.com/honeybadger-io/honeybadger-ruby) provided a couple of critical pieces of code for this project.
|
71
104
|
|
105
|
+
### Manually reporting an Error
|
106
|
+
|
107
|
+
Errors can be added to Solid Errors via the [Rails error reporter](https://guides.rubyonrails.org/error_reporting.html).
|
108
|
+
|
109
|
+
There are [three ways](https://guides.rubyonrails.org/error_reporting.html#using-the-error-reporter) you can use the error reporter:
|
110
|
+
|
111
|
+
`Rails.error.handle` will report any error raised within the block. It will then swallow the error, and the rest of your code outside the block will continue as normal.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
result = Rails.error.handle do
|
115
|
+
1 + '1' # raises TypeError
|
116
|
+
end
|
117
|
+
result # => nil
|
118
|
+
1 + 1 # This will be executed
|
119
|
+
```
|
120
|
+
|
121
|
+
`Rails.error.record` will report errors to all registered subscribers and then re-raise the error, meaning that the rest of your code won't execute.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
Rails.error.record do
|
125
|
+
1 + '1' # raises TypeError
|
126
|
+
end
|
127
|
+
1 + 1 # This won't be executed
|
128
|
+
```
|
129
|
+
|
130
|
+
You can also manually report errors by calling `Rails.error.report`:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
begin
|
134
|
+
# code
|
135
|
+
rescue StandardError => e
|
136
|
+
Rails.error.report(e)
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
All 3 reporting APIs (`#handle`, `#record`, and `#report`) support the following options, which are then passed along to all registered subscribers:
|
141
|
+
|
142
|
+
* `handled`: a Boolean to indicate if the error was handled. This is set to `true` by default. `#record` sets this to `false`.
|
143
|
+
* `severity`: a Symbol describing the severity of the error. Expected values are: `:error`, `:warning`, and `:info`. `#handle` sets this to `:warning`, while `#record` sets it to `:error`.
|
144
|
+
* `context`: a Hash to provide more context about the error, like request or user details
|
145
|
+
* `source`: a String about the source of the error. The default source is `"application"`. Errors reported by internal libraries may set other sources; the Redis cache library may use "redis_cache_store.active_support", for instance. Your subscriber can use the source to ignore errors you aren't interested in.
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
Rails.error.handle(context: { user_id: user.id }, severity: :info) do
|
149
|
+
# ...
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
72
153
|
### Configuration
|
73
154
|
|
74
155
|
You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, 6 configuration options are available:
|
@@ -2,14 +2,16 @@ module SolidErrors
|
|
2
2
|
class ErrorsController < ApplicationController
|
3
3
|
around_action :force_english_locale!
|
4
4
|
|
5
|
-
before_action :set_error, only: %i[show update]
|
5
|
+
before_action :set_error, only: %i[show update destroy]
|
6
|
+
|
7
|
+
helper_method :error_scope
|
6
8
|
|
7
9
|
# GET /errors
|
8
10
|
def index
|
9
11
|
errors_table = Error.arel_table
|
10
12
|
occurrences_table = Occurrence.arel_table
|
11
|
-
|
12
|
-
@errors =
|
13
|
+
query_scope = error_scope.resolved? ? Error.resolved : Error.unresolved
|
14
|
+
@errors = query_scope
|
13
15
|
.joins(:occurrences)
|
14
16
|
.select(errors_table[Arel.star],
|
15
17
|
occurrences_table[:created_at].maximum.as("recent_occurrence"),
|
@@ -30,6 +32,16 @@ module SolidErrors
|
|
30
32
|
redirect_to errors_path, notice: "Error marked as resolved."
|
31
33
|
end
|
32
34
|
|
35
|
+
# DELETE /errors/1
|
36
|
+
def destroy
|
37
|
+
if @error.resolved?
|
38
|
+
@error.destroy
|
39
|
+
redirect_to errors_path(scope: :resolved), notice: "Error deleted."
|
40
|
+
else
|
41
|
+
redirect_to error_path(@error), alert: "You must resolve the error before deleting it."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
33
45
|
private
|
34
46
|
|
35
47
|
# Only allow a list of trusted parameters through.
|
@@ -44,5 +56,9 @@ module SolidErrors
|
|
44
56
|
def force_english_locale!(&action)
|
45
57
|
I18n.with_locale(:en, &action)
|
46
58
|
end
|
59
|
+
|
60
|
+
def error_scope
|
61
|
+
ActiveSupport::StringInquirer.new(params[:scope] || "unresolved")
|
62
|
+
end
|
47
63
|
end
|
48
64
|
end
|
@@ -12,6 +12,14 @@ module SolidErrors
|
|
12
12
|
warning: "bg-yellow-100 text-yellow-800",
|
13
13
|
info: "bg-blue-100 text-blue-800"
|
14
14
|
}
|
15
|
+
STATUS_TO_EMOJI = {
|
16
|
+
resolved: "✅",
|
17
|
+
unresolved: "⏳"
|
18
|
+
}
|
19
|
+
STATUS_TO_BADGE_CLASSES = {
|
20
|
+
resolved: "bg-green-100 text-green-800",
|
21
|
+
unresolved: "bg-violet-100 text-violet-800"
|
22
|
+
}
|
15
23
|
|
16
24
|
has_many :occurrences, class_name: "SolidErrors::Occurrence", dependent: :destroy
|
17
25
|
|
@@ -22,12 +30,28 @@ module SolidErrors
|
|
22
30
|
scope :resolved, -> { where.not(resolved_at: nil) }
|
23
31
|
scope :unresolved, -> { where(resolved_at: nil) }
|
24
32
|
|
25
|
-
def
|
33
|
+
def severity_emoji
|
26
34
|
SEVERITY_TO_EMOJI[severity.to_sym]
|
27
35
|
end
|
28
36
|
|
29
|
-
def
|
37
|
+
def severity_badge_classes
|
30
38
|
"px-2 inline-flex text-sm font-semibold rounded-md #{SEVERITY_TO_BADGE_CLASSES[severity.to_sym]}"
|
31
39
|
end
|
40
|
+
|
41
|
+
def status
|
42
|
+
resolved? ? :resolved : :unresolved
|
43
|
+
end
|
44
|
+
|
45
|
+
def status_emoji
|
46
|
+
STATUS_TO_EMOJI[status]
|
47
|
+
end
|
48
|
+
|
49
|
+
def status_badge_classes
|
50
|
+
"px-2 inline-flex text-sm font-semibold rounded-md #{STATUS_TO_BADGE_CLASSES[status]}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def resolved?
|
54
|
+
resolved_at.present?
|
55
|
+
end
|
32
56
|
end
|
33
57
|
end
|
@@ -617,6 +617,10 @@
|
|
617
617
|
margin-bottom: 0.75rem
|
618
618
|
}
|
619
619
|
|
620
|
+
.mb-6{
|
621
|
+
margin-bottom: 1.5rem
|
622
|
+
}
|
623
|
+
|
620
624
|
.ml-6{
|
621
625
|
margin-left: 1.5rem
|
622
626
|
}
|
@@ -629,6 +633,10 @@
|
|
629
633
|
margin-top: 1rem
|
630
634
|
}
|
631
635
|
|
636
|
+
.-mb-px {
|
637
|
+
margin-bottom: -1px;
|
638
|
+
}
|
639
|
+
|
632
640
|
.inline-block{
|
633
641
|
display: inline-block
|
634
642
|
}
|
@@ -743,6 +751,12 @@
|
|
743
751
|
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse))
|
744
752
|
}
|
745
753
|
|
754
|
+
.space-x-8 > :not([hidden]) ~ :not([hidden]) {
|
755
|
+
--tw-space-x-reverse: 0;
|
756
|
+
margin-right: calc(2rem * var(--tw-space-x-reverse));
|
757
|
+
margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
|
758
|
+
}
|
759
|
+
|
746
760
|
.divide-y > :not([hidden]) ~ :not([hidden]){
|
747
761
|
--tw-divide-y-reverse: 0;
|
748
762
|
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
@@ -770,6 +784,10 @@
|
|
770
784
|
border-radius: 0.25rem
|
771
785
|
}
|
772
786
|
|
787
|
+
.rounded-md{
|
788
|
+
border-radius: 0.375rem
|
789
|
+
}
|
790
|
+
|
773
791
|
.rounded-lg{
|
774
792
|
border-radius: 0.5rem
|
775
793
|
}
|
@@ -787,6 +805,10 @@
|
|
787
805
|
border-bottom-width: 1px
|
788
806
|
}
|
789
807
|
|
808
|
+
.border-b-2 {
|
809
|
+
border-bottom-width: 2px;
|
810
|
+
}
|
811
|
+
|
790
812
|
.border-black{
|
791
813
|
--tw-border-opacity: 1;
|
792
814
|
border-color: rgb(0 0 0 / var(--tw-border-opacity))
|
@@ -802,6 +824,15 @@
|
|
802
824
|
border-color: rgb(209 213 219 / var(--tw-border-opacity))
|
803
825
|
}
|
804
826
|
|
827
|
+
.border-red-500{
|
828
|
+
--tw-border-opacity: 1;
|
829
|
+
border-color: rgb(239 68 68 / var(--tw-border-opacity))
|
830
|
+
}
|
831
|
+
|
832
|
+
.border-transparent {
|
833
|
+
border-color: transparent;
|
834
|
+
}
|
835
|
+
|
805
836
|
.bg-gray-100{
|
806
837
|
--tw-bg-opacity: 1;
|
807
838
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity))
|
@@ -848,6 +879,11 @@
|
|
848
879
|
padding-right: 0px
|
849
880
|
}
|
850
881
|
|
882
|
+
.px-1 {
|
883
|
+
padding-left: 0.25rem;
|
884
|
+
padding-right: 0.25rem;
|
885
|
+
}
|
886
|
+
|
851
887
|
.px-2{
|
852
888
|
padding-left: 0.5rem;
|
853
889
|
padding-right: 0.5rem
|
@@ -916,10 +952,6 @@
|
|
916
952
|
padding-top: 1.75rem
|
917
953
|
}
|
918
954
|
|
919
|
-
.rounded-md{
|
920
|
-
border-radius: 0.375rem
|
921
|
-
}
|
922
|
-
|
923
955
|
.text-left{
|
924
956
|
text-align: left
|
925
957
|
}
|
@@ -931,7 +963,7 @@
|
|
931
963
|
.text-right{
|
932
964
|
text-align: right
|
933
965
|
}
|
934
|
-
|
966
|
+
|
935
967
|
.text-black{
|
936
968
|
--tw-text-opacity: 1;
|
937
969
|
color: rgb(0 0 0 / var(--tw-text-opacity))
|
@@ -987,6 +1019,16 @@
|
|
987
1019
|
background-color: rgb(254 249 195 / var(--tw-bg-opacity))
|
988
1020
|
}
|
989
1021
|
|
1022
|
+
.bg-green-100{
|
1023
|
+
--tw-bg-opacity: 1;
|
1024
|
+
background-color: rgb(220 252 231 / var(--tw-bg-opacity))
|
1025
|
+
}
|
1026
|
+
|
1027
|
+
.bg-violet-100{
|
1028
|
+
--tw-bg-opacity: 1;
|
1029
|
+
background-color: rgb(237 233 254 / var(--tw-bg-opacity))
|
1030
|
+
}
|
1031
|
+
|
990
1032
|
.text-blue-400{
|
991
1033
|
--tw-text-opacity: 1;
|
992
1034
|
color: rgb(96 165 250 / var(--tw-text-opacity))
|
@@ -1037,6 +1079,16 @@
|
|
1037
1079
|
color: rgb(133 77 14 / var(--tw-text-opacity))
|
1038
1080
|
}
|
1039
1081
|
|
1082
|
+
.text-green-800{
|
1083
|
+
--tw-text-opacity: 1;
|
1084
|
+
color: rgb(22 101 52 / var(--tw-text-opacity))
|
1085
|
+
}
|
1086
|
+
|
1087
|
+
.text-violet-800{
|
1088
|
+
--tw-text-opacity: 1;
|
1089
|
+
color: rgb(91 33 182 / var(--tw-text-opacity))
|
1090
|
+
}
|
1091
|
+
|
1040
1092
|
.text-white{
|
1041
1093
|
--tw-text-opacity: 1;
|
1042
1094
|
color: rgb(255 255 255 / var(--tw-text-opacity))
|
@@ -1046,6 +1098,14 @@
|
|
1046
1098
|
text-decoration-line: underline
|
1047
1099
|
}
|
1048
1100
|
|
1101
|
+
.uppercase{
|
1102
|
+
text-transform: uppercase
|
1103
|
+
}
|
1104
|
+
|
1105
|
+
.whitespace-pre-wrap{
|
1106
|
+
white-space: pre-wrap
|
1107
|
+
}
|
1108
|
+
|
1049
1109
|
.transition-opacity{
|
1050
1110
|
transition-property: opacity;
|
1051
1111
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
@@ -1082,6 +1142,11 @@
|
|
1082
1142
|
--tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity))
|
1083
1143
|
}
|
1084
1144
|
|
1145
|
+
.hover\:ring-red-200:hover{
|
1146
|
+
--tw-ring-opacity: 1;
|
1147
|
+
--tw-ring-color: rgb(254 202 202 / var(--tw-ring-opacity))
|
1148
|
+
}
|
1149
|
+
|
1085
1150
|
.hover\:bg-black:hover{
|
1086
1151
|
--tw-bg-opacity: 1;
|
1087
1152
|
background-color: rgb(0 0 0 / var(--tw-bg-opacity))
|
@@ -1092,6 +1157,16 @@
|
|
1092
1157
|
color: rgb(255 255 255 / var(--tw-text-opacity))
|
1093
1158
|
}
|
1094
1159
|
|
1160
|
+
.hover\:text-gray-700:hover {
|
1161
|
+
--tw-text-opacity: 1;
|
1162
|
+
color: rgb(55 65 81 / var(--tw-text-opacity));
|
1163
|
+
}
|
1164
|
+
|
1165
|
+
.hover\:border-gray-300:hover {
|
1166
|
+
--tw-border-opacity: 1;
|
1167
|
+
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
1168
|
+
}
|
1169
|
+
|
1095
1170
|
@media (min-width: 640px){
|
1096
1171
|
.sm\:mx-0{
|
1097
1172
|
margin-left: 0px;
|
@@ -1,16 +1,13 @@
|
|
1
1
|
<div class="inline-flex items-center justify-start flex-wrap gap-3 whitespace-nowrap">
|
2
|
-
<%= link_to errors_path, class: "inline-flex items-center justify-center gap-2 font-medium cursor-pointer border rounded-lg py-3 px-5 bg-gray-100 text-initial border-gray-300 hover:ring-gray-200 hover:ring-8" do %>
|
2
|
+
<%= link_to errors_path(scope: error.resolved? ? :resolved : :unresolved), class: "inline-flex items-center justify-center gap-2 font-medium cursor-pointer border rounded-lg py-3 px-5 bg-gray-100 text-initial border-gray-300 hover:ring-gray-200 hover:ring-8" do %>
|
3
3
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
|
4
4
|
<path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8"/>
|
5
5
|
</svg>
|
6
6
|
<span>Back to errors</span>
|
7
7
|
<% end %>
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
<path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0z"/>
|
13
|
-
</svg>
|
14
|
-
<span>Resolve Error<span class="sr-only"> #<%= error.id %></span></span>
|
8
|
+
<% if error.resolved? %>
|
9
|
+
<%= render 'solid_errors/errors/delete_button', error: error %>
|
10
|
+
<% else %>
|
11
|
+
<%= render 'solid_errors/errors/resolve_button', error: error %>
|
15
12
|
<% end %>
|
16
13
|
</div>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<%# locals: (error:) -%>
|
2
|
+
<%= button_to error_path(error), method: :delete, class: "inline-flex items-center justify-center gap-2 font-medium cursor-pointer border rounded-lg py-3 px-5 bg-transparent text-red-500 border-red-500 hover:ring-red-200 hover:ring-8" do %>
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="bi bi-trash3" fill="currentColor" viewBox="0 0 16 16">
|
4
|
+
<path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5M11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47M8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5"/>
|
5
|
+
</svg>
|
6
|
+
<span>Delete <span class="sr-only">, Error #<%= error.id %></span></span>
|
7
|
+
<% end %>
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<%= tag.section id: dom_id(error), class: "space-y-6" do %>
|
4
4
|
<div class="flex justify-between items-center">
|
5
5
|
<%= tag.h1 class: "font-bold flex items-center text-2xl gap-2" do %>
|
6
|
-
<%= error.
|
6
|
+
<%= error.severity_emoji %>
|
7
7
|
<code><%= error.exception_class %></code>
|
8
8
|
from
|
9
9
|
<em><code><%= error.source %></code></em>
|
@@ -14,9 +14,31 @@
|
|
14
14
|
</small>
|
15
15
|
</div>
|
16
16
|
|
17
|
-
<pre class=""><%= error.message %></pre>
|
17
|
+
<pre class="whitespace-pre-wrap"><%= error.message %></pre>
|
18
18
|
|
19
19
|
<dl class="flex-1 grid grid-cols-2 gap-x-4">
|
20
|
+
<div class="flex items-center justify-between flex-wrap gap-x-2">
|
21
|
+
<dt class="font-bold">
|
22
|
+
<%= SolidErrors::Error.human_attribute_name(:severity) %>
|
23
|
+
</dt>
|
24
|
+
<dd class="inline-flex items-center gap-1">
|
25
|
+
<%= error.severity_emoji %>
|
26
|
+
<span class="<%= error.severity_badge_classes %>">
|
27
|
+
<%= error.severity %>
|
28
|
+
</span>
|
29
|
+
</dd>
|
30
|
+
</div>
|
31
|
+
<div class="flex items-center justify-between flex-wrap gap-x-2">
|
32
|
+
<dt class="font-bold">
|
33
|
+
<%= SolidErrors::Error.human_attribute_name(:status) %>
|
34
|
+
</dt>
|
35
|
+
<dd class="inline-flex items-center gap-1">
|
36
|
+
<%= error.status_emoji %>
|
37
|
+
<span class="<%= error.status_badge_classes %>">
|
38
|
+
<%= error.status %>
|
39
|
+
</span>
|
40
|
+
</dd>
|
41
|
+
</div>
|
20
42
|
<div class="flex items-center justify-between flex-wrap gap-x-2">
|
21
43
|
<dt class="font-bold">
|
22
44
|
<%= SolidErrors::Error.human_attribute_name(:first_seen) %>
|
@@ -41,14 +63,6 @@
|
|
41
63
|
</abbr>
|
42
64
|
</dd>
|
43
65
|
</div>
|
44
|
-
<div class="flex items-center justify-between flex-wrap gap-x-2">
|
45
|
-
<dt class="font-bold">
|
46
|
-
<%= SolidErrors::Error.human_attribute_name(:occurrences) %>
|
47
|
-
</dt>
|
48
|
-
<dd class="inline-flex items-center gap-1">
|
49
|
-
<%= error.occurrences.size %>
|
50
|
-
</dd>
|
51
|
-
</div>
|
52
66
|
<div class="flex items-center justify-between flex-wrap gap-x-2">
|
53
67
|
<dt class="font-bold">
|
54
68
|
<%= SolidErrors::Error.human_attribute_name(:exception_class) %>
|
@@ -57,17 +71,6 @@
|
|
57
71
|
<code><%= error.exception_class %></code>
|
58
72
|
</dd>
|
59
73
|
</div>
|
60
|
-
<div class="flex items-center justify-between flex-wrap gap-x-2">
|
61
|
-
<dt class="font-bold">
|
62
|
-
<%= SolidErrors::Error.human_attribute_name(:severity) %>
|
63
|
-
</dt>
|
64
|
-
<dd class="inline-flex items-center gap-1">
|
65
|
-
<%= error.emoji %>
|
66
|
-
<span class="<%= error.badge_classes %>">
|
67
|
-
<%= error.severity %>
|
68
|
-
</span>
|
69
|
-
</dd>
|
70
|
-
</div>
|
71
74
|
<div class="flex items-center justify-between flex-wrap gap-x-2">
|
72
75
|
<dt class="font-bold">
|
73
76
|
<%= SolidErrors::Error.human_attribute_name(:source) %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<%# locals: (error:) -%>
|
2
|
+
<%= button_to error_path(error), method: :patch, class: "inline-flex items-center justify-center gap-2 font-medium cursor-pointer border rounded-lg py-3 px-5 bg-transparent text-blue-500 border-blue-500 hover:ring-blue-200 hover:ring-8", params: { error: { resolved_at: Time.now } } do %>
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2-circle" viewBox="0 0 16 16">
|
4
|
+
<path d="M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0"/>
|
5
|
+
<path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0z"/>
|
6
|
+
</svg>
|
7
|
+
<span>Resolve <span class="sr-only">, Error #<%= error.id %></span></span>
|
8
|
+
<% end %>
|
@@ -1,7 +1,8 @@
|
|
1
|
+
<%# locals: (error:) -%>
|
1
2
|
<tr class="even:bg-gray-50 align-top">
|
2
3
|
<td scope="col" class="whitespace-wrap py-4 pl-4 pr-3 font-medium text-gray-900 sm:pl-3">
|
3
4
|
<div>
|
4
|
-
<%= error.
|
5
|
+
<%= error.severity_emoji %>
|
5
6
|
<%= link_to error_path(error), class: "text-blue-400 underline inline-flex items-baseline gap-1" do %>
|
6
7
|
<strong><code><%= error.exception_class %></code></strong>
|
7
8
|
<% end %>
|
@@ -20,12 +21,10 @@
|
|
20
21
|
</abbr>
|
21
22
|
</td>
|
22
23
|
<td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-3">
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
</svg>
|
28
|
-
<span>Resolve<span class="sr-only">, Error #<%= error.id %></span></span>
|
24
|
+
<% if error.resolved? %>
|
25
|
+
<%= render 'solid_errors/errors/delete_button', error: error %>
|
26
|
+
<% else %>
|
27
|
+
<%= render 'solid_errors/errors/resolve_button', error: error %>
|
29
28
|
<% end %>
|
30
29
|
</td>
|
31
30
|
</tr>
|
@@ -1,3 +1,25 @@
|
|
1
|
+
<div class="mb-6 border-b border-gray-300 flex justify-between items-baseline">
|
2
|
+
<h1 class="font-bold flex items-center text-2xl gap-2">
|
3
|
+
Errors
|
4
|
+
</h1>
|
5
|
+
<nav class="-mb-px flex space-x-8" aria-label="Tabs">
|
6
|
+
<%= link_to errors_path(scope: :unresolved), class: class_names(
|
7
|
+
"inline-flex items-center border-b-2 px-1 py-4 text-sm font-medium",
|
8
|
+
"border-blue-500 text-blue-500" => !error_scope.resolved?,
|
9
|
+
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" => error_scope.resolved?) do %>
|
10
|
+
<span class="mr-2"><%= SolidErrors::Error::STATUS_TO_EMOJI[:unresolved] %></span>
|
11
|
+
<span>Unresolved</span>
|
12
|
+
<% end %>
|
13
|
+
<%= link_to errors_path(scope: :resolved), class: class_names(
|
14
|
+
"inline-flex items-center border-b-2 px-1 py-4 text-sm font-medium",
|
15
|
+
"border-blue-500 text-blue-500" => error_scope.resolved?,
|
16
|
+
"border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" => !error_scope.resolved?) do %>
|
17
|
+
<span class="mr-2"><%= SolidErrors::Error::STATUS_TO_EMOJI[:resolved] %></span>
|
18
|
+
<span>Resolved</span>
|
19
|
+
<% end %>
|
20
|
+
</nav>
|
21
|
+
</div>
|
22
|
+
|
1
23
|
<table class="min-w-full divide-y divide-gray-300">
|
2
24
|
<thead>
|
3
25
|
<tr>
|
data/config/routes.rb
CHANGED
@@ -1,32 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rails/generators"
|
4
|
-
require "rails/generators/active_record"
|
5
|
-
|
6
3
|
module SolidErrors
|
7
4
|
#
|
8
5
|
# Rails generator used for setting up SolidErrors in a Rails application.
|
9
6
|
# Run it with +bin/rails g solid_errors:install+ in your console.
|
10
7
|
#
|
11
8
|
class InstallGenerator < Rails::Generators::Base
|
12
|
-
include ActiveRecord::Generators::Migration
|
13
|
-
|
14
9
|
source_root File.expand_path("templates", __dir__)
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# Generates monolithic migration file that contains all database changes.
|
20
|
-
def create_migration_file
|
21
|
-
return if options[:skip_migrations]
|
22
|
-
|
23
|
-
migration_template "create_solid_errors_tables.rb.erb", File.join(db_migrate_path, "create_solid_errors_tables.rb")
|
11
|
+
def add_solid_errors_db_schema
|
12
|
+
template "db/errors_schema.rb"
|
24
13
|
end
|
25
14
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
15
|
+
def configure_solid_errors
|
16
|
+
insert_into_file Pathname(destination_root).join("config/environments/production.rb"), after: /^([ \t]*).*?(?=\nend)$/ do
|
17
|
+
[
|
18
|
+
"",
|
19
|
+
'\1# Configure Solid Errors',
|
20
|
+
'\1config.solid_errors.connects_to = { database: { writing: :errors } }',
|
21
|
+
'\1config.solid_errors.send_emails = true',
|
22
|
+
'\1config.solid_errors.email_from = ""',
|
23
|
+
'\1config.solid_errors.email_to = ""',
|
24
|
+
'\1config.solid_errors.username = Rails.application.credentials.dig(:solid_errors, :username)',
|
25
|
+
'\1config.solid_errors.password = Rails.application.credentials.dig(:solid_errors, :password)',
|
26
|
+
].join("\n")
|
27
|
+
end
|
30
28
|
end
|
31
29
|
end
|
32
30
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ActiveRecord::Schema[7.1].define(version: 1) do
|
4
|
+
create_table "solid_errors", force: :cascade do |t|
|
5
|
+
t.text "exception_class", null: false
|
6
|
+
t.text "message", null: false
|
7
|
+
t.text "severity", null: false
|
8
|
+
t.text "source"
|
9
|
+
t.datetime "resolved_at"
|
10
|
+
t.string "fingerprint", limit: 64, null: false
|
11
|
+
t.datetime "created_at", null: false
|
12
|
+
t.datetime "updated_at", null: false
|
13
|
+
t.index ["fingerprint"], name: "index_solid_errors_on_fingerprint", unique: true
|
14
|
+
t.index ["resolved_at"], name: "index_solid_errors_on_resolved_at"
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table "solid_errors_occurrences", force: :cascade do |t|
|
18
|
+
t.integer "error_id", null: false
|
19
|
+
t.text "backtrace"
|
20
|
+
t.json "context"
|
21
|
+
t.datetime "created_at", null: false
|
22
|
+
t.datetime "updated_at", null: false
|
23
|
+
t.index ["error_id"], name: "index_solid_errors_occurrences_on_error_id"
|
24
|
+
end
|
25
|
+
|
26
|
+
add_foreign_key "solid_errors_occurrences", "solid_errors", column: "error_id"
|
27
|
+
end
|
data/lib/solid_errors/version.rb
CHANGED
metadata
CHANGED
@@ -1,97 +1,97 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_errors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.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-
|
11
|
+
date: 2024-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionmailer
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '7.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '7.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: actionpack
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '7.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '7.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: actionview
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '7.0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '7.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: activerecord
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '7.0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '7.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: activesupport
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '7.0'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '7.0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: railties
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - "
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '7.0'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- - "
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '7.0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
@@ -130,7 +130,9 @@ files:
|
|
130
130
|
- app/views/solid_errors/error_mailer/error_occurred.html.erb
|
131
131
|
- app/views/solid_errors/error_mailer/error_occurred.text.erb
|
132
132
|
- app/views/solid_errors/errors/_actions.html.erb
|
133
|
+
- app/views/solid_errors/errors/_delete_button.html.erb
|
133
134
|
- app/views/solid_errors/errors/_error.html.erb
|
135
|
+
- app/views/solid_errors/errors/_resolve_button.html.erb
|
134
136
|
- app/views/solid_errors/errors/_row.html.erb
|
135
137
|
- app/views/solid_errors/errors/index.html.erb
|
136
138
|
- app/views/solid_errors/errors/show.html.erb
|
@@ -140,7 +142,7 @@ files:
|
|
140
142
|
- config/routes.rb
|
141
143
|
- lib/generators/solid_errors/install/USAGE
|
142
144
|
- lib/generators/solid_errors/install/install_generator.rb
|
143
|
-
- lib/generators/solid_errors/install/templates/
|
145
|
+
- lib/generators/solid_errors/install/templates/errors_schema.rb
|
144
146
|
- lib/solid_errors.rb
|
145
147
|
- lib/solid_errors/engine.rb
|
146
148
|
- lib/solid_errors/sanitizer.rb
|
@@ -167,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
167
169
|
- !ruby/object:Gem::Version
|
168
170
|
version: '0'
|
169
171
|
requirements: []
|
170
|
-
rubygems_version: 3.5.
|
172
|
+
rubygems_version: 3.5.11
|
171
173
|
signing_key:
|
172
174
|
specification_version: 4
|
173
175
|
summary: Database-backed Rails error subscriber
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class CreateSolidErrorsTables < ActiveRecord::Migration<%= migration_version %>
|
4
|
-
def change
|
5
|
-
create_table :solid_errors do |t|
|
6
|
-
t.text :exception_class, null: false
|
7
|
-
t.text :message, null: false
|
8
|
-
t.text :severity, null: false
|
9
|
-
t.text :source
|
10
|
-
t.datetime :resolved_at, index: true
|
11
|
-
t.string :fingerprint, null: false, limit: 64, index: { unique: true }
|
12
|
-
|
13
|
-
t.timestamps
|
14
|
-
end
|
15
|
-
|
16
|
-
create_table :solid_errors_occurrences do |t|
|
17
|
-
t.belongs_to :error, null: false, foreign_key: { to_table: :solid_errors }
|
18
|
-
t.text :backtrace
|
19
|
-
t.json :context
|
20
|
-
|
21
|
-
t.timestamps
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|