concise_errors 0.1.0 → 0.2.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 +4 -4
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +9 -0
- data/README.md +10 -1
- data/lib/concise_errors/configuration.rb +2 -1
- data/lib/concise_errors/debug_exceptions.rb +32 -0
- data/lib/concise_errors/formatter.rb +122 -0
- data/lib/concise_errors/version.rb +1 -1
- data/sig/concise_errors.rbs +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 27769238dc3e569de3a89c3c9ff4f3fcbd43d858f7c77533df92571b66abce74
|
|
4
|
+
data.tar.gz: f4bb5b6d61033967083e9ddcbc069d05acabbf155c8ce806e1dfb32076731a97
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bc5f4bbff9b8ec08847430d9d5dfd5feec8eccbf29a6d8a3fa77f598d9674bc2f36ed8a0426a056aac703218710c1dea1a1a2eaeff319fc84b1e4491abaef767
|
|
7
|
+
data.tar.gz: 70e5c1412e25f8e379a9e2058eb1bcddeb65169783d3d3661adb9dce1ee8432ebe2909884bc59216984ff8da6f809ac8a5439be9430374fcd1b6a6418a7598b4
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2025-10-25
|
|
4
|
+
|
|
5
|
+
- Add a "View full Rails error page" button to the HTML view.
|
|
6
|
+
- Clicking the button replays the request (method, headers, and body when applicable)
|
|
7
|
+
to the same URL with the `concise_errors_full=1` flag, letting Rails render its
|
|
8
|
+
original full-featured debug page (or Web Console when present).
|
|
9
|
+
- Configurable via `config.concise_errors.full_error_param` (defaults to
|
|
10
|
+
`"concise_errors_full"`). Set to `nil` or empty to disable the button entirely.
|
|
11
|
+
|
|
3
12
|
## [0.1.0] - 2025-10-22
|
|
4
13
|
|
|
5
14
|
- Initial release
|
data/README.md
CHANGED
|
@@ -8,6 +8,7 @@ ConciseErrors swaps Rails’ `ActionDispatch::DebugExceptions` middleware with a
|
|
|
8
8
|
- Optional single-file HTML view optimised for dark/light mode without external CSS.
|
|
9
9
|
- Configurable backtrace depth with an omission indicator.
|
|
10
10
|
- Automatic fallback to `text/plain` when the request is `xhr?` or clients negotiate non-HTML `Accept` headers.
|
|
11
|
+
- HTML view includes a button to open Rails' full-featured error page.
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
|
@@ -21,7 +22,7 @@ Run `bundle install` and restart your Rails server. ConciseErrors automatically
|
|
|
21
22
|
|
|
22
23
|
## Configuration
|
|
23
24
|
|
|
24
|
-
ConciseErrors ships with opinionated defaults — HTML output, no CSS, and Web Console middleware is
|
|
25
|
+
ConciseErrors ships with opinionated defaults — HTML output, no CSS, and Web Console middleware is automatically removed in development — so simply installing the gem is enough. Override anything you need from `config/application.rb` or an environment-specific config:
|
|
25
26
|
|
|
26
27
|
```ruby
|
|
27
28
|
# config/environments/development.rb
|
|
@@ -32,6 +33,7 @@ Rails.application.configure do
|
|
|
32
33
|
cfg.enabled = true # flip to false to restore the stock debug page
|
|
33
34
|
cfg.application_root = Rails.root.to_s # optional: trim this prefix from traces
|
|
34
35
|
cfg.logger = Rails.logger # optional: reuse your preferred logger
|
|
36
|
+
cfg.full_error_param = "concise_errors_full" # optional: query param to trigger full page; set nil/"" to disable
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
```
|
|
@@ -40,6 +42,13 @@ You can also steer the default format via `ENV["CONCISE_ERRORS_FORMAT"]` (`text`
|
|
|
40
42
|
|
|
41
43
|
ConciseErrors only affects the debug middleware (the screen you see when `config.consider_all_requests_local` is true). Production 500 pages continue to use whatever `ActionDispatch::ShowExceptions` is configured to serve.
|
|
42
44
|
|
|
45
|
+
### Viewing the full Rails error page
|
|
46
|
+
|
|
47
|
+
When rendering HTML, ConciseErrors shows a "View full Rails error page" button. Clicking it performs a same-origin request back to the same URL with the `concise_errors_full=1` flag (or your configured `full_error_param`). The original request method, CSRF token, and content type are preserved; for non-GET/HEAD requests the request body is replayed.
|
|
48
|
+
|
|
49
|
+
- To disable the button entirely, set `config.concise_errors.full_error_param = nil` or `""`.
|
|
50
|
+
- If Web Console is installed, the fallback will go through its middleware; otherwise Rails' stock `ActionDispatch::DebugExceptions` page is shown.
|
|
51
|
+
|
|
43
52
|
## Sample Output
|
|
44
53
|
|
|
45
54
|
Plain text format (default):
|
|
@@ -6,7 +6,7 @@ module ConciseErrors
|
|
|
6
6
|
DEFAULT_STACK_LINES = 10
|
|
7
7
|
DEFAULT_FORMAT = :text
|
|
8
8
|
|
|
9
|
-
attr_accessor :stack_trace_lines, :enabled, :logger, :application_root, :cleaner
|
|
9
|
+
attr_accessor :stack_trace_lines, :enabled, :logger, :application_root, :cleaner, :full_error_param
|
|
10
10
|
|
|
11
11
|
def initialize
|
|
12
12
|
reset!
|
|
@@ -31,6 +31,7 @@ module ConciseErrors
|
|
|
31
31
|
@logger = nil
|
|
32
32
|
@application_root = nil
|
|
33
33
|
@cleaner = nil
|
|
34
|
+
@full_error_param = "concise_errors_full"
|
|
34
35
|
end
|
|
35
36
|
end
|
|
36
37
|
end
|
|
@@ -10,6 +10,12 @@ module ConciseErrors
|
|
|
10
10
|
# Replacement middleware that keeps the existing API while producing concise output.
|
|
11
11
|
class DebugExceptions < ::ActionDispatch::DebugExceptions
|
|
12
12
|
def call(env)
|
|
13
|
+
request = ActionDispatch::Request.new(env)
|
|
14
|
+
|
|
15
|
+
return fallback_web_console.call(env) if web_console_request?(request)
|
|
16
|
+
|
|
17
|
+
return fallback_web_console.call(env) if full_error_requested?(request)
|
|
18
|
+
|
|
13
19
|
env["action_dispatch.backtrace_cleaner"] ||= ConciseErrors.configuration.cleaner
|
|
14
20
|
super
|
|
15
21
|
end
|
|
@@ -24,5 +30,31 @@ module ConciseErrors
|
|
|
24
30
|
def logger(request)
|
|
25
31
|
ConciseErrors.logger || super
|
|
26
32
|
end
|
|
33
|
+
|
|
34
|
+
def full_error_requested?(request)
|
|
35
|
+
flag = ConciseErrors.configuration.full_error_param
|
|
36
|
+
return false if flag.nil? || flag.to_s.empty?
|
|
37
|
+
|
|
38
|
+
request.query_parameters.key?(flag.to_s)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def web_console_request?(request)
|
|
42
|
+
return false unless defined?(WebConsole::Middleware)
|
|
43
|
+
|
|
44
|
+
mount_point = WebConsole::Middleware.mount_point
|
|
45
|
+
return false if mount_point.nil? || mount_point.empty?
|
|
46
|
+
|
|
47
|
+
request.path.start_with?(mount_point)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def fallback_debug_exceptions
|
|
51
|
+
@fallback_debug_exceptions ||= ::ActionDispatch::DebugExceptions.new(@app)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def fallback_web_console
|
|
55
|
+
return fallback_debug_exceptions unless defined?(WebConsole::Middleware)
|
|
56
|
+
|
|
57
|
+
@fallback_web_console ||= WebConsole::Middleware.new(fallback_debug_exceptions)
|
|
58
|
+
end
|
|
27
59
|
end
|
|
28
60
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
|
+
require "json"
|
|
4
5
|
require "rack/utils"
|
|
5
6
|
|
|
6
7
|
module ConciseErrors
|
|
@@ -48,6 +49,7 @@ module ConciseErrors
|
|
|
48
49
|
<h1>#{html_escape(heading_line)}</h1>
|
|
49
50
|
<p>#{html_escape(status_line)}</p>
|
|
50
51
|
<pre>#{html_escape(truncated_trace.join("\n"))}</pre>
|
|
52
|
+
#{full_error_controls}
|
|
51
53
|
</body>
|
|
52
54
|
</html>
|
|
53
55
|
HTML
|
|
@@ -91,5 +93,125 @@ module ConciseErrors
|
|
|
91
93
|
|
|
92
94
|
line.sub(%r{\A#{Regexp.escape(root)}/?}, "./")
|
|
93
95
|
end
|
|
96
|
+
|
|
97
|
+
def full_error_controls
|
|
98
|
+
return "" unless flag_parameter
|
|
99
|
+
|
|
100
|
+
payload = full_error_payload
|
|
101
|
+
return "" unless payload
|
|
102
|
+
|
|
103
|
+
button_id = "concise-errors-view-full"
|
|
104
|
+
payload_id = "#{button_id}-payload"
|
|
105
|
+
payload_json = ERB::Util.json_escape(JSON.generate(payload))
|
|
106
|
+
|
|
107
|
+
<<~HTML
|
|
108
|
+
<button type="button" id="#{button_id}">View full Rails error page</button>
|
|
109
|
+
<script type="application/json" id="#{payload_id}">#{payload_json}</script>
|
|
110
|
+
<script>
|
|
111
|
+
(function() {
|
|
112
|
+
var button = document.getElementById("#{button_id}");
|
|
113
|
+
var payloadScript = document.getElementById("#{payload_id}");
|
|
114
|
+
if (!button || !payloadScript) { return; }
|
|
115
|
+
|
|
116
|
+
if (!window.fetch) {
|
|
117
|
+
button.addEventListener("click", function() {
|
|
118
|
+
window.location.assign("#{html_escape(payload.fetch(:url))}");
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
var payloadText = payloadScript.textContent || payloadScript.innerText || "{}";
|
|
124
|
+
var data;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
data = JSON.parse(payloadText);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error("Failed to parse full error request payload", error);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
button.addEventListener("click", function(event) {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
|
|
136
|
+
var options = {
|
|
137
|
+
method: data.method,
|
|
138
|
+
credentials: "same-origin"
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (data.headers && Object.keys(data.headers).length > 0) {
|
|
142
|
+
options.headers = data.headers;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (data.body && data.body.length > 0 && data.method !== "GET" && data.method !== "HEAD") {
|
|
146
|
+
options.body = data.body;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
fetch(data.url, options).then(function(response) {
|
|
150
|
+
return response.text().then(function(html) {
|
|
151
|
+
var doc = document;
|
|
152
|
+
doc.open();
|
|
153
|
+
doc.write(html);
|
|
154
|
+
doc.close();
|
|
155
|
+
});
|
|
156
|
+
}).catch(function(error) {
|
|
157
|
+
console.error("Failed to load full Rails error page", error);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
})();
|
|
161
|
+
</script>
|
|
162
|
+
HTML
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def full_error_payload
|
|
166
|
+
return unless flagged_url
|
|
167
|
+
|
|
168
|
+
{
|
|
169
|
+
method: request.request_method,
|
|
170
|
+
url: flagged_url,
|
|
171
|
+
body: replay_body,
|
|
172
|
+
headers: replay_headers
|
|
173
|
+
}
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def replay_body
|
|
177
|
+
return "" if %w[GET HEAD].include?(request.request_method)
|
|
178
|
+
|
|
179
|
+
request.raw_post.to_s
|
|
180
|
+
rescue EOFError
|
|
181
|
+
""
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def replay_headers
|
|
185
|
+
headers = {}
|
|
186
|
+
content_type = request.get_header("CONTENT_TYPE").to_s
|
|
187
|
+
headers["Content-Type"] = content_type unless content_type.empty?
|
|
188
|
+
|
|
189
|
+
csrf = request.get_header("HTTP_X_CSRF_TOKEN").to_s
|
|
190
|
+
headers["X-CSRF-Token"] = csrf unless csrf.empty?
|
|
191
|
+
|
|
192
|
+
headers
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def flagged_url
|
|
196
|
+
flag = flag_parameter
|
|
197
|
+
return unless flag
|
|
198
|
+
|
|
199
|
+
params = request.query_parameters.dup
|
|
200
|
+
params[flag] = "1"
|
|
201
|
+
|
|
202
|
+
query = Rack::Utils.build_nested_query(params)
|
|
203
|
+
base_path = request.fullpath.split("?", 2).first
|
|
204
|
+
return base_path if query.empty?
|
|
205
|
+
|
|
206
|
+
"#{base_path}?#{query}"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def flag_parameter
|
|
210
|
+
value = configuration.full_error_param
|
|
211
|
+
return if value.nil?
|
|
212
|
+
|
|
213
|
+
string = value.to_s.strip
|
|
214
|
+
string.empty? ? nil : string
|
|
215
|
+
end
|
|
94
216
|
end
|
|
95
217
|
end
|
data/sig/concise_errors.rbs
CHANGED
|
@@ -9,12 +9,14 @@ module ConciseErrors
|
|
|
9
9
|
@logger: untyped
|
|
10
10
|
@application_root: String?
|
|
11
11
|
@cleaner: untyped
|
|
12
|
+
@full_error_param: String?
|
|
12
13
|
|
|
13
14
|
attr_accessor stack_trace_lines: Integer?
|
|
14
15
|
attr_accessor enabled: bool
|
|
15
16
|
attr_accessor logger: untyped
|
|
16
17
|
attr_accessor application_root: String?
|
|
17
18
|
attr_accessor cleaner: untyped
|
|
19
|
+
attr_accessor full_error_param: String?
|
|
18
20
|
|
|
19
21
|
def initialize: () -> void
|
|
20
22
|
def format: () -> Symbol
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: concise_errors
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Obie Fernandez
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actionpack
|