findbug 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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +375 -0
- data/Rakefile +12 -0
- data/app/controllers/findbug/application_controller.rb +105 -0
- data/app/controllers/findbug/dashboard_controller.rb +93 -0
- data/app/controllers/findbug/errors_controller.rb +129 -0
- data/app/controllers/findbug/performance_controller.rb +80 -0
- data/app/jobs/findbug/alert_job.rb +40 -0
- data/app/jobs/findbug/cleanup_job.rb +132 -0
- data/app/jobs/findbug/persist_job.rb +158 -0
- data/app/models/findbug/error_event.rb +197 -0
- data/app/models/findbug/performance_event.rb +237 -0
- data/app/views/findbug/dashboard/index.html.erb +199 -0
- data/app/views/findbug/errors/index.html.erb +137 -0
- data/app/views/findbug/errors/show.html.erb +185 -0
- data/app/views/findbug/performance/index.html.erb +168 -0
- data/app/views/findbug/performance/show.html.erb +203 -0
- data/app/views/layouts/findbug/application.html.erb +601 -0
- data/lib/findbug/alerts/channels/base.rb +75 -0
- data/lib/findbug/alerts/channels/discord.rb +155 -0
- data/lib/findbug/alerts/channels/email.rb +179 -0
- data/lib/findbug/alerts/channels/slack.rb +149 -0
- data/lib/findbug/alerts/channels/webhook.rb +143 -0
- data/lib/findbug/alerts/dispatcher.rb +126 -0
- data/lib/findbug/alerts/throttler.rb +110 -0
- data/lib/findbug/background_persister.rb +142 -0
- data/lib/findbug/capture/context.rb +301 -0
- data/lib/findbug/capture/exception_handler.rb +141 -0
- data/lib/findbug/capture/exception_subscriber.rb +228 -0
- data/lib/findbug/capture/message_handler.rb +104 -0
- data/lib/findbug/capture/middleware.rb +247 -0
- data/lib/findbug/configuration.rb +381 -0
- data/lib/findbug/engine.rb +109 -0
- data/lib/findbug/performance/instrumentation.rb +336 -0
- data/lib/findbug/performance/transaction.rb +193 -0
- data/lib/findbug/processing/data_scrubber.rb +163 -0
- data/lib/findbug/rails/controller_methods.rb +152 -0
- data/lib/findbug/railtie.rb +222 -0
- data/lib/findbug/storage/circuit_breaker.rb +223 -0
- data/lib/findbug/storage/connection_pool.rb +134 -0
- data/lib/findbug/storage/redis_buffer.rb +285 -0
- data/lib/findbug/tasks/findbug.rake +167 -0
- data/lib/findbug/version.rb +5 -0
- data/lib/findbug.rb +216 -0
- data/lib/generators/findbug/install_generator.rb +67 -0
- data/lib/generators/findbug/templates/POST_INSTALL +41 -0
- data/lib/generators/findbug/templates/create_findbug_error_events.rb +44 -0
- data/lib/generators/findbug/templates/create_findbug_performance_events.rb +47 -0
- data/lib/generators/findbug/templates/initializer.rb +157 -0
- data/sig/findbug.rbs +4 -0
- metadata +251 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 494c404f3c7c5304cc166a253ccf04757b9b2ab2728c280931f9b1643ce5c5aa
|
|
4
|
+
data.tar.gz: 9e21ab5ccfd0c92df80d0517176201a46dc4854e238cc70a2ada04461ff647b0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9bedec8cfb4d78ab1e11205353b883e0fe035e30692d2485208db7382a98e5d97398df436a5acf19e0329eb9d60db4bdbdf69605281c7ba714bcabdaef2c5347
|
|
7
|
+
data.tar.gz: c3d2af6ee0473bd1ac01330b912b892ac23a125c12e11bfb0f9b138a9bc411d1eb6bf722f627d6397c2d20ce308e00d1c18a1e5c8cefed76ca86bec8da3d9ff5
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Soumit Das
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# Findbug
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/findbug)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/ITSSOUMIT/findbug)
|
|
6
|
+
|
|
7
|
+
**Self-hosted error tracking and performance monitoring for Rails applications.**
|
|
8
|
+
|
|
9
|
+
Findbug provides Sentry-like functionality with all data stored on your own infrastructure using Redis and your database. Zero external dependencies, full data ownership.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Error Tracking** - Capture exceptions with full context, stack traces, and request data
|
|
14
|
+
- **Performance Monitoring** - Track request timing, SQL queries, and automatic N+1 detection
|
|
15
|
+
- **Self-Hosted** - All data stays on your infrastructure (Redis + PostgreSQL/MySQL)
|
|
16
|
+
- **Zero Performance Impact** - Async writes via Redis buffer, never blocks your requests
|
|
17
|
+
- **Built-in Dashboard** - Beautiful web UI for viewing errors and performance metrics
|
|
18
|
+
- **Multi-channel Alerts** - Email, Slack, Discord, and custom webhooks
|
|
19
|
+
- **Works Out of the Box** - Built-in background persister, no job scheduler required
|
|
20
|
+
- **Rails 7+ Native** - Designed for modern Rails applications
|
|
21
|
+
|
|
22
|
+
## Why Findbug?
|
|
23
|
+
|
|
24
|
+
| Feature | Sentry/Bugsnag | Findbug |
|
|
25
|
+
|---------|----------------|---------|
|
|
26
|
+
| Data Location | Third-party servers | Your infrastructure |
|
|
27
|
+
| Monthly Cost | $26+ per seat | Free |
|
|
28
|
+
| Privacy/Compliance | Requires DPA | Full control |
|
|
29
|
+
| Network Dependency | Required | None |
|
|
30
|
+
| Setup Complexity | API keys, SDKs | One gem, one command |
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- Ruby 3.1+
|
|
35
|
+
- Rails 7.0+
|
|
36
|
+
- Redis 4.0+
|
|
37
|
+
- PostgreSQL or MySQL
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Add to your Gemfile:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
gem 'findbug'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Run the installer:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
bundle install
|
|
51
|
+
rails generate findbug:install
|
|
52
|
+
rails db:migrate
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### 1. Configure Redis (Optional)
|
|
58
|
+
|
|
59
|
+
Findbug uses Redis as a high-speed buffer. By default, it connects to `redis://localhost:6379/1`.
|
|
60
|
+
|
|
61
|
+
To use a different Redis URL, set the environment variable:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
export FINDBUG_REDIS_URL=redis://localhost:6379/1
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or configure in `config/initializers/findbug.rb`:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
config.redis_url = ENV.fetch("FINDBUG_REDIS_URL", "redis://localhost:6379/1")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Enable the Dashboard
|
|
74
|
+
|
|
75
|
+
Set credentials via environment variables:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
export FINDBUG_USERNAME=admin
|
|
79
|
+
export FINDBUG_PASSWORD=your-secure-password
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Access the dashboard at: `http://localhost:3000/findbug`
|
|
83
|
+
|
|
84
|
+
### 3. That's It!
|
|
85
|
+
|
|
86
|
+
Findbug automatically:
|
|
87
|
+
- Captures unhandled exceptions
|
|
88
|
+
- Monitors request performance
|
|
89
|
+
- Persists data to your database (via built-in background thread)
|
|
90
|
+
- No additional job scheduler required
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
All configuration options in `config/initializers/findbug.rb`:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
Findbug.configure do |config|
|
|
98
|
+
# ===================
|
|
99
|
+
# Core Settings
|
|
100
|
+
# ===================
|
|
101
|
+
config.enabled = !Rails.env.test?
|
|
102
|
+
config.redis_url = ENV.fetch("FINDBUG_REDIS_URL", "redis://localhost:6379/1")
|
|
103
|
+
config.redis_pool_size = 5
|
|
104
|
+
|
|
105
|
+
# ===================
|
|
106
|
+
# Error Capture
|
|
107
|
+
# ===================
|
|
108
|
+
config.sample_rate = 1.0 # Capture 100% of errors
|
|
109
|
+
config.ignored_exceptions = [
|
|
110
|
+
ActiveRecord::RecordNotFound,
|
|
111
|
+
ActionController::RoutingError
|
|
112
|
+
]
|
|
113
|
+
config.ignored_paths = [/^\/health/, /^\/assets/]
|
|
114
|
+
|
|
115
|
+
# ===================
|
|
116
|
+
# Performance Monitoring
|
|
117
|
+
# ===================
|
|
118
|
+
config.performance_enabled = true
|
|
119
|
+
config.performance_sample_rate = 0.1 # Sample 10% of requests
|
|
120
|
+
config.slow_request_threshold_ms = 0
|
|
121
|
+
config.slow_query_threshold_ms = 100
|
|
122
|
+
|
|
123
|
+
# ===================
|
|
124
|
+
# Data Security
|
|
125
|
+
# ===================
|
|
126
|
+
config.scrub_fields = %w[password api_key credit_card ssn token secret]
|
|
127
|
+
config.scrub_headers = true
|
|
128
|
+
|
|
129
|
+
# ===================
|
|
130
|
+
# Storage & Retention
|
|
131
|
+
# ===================
|
|
132
|
+
config.retention_days = 30
|
|
133
|
+
config.max_buffer_size = 10_000
|
|
134
|
+
|
|
135
|
+
# ===================
|
|
136
|
+
# Dashboard
|
|
137
|
+
# ===================
|
|
138
|
+
config.web_username = ENV["FINDBUG_USERNAME"]
|
|
139
|
+
config.web_password = ENV["FINDBUG_PASSWORD"]
|
|
140
|
+
config.web_path = "/findbug"
|
|
141
|
+
|
|
142
|
+
# ===================
|
|
143
|
+
# Alerts (Optional)
|
|
144
|
+
# ===================
|
|
145
|
+
config.alerts do |alerts|
|
|
146
|
+
alerts.throttle_period = 5.minutes
|
|
147
|
+
|
|
148
|
+
# Slack
|
|
149
|
+
# alerts.slack(
|
|
150
|
+
# enabled: true,
|
|
151
|
+
# webhook_url: ENV["SLACK_WEBHOOK_URL"],
|
|
152
|
+
# channel: "#errors"
|
|
153
|
+
# )
|
|
154
|
+
|
|
155
|
+
# Email
|
|
156
|
+
# alerts.email(
|
|
157
|
+
# enabled: true,
|
|
158
|
+
# recipients: ["team@example.com"]
|
|
159
|
+
# )
|
|
160
|
+
|
|
161
|
+
# Discord
|
|
162
|
+
# alerts.discord(
|
|
163
|
+
# enabled: true,
|
|
164
|
+
# webhook_url: ENV["DISCORD_WEBHOOK_URL"]
|
|
165
|
+
# )
|
|
166
|
+
|
|
167
|
+
# Custom Webhook
|
|
168
|
+
# alerts.webhook(
|
|
169
|
+
# enabled: true,
|
|
170
|
+
# url: "https://your-service.com/webhook",
|
|
171
|
+
# headers: { "Authorization" => "Bearer token" }
|
|
172
|
+
# )
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Usage
|
|
178
|
+
|
|
179
|
+
### Automatic Error Capture
|
|
180
|
+
|
|
181
|
+
Findbug automatically captures:
|
|
182
|
+
- Unhandled exceptions in controllers
|
|
183
|
+
- Errors reported via `Rails.error.handle` / `Rails.error.report`
|
|
184
|
+
- Any exception that bubbles up through the middleware stack
|
|
185
|
+
|
|
186
|
+
### Manual Error Capture
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
# Capture an exception with context
|
|
190
|
+
begin
|
|
191
|
+
risky_operation
|
|
192
|
+
rescue => e
|
|
193
|
+
Findbug.capture_exception(e, user_id: current_user.id)
|
|
194
|
+
# Handle gracefully...
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Capture a message (non-exception event)
|
|
198
|
+
Findbug.capture_message("Rate limit exceeded", :warning, user_id: 123)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Adding Context
|
|
202
|
+
|
|
203
|
+
In your `ApplicationController`:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
class ApplicationController < ActionController::Base
|
|
207
|
+
before_action :set_findbug_context
|
|
208
|
+
|
|
209
|
+
private
|
|
210
|
+
|
|
211
|
+
def set_findbug_context
|
|
212
|
+
findbug_set_user(current_user)
|
|
213
|
+
findbug_set_context(
|
|
214
|
+
plan: current_user&.plan,
|
|
215
|
+
organization_id: current_org&.id
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Breadcrumbs
|
|
222
|
+
|
|
223
|
+
Track events leading up to an error:
|
|
224
|
+
|
|
225
|
+
```ruby
|
|
226
|
+
findbug_breadcrumb("User clicked checkout", category: "ui")
|
|
227
|
+
findbug_breadcrumb("Payment API called", category: "http", data: { amount: 99.99 })
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Performance Tracking
|
|
231
|
+
|
|
232
|
+
Automatic tracking includes:
|
|
233
|
+
- HTTP request duration
|
|
234
|
+
- SQL query count and timing
|
|
235
|
+
- N+1 query detection
|
|
236
|
+
- View rendering time
|
|
237
|
+
|
|
238
|
+
Manual tracking for custom operations:
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
Findbug.track_performance("external_api_call") do
|
|
242
|
+
ExternalAPI.fetch_data
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Architecture
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
250
|
+
│ Your Rails App │
|
|
251
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
252
|
+
│ │
|
|
253
|
+
│ Request ──► Middleware ──► Exception? ──► Redis Buffer │
|
|
254
|
+
│ (async, ~1ms) │
|
|
255
|
+
│ │ │
|
|
256
|
+
│ Request ──► Instrumentation ──► Perf Data ──► Redis Buffer │
|
|
257
|
+
│ (async, ~1ms) │
|
|
258
|
+
│ │ │
|
|
259
|
+
│ ▼ │
|
|
260
|
+
│ ┌──────────────────┐ │
|
|
261
|
+
│ │ BackgroundThread │ │
|
|
262
|
+
│ │ (every 30s) │ │
|
|
263
|
+
│ └────────┬─────────┘ │
|
|
264
|
+
│ │ │
|
|
265
|
+
│ ▼ │
|
|
266
|
+
│ Dashboard ◄──────────────────── Database (PostgreSQL/MySQL) │
|
|
267
|
+
│ (/findbug) │
|
|
268
|
+
│ │
|
|
269
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Performance Guarantees:**
|
|
273
|
+
- Error capture: ~1-2ms (async Redis write)
|
|
274
|
+
- Never blocks your HTTP requests
|
|
275
|
+
- Circuit breaker auto-disables if Redis is unavailable
|
|
276
|
+
- Dedicated connection pool (won't affect your app's Redis usage)
|
|
277
|
+
|
|
278
|
+
## Rake Tasks
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Show configuration and system status
|
|
282
|
+
rails findbug:status
|
|
283
|
+
|
|
284
|
+
# Test error capture
|
|
285
|
+
rails findbug:test
|
|
286
|
+
|
|
287
|
+
# Manually flush Redis buffer to database
|
|
288
|
+
rails findbug:flush
|
|
289
|
+
|
|
290
|
+
# Run retention cleanup
|
|
291
|
+
rails findbug:cleanup
|
|
292
|
+
|
|
293
|
+
# Clear Redis buffers (use with caution)
|
|
294
|
+
rails findbug:clear_buffers
|
|
295
|
+
|
|
296
|
+
# Show database statistics
|
|
297
|
+
rails findbug:db:stats
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Advanced: Using ActiveJob Instead of Built-in Thread
|
|
301
|
+
|
|
302
|
+
By default, Findbug uses a built-in background thread for persistence. If you prefer to use ActiveJob with your own job backend:
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
# config/initializers/findbug.rb
|
|
306
|
+
config.auto_persist = false # Disable built-in thread
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Then schedule the jobs with your preferred scheduler:
|
|
310
|
+
|
|
311
|
+
```ruby
|
|
312
|
+
# With any scheduler (Sidekiq, GoodJob, Solid Queue, etc.)
|
|
313
|
+
Findbug::PersistJob.perform_later # Run every 30 seconds
|
|
314
|
+
Findbug::CleanupJob.perform_later # Run daily
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## API Reference
|
|
318
|
+
|
|
319
|
+
### Error Capture
|
|
320
|
+
|
|
321
|
+
```ruby
|
|
322
|
+
Findbug.capture_exception(exception, context = {})
|
|
323
|
+
Findbug.capture_message(message, level = :info, context = {})
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Performance Tracking
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
Findbug.track_performance(name) { ... }
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Controller Helpers
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
findbug_set_user(user)
|
|
336
|
+
findbug_set_context(hash)
|
|
337
|
+
findbug_breadcrumb(message, category:, data: {})
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Configuration
|
|
341
|
+
|
|
342
|
+
```ruby
|
|
343
|
+
Findbug.config # Access configuration
|
|
344
|
+
Findbug.enabled? # Check if enabled
|
|
345
|
+
Findbug.reset! # Reset configuration (for testing)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Development
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
git clone https://github.com/ITSSOUMIT/findbug.git
|
|
352
|
+
cd findbug
|
|
353
|
+
bin/setup
|
|
354
|
+
bundle exec rspec
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Contributing
|
|
358
|
+
|
|
359
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ITSSOUMIT/findbug.
|
|
360
|
+
|
|
361
|
+
If you encounter any bugs, please open an issue or send an email to hey@soumit.in.
|
|
362
|
+
|
|
363
|
+
1. Fork it
|
|
364
|
+
2. Create your feature branch (`git checkout -b feature/my-feature`)
|
|
365
|
+
3. Commit your changes (`git commit -am 'Add my feature'`)
|
|
366
|
+
4. Push to the branch (`git push origin feature/my-feature`)
|
|
367
|
+
5. Create a Pull Request
|
|
368
|
+
|
|
369
|
+
## License
|
|
370
|
+
|
|
371
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
372
|
+
|
|
373
|
+
## Credits
|
|
374
|
+
|
|
375
|
+
Built by [Soumit Das](https://github.com/ITSSOUMIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Findbug
|
|
4
|
+
# ApplicationController is the base controller for all Findbug dashboard controllers.
|
|
5
|
+
#
|
|
6
|
+
# AUTHENTICATION
|
|
7
|
+
# ==============
|
|
8
|
+
#
|
|
9
|
+
# We use HTTP Basic Auth for simplicity (same as Sidekiq).
|
|
10
|
+
#
|
|
11
|
+
# Why Basic Auth?
|
|
12
|
+
# - Simple to set up (just username/password in config)
|
|
13
|
+
# - Works with any deployment (no OAuth setup needed)
|
|
14
|
+
# - Stateless (no session management)
|
|
15
|
+
# - Secure enough for internal tools (over HTTPS)
|
|
16
|
+
#
|
|
17
|
+
# If you need more sophisticated auth (SSO, OAuth, role-based):
|
|
18
|
+
# - Override `authenticate!` in your app
|
|
19
|
+
# - Or use a Rack middleware before the engine
|
|
20
|
+
#
|
|
21
|
+
class ApplicationController < ActionController::Base
|
|
22
|
+
# Protect from forgery with exception
|
|
23
|
+
protect_from_forgery with: :exception
|
|
24
|
+
|
|
25
|
+
# Authenticate before all actions
|
|
26
|
+
before_action :authenticate!
|
|
27
|
+
before_action :set_findbug_view_path
|
|
28
|
+
|
|
29
|
+
# Set layout
|
|
30
|
+
layout "findbug/application"
|
|
31
|
+
|
|
32
|
+
# Helper methods
|
|
33
|
+
helper_method :findbug_path
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# HTTP Basic Authentication
|
|
38
|
+
#
|
|
39
|
+
# WHY BASIC AUTH?
|
|
40
|
+
# ---------------
|
|
41
|
+
# 1. Zero setup for users (no OAuth, no devise)
|
|
42
|
+
# 2. Works everywhere (curl, browser, CI)
|
|
43
|
+
# 3. Secure over HTTPS
|
|
44
|
+
# 4. Same pattern as Sidekiq (users know it)
|
|
45
|
+
#
|
|
46
|
+
def authenticate!
|
|
47
|
+
return true unless Findbug.config.web_enabled?
|
|
48
|
+
|
|
49
|
+
authenticate_or_request_with_http_basic("Findbug") do |username, password|
|
|
50
|
+
# Use secure comparison to prevent timing attacks
|
|
51
|
+
secure_compare(username, Findbug.config.web_username) &&
|
|
52
|
+
secure_compare(password, Findbug.config.web_password)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Secure string comparison (constant-time)
|
|
57
|
+
#
|
|
58
|
+
# WHY CONSTANT-TIME?
|
|
59
|
+
# ------------------
|
|
60
|
+
# Normal string comparison stops at the first different character.
|
|
61
|
+
# An attacker could measure response times to guess characters one by one.
|
|
62
|
+
# Constant-time comparison always takes the same time regardless of input.
|
|
63
|
+
#
|
|
64
|
+
def secure_compare(a, b)
|
|
65
|
+
return false if a.nil? || b.nil?
|
|
66
|
+
|
|
67
|
+
ActiveSupport::SecurityUtils.secure_compare(a.to_s, b.to_s)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Helper to get the engine's mount path
|
|
71
|
+
def findbug_path
|
|
72
|
+
Findbug.config.web_path
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Add the gem's view path so templates can be found
|
|
76
|
+
# This runs before each request to ensure the path is set
|
|
77
|
+
def set_findbug_view_path
|
|
78
|
+
return unless defined?(FINDBUG_GEM_ROOT)
|
|
79
|
+
|
|
80
|
+
views_path = File.join(FINDBUG_GEM_ROOT, "app", "views")
|
|
81
|
+
prepend_view_path(views_path) unless view_paths.include?(views_path)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Handle ActiveRecord errors gracefully
|
|
85
|
+
rescue_from ActiveRecord::RecordNotFound do |e|
|
|
86
|
+
flash_error "Record not found"
|
|
87
|
+
redirect_to findbug.root_path
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Safe flash helpers that work with API-only Rails apps
|
|
91
|
+
def flash_success(message)
|
|
92
|
+
flash[:success] = message if flash_available?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def flash_error(message)
|
|
96
|
+
flash[:error] = message if flash_available?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def flash_available?
|
|
100
|
+
respond_to?(:flash) && flash.respond_to?(:[]=)
|
|
101
|
+
rescue NoMethodError
|
|
102
|
+
false
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Findbug
|
|
4
|
+
# DashboardController handles the main dashboard view.
|
|
5
|
+
#
|
|
6
|
+
class DashboardController < ApplicationController
|
|
7
|
+
# GET /findbug
|
|
8
|
+
#
|
|
9
|
+
# Main dashboard with overview of errors and performance.
|
|
10
|
+
#
|
|
11
|
+
def index
|
|
12
|
+
@stats = calculate_stats
|
|
13
|
+
@recent_errors = Findbug::ErrorEvent.unresolved.recent.limit(10)
|
|
14
|
+
@slowest_endpoints = Findbug::PerformanceEvent.slowest_transactions(since: 24.hours.ago, limit: 5)
|
|
15
|
+
@error_trend = calculate_error_trend
|
|
16
|
+
|
|
17
|
+
render template: "findbug/dashboard/index", layout: "findbug/application"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# GET /findbug/health
|
|
21
|
+
#
|
|
22
|
+
# Health check endpoint for monitoring.
|
|
23
|
+
# Returns JSON with system status.
|
|
24
|
+
#
|
|
25
|
+
def health
|
|
26
|
+
status = {
|
|
27
|
+
status: "ok",
|
|
28
|
+
version: Findbug::VERSION,
|
|
29
|
+
redis: check_redis_health,
|
|
30
|
+
database: check_database_health,
|
|
31
|
+
buffer: Findbug::Storage::RedisBuffer.stats
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
render json: status
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# GET /findbug/stats
|
|
38
|
+
#
|
|
39
|
+
# JSON stats endpoint for AJAX updates.
|
|
40
|
+
# Used by Turbo to refresh dashboard stats without full page reload.
|
|
41
|
+
#
|
|
42
|
+
def stats
|
|
43
|
+
render json: calculate_stats
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def calculate_stats
|
|
49
|
+
now = Time.current
|
|
50
|
+
{
|
|
51
|
+
errors: {
|
|
52
|
+
total: Findbug::ErrorEvent.count,
|
|
53
|
+
unresolved: Findbug::ErrorEvent.unresolved.count,
|
|
54
|
+
last_24h: Findbug::ErrorEvent.where("created_at >= ?", 24.hours.ago).count,
|
|
55
|
+
last_7d: Findbug::ErrorEvent.where("created_at >= ?", 7.days.ago).count
|
|
56
|
+
},
|
|
57
|
+
performance: {
|
|
58
|
+
total: Findbug::PerformanceEvent.count,
|
|
59
|
+
last_24h: Findbug::PerformanceEvent.where("captured_at >= ?", 24.hours.ago).count,
|
|
60
|
+
avg_duration: Findbug::PerformanceEvent.where("captured_at >= ?", 24.hours.ago)
|
|
61
|
+
.average(:duration_ms)&.round(2) || 0,
|
|
62
|
+
n_plus_one_count: Findbug::PerformanceEvent.with_n_plus_one
|
|
63
|
+
.where("captured_at >= ?", 24.hours.ago)
|
|
64
|
+
.count
|
|
65
|
+
},
|
|
66
|
+
buffer: Findbug::Storage::RedisBuffer.stats,
|
|
67
|
+
timestamp: now.iso8601
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def calculate_error_trend
|
|
72
|
+
# Get hourly error counts for the last 24 hours
|
|
73
|
+
Findbug::ErrorEvent.where("last_seen_at >= ?", 24.hours.ago)
|
|
74
|
+
.group_by_hour(:last_seen_at)
|
|
75
|
+
.count
|
|
76
|
+
rescue NoMethodError
|
|
77
|
+
# groupdate gem not installed, return simple count
|
|
78
|
+
{}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def check_redis_health
|
|
82
|
+
Findbug::Storage::ConnectionPool.healthy? ? "ok" : "error"
|
|
83
|
+
rescue StandardError
|
|
84
|
+
"error"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def check_database_health
|
|
88
|
+
Findbug::ErrorEvent.connection.active? ? "ok" : "error"
|
|
89
|
+
rescue StandardError
|
|
90
|
+
"error"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|