solid_errors 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a034955a760c572f2e8e317e1120eed78d92a0fe1a55cae7f96e571c66ef00cd
4
- data.tar.gz: 8d7490b0198ee2e899c712c749a9e907755df31c3a900a51f21db5d089267ea7
3
+ metadata.gz: b0f7fa69e2efa8949c085b3ba7b72b407dd3d670643c76260a6f8addf836305f
4
+ data.tar.gz: f931d1de445d5d3fe124dc352e2a81513d174d37ed6ae923122e95e2468ccc7e
5
5
  SHA512:
6
- metadata.gz: 9f674d6137d43517bc2c984c49bef87b521cc8fd72001f0d7fc6c14ac9072bd5a944af1f78ee06a74f10e0793db5187015d995915fda37bf5ed6f9e4f2314ea8
7
- data.tar.gz: 39b0a00ec120b2d2a4c80b7602cacbc18ce97ad268cba8a62426d4c77c593ca7d10d7af18bb54c768530f2f7fe6999bdff76e533db74c7adc2680c2104d19f24
6
+ metadata.gz: 3a2eb10327f7a11569d1cc90f2418bd28252d177b23c329c8a4105b2cb22a4fa17e36b44dbe5f1f341c222de2be9ba149fb4ce6e308b5c6aa99b1160de678b83
7
+ data.tar.gz: 52f3d7fd1669173da1ce9155370d92bae1678c4ae4326c2637e026c93cc9eeb84e091c587f8322fd6b9f9228c79f31262c0cd9b42be98d65837004754efc2ec3
data/README.md CHANGED
@@ -30,22 +30,28 @@ Solid Errors is a DB-based, app-internal exception tracker for Rails application
30
30
  ## Installation
31
31
 
32
32
  Install the gem and add to the application's Gemfile by executing:
33
-
34
- $ bundle add solid_errors
33
+ ```bash
34
+ $ bundle add solid_errors
35
+ ```
35
36
 
36
37
  If bundler is not being used to manage dependencies, install the gem by executing:
37
-
38
- $ gem install solid_errors
38
+ ```bash
39
+ $ gem install solid_errors
40
+ ```
39
41
 
40
42
  After installing the gem, run the installer:
41
-
42
- $ rails generate solid_errors:install
43
+ ```bash
44
+ $ rails generate solid_errors:install
45
+ ```
43
46
 
44
47
  This will copy the required migration over to your app.
45
48
 
46
- Then mount the engine in your config/routes.rb file
47
-
48
- mount SolidErrors::Engine, at: "/solid_errors"
49
+ Then mount the engine in your `config/routes.rb` file:
50
+ ```ruby
51
+ authenticate :user, -> (user) { user.admin? } do
52
+ mount SolidErrors::Engine, at: "/solid_errors"
53
+ end
54
+ ```
49
55
 
50
56
  > [!NOTE]
