faultline-rails 0.7.0 → 1.0.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/README.md +229 -121
- data/app/assets/stylesheets/faultline/dashboard.css +8 -0
- data/app/controllers/faultline/logged_exceptions_controller.rb +2 -2
- data/app/javascript/controllers/faultline_controller.js +1 -1
- data/app/javascript/controllers/faultline_detail_controller.js +98 -0
- data/app/views/faultline/logged_exceptions/_show.html.erb +24 -24
- data/app/views/layouts/faultline/application.html.erb +9 -7
- data/lib/faultline/version.rb +1 -1
- data/lib/generators/faultline/templates/javascript/controllers/faultline_controller.js +1 -1
- data/lib/generators/faultline/templates/javascript/controllers/faultline_detail_controller.js +98 -0
- data/lib/generators/faultline/templates/views/faultline/logged_exceptions/_show.html.erb +269 -55
- data/lib/generators/faultline/templates/views/layouts/faultline/application.html.erb +9 -7
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 870d33f7e1f9311ee4e932c60c0b430f91a5bf3f5e2e5c446bf69753647f8eda
|
|
4
|
+
data.tar.gz: 36a4dfce3b186ad20c60f849cc0d7a220579014bf44aa630dab6843d4bd68efa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9311ba143ef33928040c5fcbe360fcd71fd2741d1f03f7793eac409545c988b9c3043c019f64217a0d907a7dde4d593cc5c9e0fffdd8aae9d4d0e334a0b3de2
|
|
7
|
+
data.tar.gz: 7cdae081decc10a6c3d8c406486aec7887fd89ee0d388c7da2da26ea8c7597fb62d34b56912feb7e20263fbb5d745409c36dd2aae57066a648d379e8caf10219
|
data/README.md
CHANGED
|
@@ -5,13 +5,27 @@
|
|
|
5
5
|
|
|
6
6
|
Faultline is a production-friendly exception dashboard for Rails 8. It records unhandled application exceptions and gives your team a fast, searchable view of messages, requests, environments, and backtraces.
|
|
7
7
|
|
|
8
|
-
The dashboard
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
8
|
+
The dashboard features a polished dark/light theme, keyboard shortcuts, tabbed detail view, stats overview, and webhook notifications.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Dark / Light theme** — Toggle with the sun/moon button or press `t`. Remembers preference in localStorage.
|
|
13
|
+
- **Keyboard shortcuts** — Press `?` to see all shortcuts. Navigate with `j`/`k`, focus search with `/`, open details with `Enter`.
|
|
14
|
+
- **Stats overview** — Total, weekly, today, and unique class counts at a glance.
|
|
15
|
+
- **Advanced filters** — Exception type, controller, time range, and full-text search with debounced input.
|
|
16
|
+
- **Tabbed detail view** — Overview, Backtrace, Request, Environment, and User Info tabs.
|
|
17
|
+
- **Prev/Next navigation** — Navigate between exceptions without returning to the list.
|
|
18
|
+
- **Copy to clipboard** — One-click copy for exception ID, message, and backtrace.
|
|
19
|
+
- **Collapsible backtrace** — Long traces collapse automatically; expand on demand.
|
|
20
|
+
- **Metadata grid** — Structured detail cards for exception class, controller, action, time, IP, and user agent.
|
|
21
|
+
- **Page size selector** — Choose 25, 50, or 100 results per page.
|
|
22
|
+
- **Active filter pills** — See and dismiss active filters with one click.
|
|
23
|
+
- **RSS feed** — Subscribe to `/faultline/logged_exceptions/feed.rss`.
|
|
24
|
+
- **Webhook notifications** — POST to Slack, Discord, or custom endpoints on each new exception.
|
|
25
|
+
- **Telegram notifications** — Built-in notifier with rate limiting.
|
|
26
|
+
- **Sidekiq middleware** — Automatically logs background job exceptions.
|
|
27
|
+
- **Cleanup / Retention** — Auto-delete old exceptions via rake tasks.
|
|
28
|
+
- **Structured logging** — JSON log output with request context and metadata.
|
|
15
29
|
|
|
16
30
|
## Requirements
|
|
17
31
|
|
|
@@ -28,7 +42,7 @@ Add Faultline to your application:
|
|
|
28
42
|
gem "faultline-rails"
|
|
29
43
|
```
|
|
30
44
|
|
|
31
|
-
Run the install generator
|
|
45
|
+
Run the install generator:
|
|
32
46
|
|
|
33
47
|
```bash
|
|
34
48
|
bundle install
|
|
@@ -40,53 +54,74 @@ This will:
|
|
|
40
54
|
1. Copy the database migration with proper indexes.
|
|
41
55
|
2. Create a configuration initializer at `config/initializers/faultline.rb`.
|
|
42
56
|
3. Mount the engine in your routes.
|
|
43
|
-
4. Add `rescue_from Exception, with: :log_exception_handler` to
|
|
57
|
+
4. Add `rescue_from Exception, with: :log_exception_handler` to `ApplicationController`.
|
|
44
58
|
|
|
45
59
|
The dashboard is now available at `/faultline`.
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
### Modern UI setup
|
|
62
|
+
|
|
63
|
+
Generate the polished dark/light theme with keyboard shortcuts:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bin/rails generate faultline:customize
|
|
67
|
+
bin/rails stimulus:manifest:update
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This generates:
|
|
71
|
+
- Self-contained layout with sidebar, topbar, and theme toggle
|
|
72
|
+
- Tabbed detail view with metadata grid
|
|
73
|
+
- Keyboard shortcuts dialog
|
|
74
|
+
- Stimulus controllers for theme, sidebar, and interactions
|
|
75
|
+
|
|
76
|
+
Options: `--layout-only`, `--views-only`, `--stimulus-only`, `--initializer-only`
|
|
48
77
|
|
|
49
78
|
## Start logging exceptions
|
|
50
79
|
|
|
51
80
|
Include `Faultline::ExceptionLoggable` in your application controller:
|
|
52
81
|
|
|
53
82
|
```ruby
|
|
54
|
-
# app/controllers/application_controller.rb
|
|
55
83
|
class ApplicationController < ActionController::Base
|
|
56
84
|
include Faultline::ExceptionLoggable
|
|
57
85
|
end
|
|
58
86
|
```
|
|
59
87
|
|
|
60
|
-
Faultline logs the exception and then re-raises it so Rails keeps its normal error handling
|
|
88
|
+
Faultline logs the exception and then re-raises it so Rails keeps its normal error handling.
|
|
61
89
|
|
|
62
|
-
###
|
|
90
|
+
### API controllers
|
|
63
91
|
|
|
64
|
-
|
|
92
|
+
For `ActionController::API` subclasses (e.g., API namespaces):
|
|
65
93
|
|
|
66
|
-
|
|
94
|
+
```ruby
|
|
95
|
+
class Api::V1::BaseController < ActionController::API
|
|
96
|
+
include Faultline::ExceptionLoggable
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Background jobs (Sidekiq)
|
|
101
|
+
|
|
102
|
+
Faultline automatically logs Sidekiq job exceptions when the middleware is configured:
|
|
67
103
|
|
|
68
104
|
```ruby
|
|
69
|
-
|
|
70
|
-
|
|
105
|
+
# config/initializers/sidekiq.rb
|
|
106
|
+
Sidekiq.configure_server do |config|
|
|
107
|
+
config.server_middleware do |chain|
|
|
108
|
+
chain.add FaultlineSidekiqMiddleware
|
|
109
|
+
end
|
|
71
110
|
end
|
|
72
111
|
```
|
|
73
112
|
|
|
74
113
|
## Protect the dashboard
|
|
75
114
|
|
|
76
|
-
The dashboard contains sensitive information
|
|
115
|
+
The dashboard contains sensitive information. **Do not expose it to unauthenticated users.**
|
|
77
116
|
|
|
78
|
-
By default, the dashboard returns `403 Forbidden` for all requests. Configure authentication
|
|
117
|
+
By default, the dashboard returns `403 Forbidden` for all requests. Configure authentication:
|
|
79
118
|
|
|
80
119
|
```ruby
|
|
81
120
|
# config/initializers/faultline.rb
|
|
82
121
|
Rails.application.config.to_prepare do
|
|
83
122
|
Faultline.configure do |config|
|
|
84
123
|
config.auth_block = lambda do |controller|
|
|
85
|
-
|
|
86
|
-
# Examples:
|
|
87
|
-
controller.authenticate_user! # Devise
|
|
88
|
-
# controller.current_user&.admin? # Custom auth
|
|
89
|
-
# false # Block everyone (default)
|
|
124
|
+
controller.current_user&.admin?
|
|
90
125
|
end
|
|
91
126
|
end
|
|
92
127
|
end
|
|
@@ -94,167 +129,240 @@ end
|
|
|
94
129
|
|
|
95
130
|
## Configuration
|
|
96
131
|
|
|
97
|
-
Configure Faultline through the block-style DSL in your initializer:
|
|
98
|
-
|
|
99
132
|
```ruby
|
|
100
133
|
# config/initializers/faultline.rb
|
|
101
134
|
Rails.application.config.to_prepare do
|
|
102
135
|
Faultline.configure do |config|
|
|
103
|
-
#
|
|
104
|
-
config.application_name = "
|
|
136
|
+
# ─── General ─────────────────────────────────────────────
|
|
137
|
+
config.application_name = "My App" # Dashboard title
|
|
138
|
+
config.per_page = 50 # Items per page (default: 30)
|
|
105
139
|
|
|
106
|
-
#
|
|
107
|
-
config.per_page = 50
|
|
108
|
-
|
|
109
|
-
# Authentication block (see "Protect the dashboard" above)
|
|
140
|
+
# ─── Authentication ──────────────────────────────────────
|
|
110
141
|
config.auth_block = lambda do |controller|
|
|
111
142
|
controller.current_user&.admin?
|
|
112
143
|
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
```
|
|
116
144
|
|
|
117
|
-
|
|
145
|
+
# ─── Exception Data ──────────────────────────────────────
|
|
146
|
+
config.exception_data = lambda do |controller|
|
|
147
|
+
{
|
|
148
|
+
user_id: controller.current_user&.id,
|
|
149
|
+
request_id: controller.request.request_id,
|
|
150
|
+
environment: Rails.env
|
|
151
|
+
}
|
|
152
|
+
end
|
|
118
153
|
|
|
119
|
-
|
|
120
|
-
config.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
154
|
+
# ─── Notifications ───────────────────────────────────────
|
|
155
|
+
config.after_create = lambda do |exception|
|
|
156
|
+
MyNotifier.alert(exception)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
config.webhooks = ["https://hooks.slack.com/services/xxx"]
|
|
160
|
+
config.webhook_timeout = 5
|
|
161
|
+
config.webhook_headers = { "Authorization" => "Bearer token" }
|
|
162
|
+
|
|
163
|
+
# ─── Cleanup ─────────────────────────────────────────────
|
|
164
|
+
config.retention_days = 90 # Auto-delete after N days
|
|
165
|
+
|
|
166
|
+
# ─── Appearance ──────────────────────────────────────────
|
|
167
|
+
config.theme = "auto" # "light", "dark", or "auto"
|
|
168
|
+
config.body_class = "" # Custom body CSS class
|
|
169
|
+
config.use_host_layout = false # Use host app's layout
|
|
170
|
+
|
|
171
|
+
# ─── Advanced UI ─────────────────────────────────────────
|
|
172
|
+
config.show_stats = true # Stats overview cards
|
|
173
|
+
config.keyboard_shortcuts = true # Keyboard shortcuts
|
|
174
|
+
config.enable_navigation = true # Prev/next in detail view
|
|
175
|
+
config.backtrace_limit = 30 # Frames before collapse
|
|
176
|
+
config.show_metadata = true # Metadata grid
|
|
177
|
+
config.show_environment = false # Environment tab
|
|
178
|
+
config.show_request = true # Request params tab
|
|
179
|
+
config.page_size_options = [25, 50, 100] # Page size selector
|
|
180
|
+
|
|
181
|
+
# ─── Sidebar Links ───────────────────────────────────────
|
|
182
|
+
config.sidebar_links = [
|
|
183
|
+
{ label: "GitHub", url: "https://github.com/myorg/myapp" },
|
|
184
|
+
{ label: "Docs", url: "/docs" }
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
# ─── Logging ─────────────────────────────────────────────
|
|
188
|
+
config.logging_enabled = true
|
|
189
|
+
config.log_level = :info # :debug, :info, :warn, :error, :fatal
|
|
190
|
+
config.log_file = "log/faultline.log" # JSON log file (nil = none)
|
|
191
|
+
config.log_backtrace = true
|
|
192
|
+
config.log_params = true
|
|
193
|
+
config.log_user_info = true
|
|
194
|
+
config.log_backtrace_limit = 20
|
|
195
|
+
config.deduplication_enabled = false
|
|
196
|
+
config.deduplication_window = 300 # Seconds
|
|
197
|
+
end
|
|
127
198
|
end
|
|
128
199
|
```
|
|
129
200
|
|
|
130
|
-
|
|
201
|
+
## Keyboard shortcuts
|
|
131
202
|
|
|
132
|
-
|
|
133
|
-
class ApplicationController < ActionController::Base
|
|
134
|
-
include Faultline::ExceptionLoggable
|
|
203
|
+
Press `?` in the dashboard to see all shortcuts:
|
|
135
204
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
205
|
+
| Key | Action |
|
|
206
|
+
|---|---|
|
|
207
|
+
| `/` | Focus search |
|
|
208
|
+
| `j` or `↓` | Navigate down |
|
|
209
|
+
| `k` or `↑` | Navigate up |
|
|
210
|
+
| `Enter` | Open selected exception |
|
|
211
|
+
| `p` or `←` | Previous exception |
|
|
212
|
+
| `n` or `→` | Next exception |
|
|
213
|
+
| `t` | Toggle dark/light theme |
|
|
214
|
+
| `1`-`5` | Switch tabs |
|
|
215
|
+
| `Esc` | Close panel / Clear |
|
|
216
|
+
| `?` | Show shortcuts dialog |
|
|
139
217
|
|
|
140
|
-
|
|
218
|
+
## Search & Filter
|
|
141
219
|
|
|
142
|
-
|
|
220
|
+
- **Exception Type** — Select dropdown, auto-submits on change
|
|
221
|
+
- **Controller** — Select dropdown, auto-submits on change
|
|
222
|
+
- **Time Range** — Today, 3 days, 7 days, 30 days
|
|
223
|
+
- **Search** — Full-text search with 400ms debounce
|
|
224
|
+
- **Active Filters** — Pills with one-click dismiss
|
|
143
225
|
|
|
144
|
-
|
|
226
|
+
All filters work with Ransack for advanced search, or fall back to built-in scopes.
|
|
145
227
|
|
|
146
|
-
|
|
147
|
-
- **Sortable columns** — Click column headers to sort by exception class, controller, action, or date.
|
|
148
|
-
- **Sidebar filters** — Quick filter by exception class, controller/action, and time range (today, 3 days, 7 days, 30 days).
|
|
228
|
+
## Notifications
|
|
149
229
|
|
|
150
|
-
|
|
230
|
+
### Webhooks
|
|
151
231
|
|
|
152
|
-
|
|
232
|
+
```ruby
|
|
233
|
+
config.webhooks = [
|
|
234
|
+
"https://hooks.slack.com/services/T00/B00/xxx",
|
|
235
|
+
"https://discord.com/api/webhooks/xxx"
|
|
236
|
+
]
|
|
237
|
+
config.webhook_timeout = 5
|
|
238
|
+
config.webhook_headers = { "Authorization" => "Bearer token" }
|
|
239
|
+
```
|
|
153
240
|
|
|
154
|
-
|
|
241
|
+
POST JSON to each URL on every new exception:
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"exception_class": "RuntimeError",
|
|
245
|
+
"controller_name": "users",
|
|
246
|
+
"action_name": "show",
|
|
247
|
+
"message": "Something went wrong",
|
|
248
|
+
"created_at": "2026-08-22T12:00:00Z",
|
|
249
|
+
"dashboard_url": "http://localhost:3000/faultline/logged_exceptions/42"
|
|
250
|
+
}
|
|
251
|
+
```
|
|
155
252
|
|
|
156
|
-
|
|
157
|
-
- **Controller** — Search by controller name (e.g., `users`, `posts`)
|
|
158
|
-
- **Message** — Full-text search across exception messages
|
|
253
|
+
### Telegram
|
|
159
254
|
|
|
160
|
-
|
|
255
|
+
Set `TELEGRAM_TOKEN` and `TELEGRAM_CHAT_ID` environment variables:
|
|
161
256
|
|
|
162
|
-
|
|
257
|
+
```bash
|
|
258
|
+
export TELEGRAM_TOKEN="your-bot-token"
|
|
259
|
+
export TELEGRAM_CHAT_ID="your-chat-id"
|
|
260
|
+
```
|
|
163
261
|
|
|
164
|
-
|
|
165
|
-
- **Controller** — Sort by controller name
|
|
166
|
-
- **Action** — Sort by action name
|
|
167
|
-
- **Date** — Sort by creation date (newest/oldest first)
|
|
262
|
+
Configure the notifier:
|
|
168
263
|
|
|
169
|
-
|
|
264
|
+
```ruby
|
|
265
|
+
config.after_create = lambda do |exception|
|
|
266
|
+
Faultline::TelegramNotifier.new.notify(exception)
|
|
267
|
+
end
|
|
268
|
+
```
|
|
170
269
|
|
|
171
|
-
|
|
270
|
+
Rate limiting: 10-minute cooldown per exception class + controller combination.
|
|
271
|
+
|
|
272
|
+
## Cleanup / Retention
|
|
172
273
|
|
|
173
|
-
|
|
274
|
+
```ruby
|
|
275
|
+
config.retention_days = 90 # Auto-delete after 90 days
|
|
276
|
+
```
|
|
174
277
|
|
|
175
|
-
|
|
278
|
+
Rake tasks:
|
|
176
279
|
|
|
177
280
|
```bash
|
|
178
|
-
|
|
179
|
-
|
|
281
|
+
rake faultline:cleanup # Delete exceptions older than retention_days
|
|
282
|
+
rake faultline:stats # Show exception statistics
|
|
283
|
+
rake faultline:tail # Tail JSON logs in real-time
|
|
284
|
+
rake faultline:export # Export exceptions to JSON
|
|
285
|
+
rake faultline:test_webhook # Test webhook configuration
|
|
180
286
|
```
|
|
181
287
|
|
|
182
|
-
|
|
288
|
+
## Frontend setup
|
|
183
289
|
|
|
184
|
-
|
|
290
|
+
### Hotwire (default)
|
|
185
291
|
|
|
186
|
-
|
|
292
|
+
Faultline includes `turbo-rails` and `stimulus-rails` as dependencies. For Rails 8, no extra setup is needed.
|
|
187
293
|
|
|
188
|
-
|
|
294
|
+
### Styling
|
|
189
295
|
|
|
190
|
-
|
|
296
|
+
Faultline ships with its own CSS (`faultline/dashboard.css`) that works out of the box with dark/light themes.
|
|
191
297
|
|
|
192
|
-
|
|
193
|
-
bundle show faultline-rails
|
|
194
|
-
```
|
|
298
|
+
#### Tailwind CSS
|
|
195
299
|
|
|
196
|
-
|
|
300
|
+
If your app uses Tailwind, add the gem's views as a source:
|
|
197
301
|
|
|
198
302
|
```css
|
|
303
|
+
/* Tailwind v4 */
|
|
199
304
|
@import "tailwindcss";
|
|
200
|
-
@source "/
|
|
305
|
+
@source "/path/to/faultline-rails/app/views";
|
|
201
306
|
```
|
|
202
307
|
|
|
203
|
-
|
|
308
|
+
#### Esbuild / Propshaft
|
|
204
309
|
|
|
205
|
-
|
|
206
|
-
const faultlinePath = "/absolute/path/to/faultline-rails"
|
|
310
|
+
The layout automatically detects your asset pipeline and loads `application.js`:
|
|
207
311
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
312
|
+
```erb
|
|
313
|
+
<% if defined?(Propshaft) || Rails.application.assets&.find_asset("application.js") %>
|
|
314
|
+
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
|
|
315
|
+
<% elsif respond_to?(:javascript_importmap_tags) %>
|
|
316
|
+
<%= javascript_importmap_tags %>
|
|
317
|
+
<% end %>
|
|
214
318
|
```
|
|
215
319
|
|
|
216
|
-
####
|
|
217
|
-
|
|
218
|
-
Faultline works with **importmap**, **sprockets**, **propshaft**, or **no JS pipeline** at all. The layout adapts to your asset pipeline:
|
|
320
|
+
#### Importmap
|
|
219
321
|
|
|
220
|
-
|
|
221
|
-
- If `turbo_refreshes_with` is available, it enables Turbo morph scrolling.
|
|
222
|
-
- If neither is available, the dashboard works without JavaScript.
|
|
322
|
+
Works automatically. Run `stimulus:manifest:update` to register the faultline controllers.
|
|
223
323
|
|
|
224
|
-
###
|
|
324
|
+
### Stimulus controllers
|
|
225
325
|
|
|
226
|
-
|
|
326
|
+
Three controllers are included:
|
|
227
327
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
3. The built-in CSS in `faultline/application.css` provides fallback styles.
|
|
328
|
+
| Controller | Registered as | Purpose |
|
|
329
|
+
|---|---|---|
|
|
330
|
+
| `faultline_controller.js` | `faultline` | Keyboard nav, search, tabs, copy, backtrace expand |
|
|
331
|
+
| `faultline_sidebar_controller.js` | `faultline-sidebar` | Mobile drawer toggle |
|
|
332
|
+
| `faultline_theme_controller.js` | `faultline-theme` | Dark/light/auto theme toggle |
|
|
234
333
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
- **Search** — Full-text search across exception messages, classes, and controllers.
|
|
238
|
-
- **Sortable columns** — Sort by exception class, controller, action, or date.
|
|
239
|
-
- **Filter** — Filter by exception class, controller/action, or age.
|
|
240
|
-
- **Exception details** — Open full exception details in a Turbo Frame.
|
|
241
|
-
- **Delete** — Delete individual exceptions without leaving the page.
|
|
242
|
-
- **Bulk delete** — Delete the currently visible result set.
|
|
243
|
-
- **Clear history** — Clear the complete exception history.
|
|
244
|
-
- **RSS feed** — Subscribe to `/faultline/logged_exceptions/feed.rss`.
|
|
334
|
+
Run `bin/rails stimulus:manifest:update` after installing to auto-register them.
|
|
245
335
|
|
|
246
336
|
## Data storage
|
|
247
337
|
|
|
248
|
-
The engine creates a `faultline_logged_exceptions` table
|
|
338
|
+
The engine creates a `faultline_logged_exceptions` table with columns for exception class, controller/action, message, backtrace, request, environment, user information, user agent, remote IP, and timestamps.
|
|
249
339
|
|
|
250
340
|
Exception records can contain secrets. Apply your normal database encryption, retention, backup, and access-control policies.
|
|
251
341
|
|
|
342
|
+
## Generators
|
|
343
|
+
|
|
344
|
+
| Generator | Description |
|
|
345
|
+
|---|---|
|
|
346
|
+
| `rails generate faultline:install` | Full setup (migration, initializer, routes) |
|
|
347
|
+
| `rails generate faultline:install --modern` | Install + generate all customizations |
|
|
348
|
+
| `rails generate faultline:customize` | Generate customizable views, layout, and JS |
|
|
349
|
+
| `rails generate faultline:customize --layout-only` | Only the layout |
|
|
350
|
+
| `rails generate faultline:customize --views-only` | Only the views |
|
|
351
|
+
| `rails generate faultline:customize --stimulus-only` | Only the Stimulus controller |
|
|
352
|
+
|
|
252
353
|
## Development
|
|
253
354
|
|
|
254
|
-
Run the test suite
|
|
355
|
+
Run the test suite:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
cd /path/to/faultline-rails
|
|
359
|
+
RAILS_ENV=test bundle exec rails test
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Build the gem:
|
|
255
363
|
|
|
256
364
|
```bash
|
|
257
|
-
|
|
365
|
+
gem build faultline-rails.gemspec
|
|
258
366
|
```
|
|
259
367
|
|
|
260
368
|
The repository includes a Rails dummy application under `test/dummy` for engine integration testing.
|
|
@@ -959,3 +959,11 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
959
959
|
.fl-text-sm { font-size: 0.875rem; }
|
|
960
960
|
.fl-text-muted { color: var(--fl-text-muted); }
|
|
961
961
|
.fl-font-semibold { font-weight: 600; }
|
|
962
|
+
|
|
963
|
+
/* ─── Layout (override host app resets) ─────────────────── */
|
|
964
|
+
html { height: 100%; }
|
|
965
|
+
body { height: 100vh; overflow: hidden; margin: 0; }
|
|
966
|
+
body > div.min-h-full { height: 100vh; min-height: 100vh; }
|
|
967
|
+
body > div.min-h-full > .fl-sidebar { height: 100vh; min-height: 100vh; }
|
|
968
|
+
body > div.min-h-full > div:last-child { display: flex; flex-direction: column; overflow: hidden; }
|
|
969
|
+
body > div.min-h-full > div:last-child > main { flex: 1; overflow-y: auto; }
|
|
@@ -186,12 +186,12 @@ module Faultline
|
|
|
186
186
|
|
|
187
187
|
def prev_exception
|
|
188
188
|
return nil unless @prev_exception_id
|
|
189
|
-
|
|
189
|
+
Struct.new(:id).new(@prev_exception_id)
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
def next_exception
|
|
193
193
|
return nil unless @next_exception_id
|
|
194
|
-
|
|
194
|
+
Struct.new(:id).new(@next_exception_id)
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
def load_exception_tabs
|
|
@@ -251,7 +251,7 @@ export default class extends Controller {
|
|
|
251
251
|
toggleTheme() {
|
|
252
252
|
const themeController = this.application.getControllerForElementAndIdentifier(
|
|
253
253
|
document.documentElement,
|
|
254
|
-
"faultline
|
|
254
|
+
"faultline-theme"
|
|
255
255
|
)
|
|
256
256
|
if (themeController) {
|
|
257
257
|
themeController.toggle()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["tabs", "tabContent", "backtraceContent", "backtraceHidden"]
|
|
5
|
+
|
|
6
|
+
// ─── Tab Switching ────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
switchTab(event) {
|
|
9
|
+
const tabName = event.params.tab || event.currentTarget.getAttribute("data-fl-tab-param")
|
|
10
|
+
if (!tabName) return
|
|
11
|
+
|
|
12
|
+
// Update tab buttons
|
|
13
|
+
this.tabsTarget.querySelectorAll(".fl-tab").forEach(tab => {
|
|
14
|
+
const isActive = tab.getAttribute("data-fl-tab-param") === tabName
|
|
15
|
+
tab.classList.toggle("active", isActive)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// Show/hide tab content
|
|
19
|
+
this.tabContentTargets.forEach(content => {
|
|
20
|
+
const isTarget = content.getAttribute("data-fl-tab-content") === tabName
|
|
21
|
+
content.style.display = isTarget ? "" : "none"
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── Copy to Clipboard ───────────────────────────────────
|
|
26
|
+
|
|
27
|
+
copyId(event) {
|
|
28
|
+
const id = event.currentTarget.getAttribute("data-faultline-detail-id-value") ||
|
|
29
|
+
event.currentTarget.dataset.faultlineDetailIdValue
|
|
30
|
+
if (id) this.copyToClipboard(id, event.currentTarget)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
copyMessage(event) {
|
|
34
|
+
const msg = event.currentTarget.getAttribute("data-faultline-detail-message-value") ||
|
|
35
|
+
event.currentTarget.dataset.faultlineDetailMessageValue
|
|
36
|
+
if (msg) this.copyToClipboard(msg, event.currentTarget)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
copyBacktrace(event) {
|
|
40
|
+
const bt = event.currentTarget.getAttribute("data-faultline-detail-backtrace-value") ||
|
|
41
|
+
event.currentTarget.dataset.faultlineDetailBacktraceValue
|
|
42
|
+
if (bt) this.copyToClipboard(bt, event.currentTarget)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async copyToClipboard(text, button) {
|
|
46
|
+
try {
|
|
47
|
+
await navigator.clipboard.writeText(text)
|
|
48
|
+
} catch {
|
|
49
|
+
const textarea = document.createElement("textarea")
|
|
50
|
+
textarea.value = text
|
|
51
|
+
textarea.style.position = "fixed"
|
|
52
|
+
textarea.style.opacity = "0"
|
|
53
|
+
document.body.appendChild(textarea)
|
|
54
|
+
textarea.select()
|
|
55
|
+
document.execCommand("copy")
|
|
56
|
+
document.body.removeChild(textarea)
|
|
57
|
+
}
|
|
58
|
+
const orig = button.innerHTML
|
|
59
|
+
button.classList.add("copied")
|
|
60
|
+
button.innerHTML = `<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" /></svg> Copied!`
|
|
61
|
+
setTimeout(() => { button.classList.remove("copied"); button.innerHTML = orig }, 2000)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Backtrace Expand ────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
expandBacktrace(event) {
|
|
67
|
+
event.preventDefault()
|
|
68
|
+
const hidden = this.backtraceHiddenTarget
|
|
69
|
+
if (hidden) {
|
|
70
|
+
hidden.style.display = hidden.style.display === "none" ? "" : "none"
|
|
71
|
+
event.currentTarget.textContent = hidden.style.display === "none"
|
|
72
|
+
? "Show more frames"
|
|
73
|
+
: "Hide additional frames"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Close Details ───────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
closeDetails() {
|
|
80
|
+
const frame = document.getElementById("exception-details")
|
|
81
|
+
if (frame) {
|
|
82
|
+
frame.innerHTML = `
|
|
83
|
+
<div class="text-center py-12">
|
|
84
|
+
<div class="w-10 h-10 rounded-full flex items-center justify-center mx-auto" style="background: var(--fl-bg-secondary)">
|
|
85
|
+
<svg class="w-5 h-5" style="color: var(--fl-text-muted)" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
86
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
|
|
87
|
+
</svg>
|
|
88
|
+
</div>
|
|
89
|
+
<p class="mt-2 text-xs" style="color: var(--fl-text-muted)">Select an exception to view details</p>
|
|
90
|
+
<p class="mt-1 text-xs" style="color: var(--fl-text-muted)">Click a row or press <span class="fl-kbd">↓</span> <span class="fl-kbd">↑</span> to navigate</p>
|
|
91
|
+
</div>
|
|
92
|
+
`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Deselect rows
|
|
96
|
+
document.querySelectorAll(".fl-table tbody tr.selected").forEach(r => r.classList.remove("selected"))
|
|
97
|
+
}
|
|
98
|
+
}
|