51
57
  > Be sure to [secure the dashboard](#authentication) in production.
@@ -60,11 +66,14 @@ There are intentionally few features; you can view and resolve errors. That’s
60
66
 
61
67
  ### Configuration
62
68
 
63
- You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, only 3 configuration options are available:
69
+ You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, 6 configuration options are available:
64
70
 
65
71
  * `connects_to` - The database configuration to use for the Solid Errors database. See [Database Configuration](#database-configuration) for more information.
66
72
  * `username` - The username to use for HTTP authentication. See [Authentication](#authentication) for more information.
67
73
  * `password` - The password to use for HTTP authentication. See [Authentication](#authentication) for more information.
74
+ * `sends_email` - Whether or not to send emails when an error occurs. See [Email notifications](#email-notifications) for more information.
75
+ * `email_from` - The email address to send a notification from. See [Email notifications](#email-notifications) for more information.
76
+ * `email_to` - The email address(es) to send a notification to. See [Email notifications](#email-notifications) for more information.
68
77
 
69
78
  #### Database Configuration
70
79
 
@@ -103,7 +112,7 @@ config.solid_errors.password = Rails.application.credentials.solid_errors.passwo
103
112
 
104
113
  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.
105
114
 
106
- 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:
115
+ If you use Devise for authentication in your app, you can also restrict access to the dashboard by using their `authenticate` constraint in your routes file:
107
116
 
108
117
  ```ruby
109
118
  authenticate :user, -> (user) { user.admin? } do
@@ -111,6 +120,29 @@ authenticate :user, -> (user) { user.admin? } do
111
120
  end
112
121
  ```
113
122
 
123
+ #### Email notifications
124
+
125
+ Solid Errors _can_ send email notifications whenever an error occurs, if your application has ActionMailer already properly setup to send emails. However, in order to activate this feature you must define the email address(es) to send the notifications to. Optionally, you can also define the email address to send the notifications from (useful if your email provider only allows emails to be sent from a predefined list of addresses) or simply turn off this feature altogether.
126
+
127
+ There are two ways to configure email notifications. First, you can use environment variables:
128
+
129
+ ```ruby
130
+ ENV["SOLIDERRORS_SEND_EMAILS"] = true # defaults to true
131
+ ENV["SOLIDERRORS_EMAIL_FROM"] = "errors@myapp.com" # defaults to "solid_errors@noreply.com"
132
+ ENV["SOLIDERRORS_EMAIL_TO"] = "devs@myapp.com" # no default, must be set
133
+ ```
134
+
135
+ Second, you can set the values via the configuration object:
136
+
137
+ ```ruby
138
+ # Set authentication credentials for Solid Errors
139
+ config.solid_errors.send_emails = true
140
+ config.solid_errors.email_from = "errors@myapp.com"
141
+ config.solid_errors.email_to = "devs@myapp.com"
142
+ ```
143
+
144
+ If you have set `send_emails` to `true` and have set an `email_to` address, Solid Errors will send an email notification whenever an error occurs. If you have not set `send_emails` to `true` or have not set an `email_to` address, Solid Errors will not send any email notifications.
145
+
114
146
  ### Examples
115
147
 
116
148
  There are only two screens in the dashboard.
@@ -123,6 +155,76 @@ There are only two screens in the dashboard.
123
155
 
124
156
  ![image description](images/show-screenshot.png)
125
157
 
158
+ ### Usage with API-only Applications
159
+
160
+ If your Rails application is an API-only application (generated with the `rails new --api` command), you will need to add the following middleware to your `config/application.rb` file in order to use the dashboard UI provided by Solid Errors:
161
+
162
+ ```ruby
163
+ # /config/application.rb
164
+ config.middleware.use ActionDispatch::Cookies
165
+ config.middleware.use ActionDispatch::Session::CookieStore
166
+ config.middleware.use ActionDispatch::Flash
167
+ ```
168
+
169
+ ### Overwriting the views
170
+
171
+ You can find the views in [`app/views`](https://github.com/fractaledmind/solid_errors/tree/main/app/views).
172
+
173
+ ```bash
174
+ app/views/
175
+ ├── layouts
176
+ │   └── solid_errors
177
+ │   ├── _style.html
178
+ │   └── application.html.erb
179
+ └── solid_errors
180
+ ├── error_mailer
181
+ │   ├── error_occurred.html.erb
182
+ │   └── error_occurred.text.erb
183
+ ├── errors
184
+ │   ├── _actions.html.erb
185
+ │   ├── _error.html.erb
186
+ │   ├── _row.html.erb
187
+ │   ├── index.html.erb
188
+ │   └── show.html.erb
189
+ └── occurrences
190
+ ├── _collection.html.erb
191
+ └── _occurrence.html.erb
192
+ ```
193
+
194
+ You can always take control of the views by creating your own views and/or partials at these paths in your application. For example, if you wanted to overwrite the application layout, you could create a file at `app/views/layouts/solid_errors/application.html.erb`. If you wanted to remove the footer and the automatically disappearing flash messages, as one concrete example, you could define that file as:
195
+
196
+ ```erb
197
+ <!DOCTYPE html>
198
+ <html>
199
+ <head>
200
+ <title>Solid Errors</title>
201
+ <%= csrf_meta_tags %>
202
+ <%= csp_meta_tag %>
203
+
204
+ <%= render "layouts/solid_errors/style" %>
205
+ </head>
206
+ <body class="pb-4">
207
+ <main class="container mx-auto mt-4">
208
+ <%= content_for?(:content) ? yield(:content) : yield %>
209
+ </main>
210
+
211
+ <div class="fixed top-0 left-0 right-0 text-center py-2">
212
+ <% if notice.present? %>
213
+ <p class="py-2 px-3 bg-green-50 text-green-500 font-medium rounded-lg inline-block">
214
+ <%= notice %>
215
+ </p>
216
+ <% end %>
217
+
218
+ <% if alert.present? %>
219
+ <p class="py-2 px-3 bg-red-50 text-red-500 font-medium rounded-lg inline-block">
220
+ <%= alert %>
221
+ </p>
222
+ <% end %>
223
+ </div>
224
+ </body>
225
+ </html>
226
+ ```
227
+
126
228
  ## Development
127
229
 
128
230
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -3,5 +3,25 @@ module SolidErrors
3
3
  protect_from_forgery with: :exception
4
4
 
5
5
  http_basic_authenticate_with name: SolidErrors.username, password: SolidErrors.password if SolidErrors.password
6
+
7
+ # adapted from: https://github.com/ddnexus/pagy/blob/master/gem/lib/pagy.rb
8
+ OverflowError = Class.new(StandardError)
9
+ class Page
10
+ attr_reader :count, :items, :pages, :first, :last, :prev, :next, :offset, :from, :to
11
+
12
+ def initialize(collection, params)
13
+ @count = collection.count
14
+ @items = (params[:items] || 20).to_i
15
+ @pages = [(@count.to_f / @items).ceil, 1].max
16
+ @page = ((page = (params[:page] || 1).to_i) > @pages) ? @pages : page
17
+ @first = (1 unless @page == 1)
18
+ @last = (@pages unless @page == @pages)
19
+ @prev = (@page - 1 unless @page == 1)
20
+ @next = (@page == @pages) ? nil : @page + 1
21
+ @offset = (@items * (@page - 1))
22
+ @from = [@offset + 1, @count].min
23
+ @to = [@offset + @items, @count].min
24
+ end
25
+ end
6
26
  end
7
27
  end
@@ -1,5 +1,7 @@
1
1
  module SolidErrors
2
2
  class ErrorsController < ApplicationController
3
+ around_action :force_english_locale!
4
+
3
5
  before_action :set_error, only: %i[show update]
4
6
 
5
7
  # GET /errors
@@ -9,13 +11,17 @@ module SolidErrors
9
11
 
10
12
  @errors = Error.unresolved
11
13
  .joins(:occurrences)
12
- .select(errors_table[Arel.star], occurrences_table[:created_at].maximum.as("recent_occurrence"))
14
+ .select(errors_table[Arel.star],
15
+ occurrences_table[:created_at].maximum.as("recent_occurrence"),
16
+ occurrences_table[:id].count.as("occurrences_count"))
13
17
  .group(errors_table[:id])
14
18
  .order(recent_occurrence: :desc)
15
19
  end
16
20
 
17
21
  # GET /errors/1
18
22
  def show
23
+ @page = Page.new(@error.occurrences, params)
24
+ @occurrences = @error.occurrences.offset(@page.offset).limit(@page.items)
19
25
  end
20
26
 
21
27
  # PATCH/PUT /errors/1
@@ -34,5 +40,9 @@ module SolidErrors
34
40
  def set_error
35
41
  @error = Error.find(params[:id])
36
42
  end
43
+
44
+ def force_english_locale!(&action)
45
+ I18n.with_locale(:en, &action)
46
+ end
37
47
  end
38
48
  end
@@ -0,0 +1,15 @@
1
+ module SolidErrors
2
+ # adapted from: https://github.com/codergeek121/email_error_reporter/blob/main/lib/email_error_reporter/error_mailer.rb
3
+ class ErrorMailer < ActionMailer::Base
4
+ def error_occurred(occurrence)
5
+ @occurrence = occurrence
6
+ @error = occurrence.error
7
+
8
+ mail(
9
+ subject: "#{@error.emoji} #{@error.exception_class}",
10
+ from: SolidErrors.email_from,
11
+ to: SolidErrors.email_to
12
+ )
13
+ end
14
+ end
15
+ end
@@ -2,7 +2,7 @@ module SolidErrors
2
2
  # adapted from: https://github.com/honeybadger-io/honeybadger-ruby/blob/master/lib/honeybadger/backtrace.rb
3
3
  class BacktraceLine
4
4
  # Backtrace line regexp (optionally allowing leading X: for windows support).
5
- INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
5
+ INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}
6
6
  STRING_EMPTY = "".freeze
7
7
  GEM_ROOT = "[GEM_ROOT]".freeze
8
8
  PROJECT_ROOT = "[PROJECT_ROOT]".freeze
@@ -29,7 +29,7 @@ module SolidErrors
29
29
  attr_reader :file
30
30
  attr_reader :number
31
31
  attr_reader :method
32
- attr_reader :filtered_file, :filtered_number, :filtered_method
32
+ attr_reader :filtered_file, :filtered_number, :filtered_method, :unparsed_line
33
33
 
34
34
  # Parses a single line of a given backtrace
35
35
  #
@@ -47,13 +47,14 @@ module SolidErrors
47
47
 
48
48
  file, number, method = match[1], match[2], match[3]
49
49
  filtered_args = [fmatch[1], fmatch[2], fmatch[3]]
50
- new(file, number, method, *filtered_args, opts.fetch(:source_radius, 2))
50
+ new(unparsed_line, file, number, method, *filtered_args, opts.fetch(:source_radius, 2))
51
51
  end
52
52
  end
53
53
 
54
- def initialize(file, number, method, filtered_file = file,
54
+ def initialize(unparsed_line, file, number, method, filtered_file = file,
55
55
  filtered_number = number, filtered_method = method,
56
56
  source_radius = 2)
57
+ self.unparsed_line = unparsed_line
57
58
  self.filtered_file = filtered_file
58
59
  self.filtered_number = filtered_number
59
60
  self.filtered_method = filtered_method
@@ -87,7 +88,7 @@ module SolidErrors
87
88
 
88
89
  private
89
90
 
90
- attr_writer :file, :number, :method, :filtered_file, :filtered_number, :filtered_method
91
+ attr_writer :file, :number, :method, :filtered_file, :filtered_number, :filtered_method, :unparsed_line
91
92
 
92
93
  attr_accessor :source_radius
93
94
 
@@ -2,6 +2,8 @@ module SolidErrors
2
2
  class Occurrence < Record
3
3
  belongs_to :error, class_name: "SolidErrors::Error"
4
4
 
5
+ after_create_commit :send_email, if: -> { SolidErrors.send_emails? && SolidErrors.email_to.present? }
6
+
5
7
  # The parsed exception backtrace. Lines in this backtrace that are from installed gems
6
8
  # have the base path for gem installs replaced by "[GEM_ROOT]", while those in the project
7
9
  # have "[PROJECT_ROOT]".
@@ -17,5 +19,9 @@ module SolidErrors
17
19
  def parse_backtrace(backtrace)
18
20
  Backtrace.parse(backtrace)
19
21
  end
22
+
23
+ def send_email
24
+ ErrorMailer.error_occurred(self).deliver_later
25
+ end
20
26
  end
21
27
  end