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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +8 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +375 -0
  6. data/Rakefile +12 -0
  7. data/app/controllers/findbug/application_controller.rb +105 -0
  8. data/app/controllers/findbug/dashboard_controller.rb +93 -0
  9. data/app/controllers/findbug/errors_controller.rb +129 -0
  10. data/app/controllers/findbug/performance_controller.rb +80 -0
  11. data/app/jobs/findbug/alert_job.rb +40 -0
  12. data/app/jobs/findbug/cleanup_job.rb +132 -0
  13. data/app/jobs/findbug/persist_job.rb +158 -0
  14. data/app/models/findbug/error_event.rb +197 -0
  15. data/app/models/findbug/performance_event.rb +237 -0
  16. data/app/views/findbug/dashboard/index.html.erb +199 -0
  17. data/app/views/findbug/errors/index.html.erb +137 -0
  18. data/app/views/findbug/errors/show.html.erb +185 -0
  19. data/app/views/findbug/performance/index.html.erb +168 -0
  20. data/app/views/findbug/performance/show.html.erb +203 -0
  21. data/app/views/layouts/findbug/application.html.erb +601 -0
  22. data/lib/findbug/alerts/channels/base.rb +75 -0
  23. data/lib/findbug/alerts/channels/discord.rb +155 -0
  24. data/lib/findbug/alerts/channels/email.rb +179 -0
  25. data/lib/findbug/alerts/channels/slack.rb +149 -0
  26. data/lib/findbug/alerts/channels/webhook.rb +143 -0
  27. data/lib/findbug/alerts/dispatcher.rb +126 -0
  28. data/lib/findbug/alerts/throttler.rb +110 -0
  29. data/lib/findbug/background_persister.rb +142 -0
  30. data/lib/findbug/capture/context.rb +301 -0
  31. data/lib/findbug/capture/exception_handler.rb +141 -0
  32. data/lib/findbug/capture/exception_subscriber.rb +228 -0
  33. data/lib/findbug/capture/message_handler.rb +104 -0
  34. data/lib/findbug/capture/middleware.rb +247 -0
  35. data/lib/findbug/configuration.rb +381 -0
  36. data/lib/findbug/engine.rb +109 -0
  37. data/lib/findbug/performance/instrumentation.rb +336 -0
  38. data/lib/findbug/performance/transaction.rb +193 -0
  39. data/lib/findbug/processing/data_scrubber.rb +163 -0
  40. data/lib/findbug/rails/controller_methods.rb +152 -0
  41. data/lib/findbug/railtie.rb +222 -0
  42. data/lib/findbug/storage/circuit_breaker.rb +223 -0
  43. data/lib/findbug/storage/connection_pool.rb +134 -0
  44. data/lib/findbug/storage/redis_buffer.rb +285 -0
  45. data/lib/findbug/tasks/findbug.rake +167 -0
  46. data/lib/findbug/version.rb +5 -0
  47. data/lib/findbug.rb +216 -0
  48. data/lib/generators/findbug/install_generator.rb +67 -0
  49. data/lib/generators/findbug/templates/POST_INSTALL +41 -0
  50. data/lib/generators/findbug/templates/create_findbug_error_events.rb +44 -0
  51. data/lib/generators/findbug/templates/create_findbug_performance_events.rb +47 -0
  52. data/lib/generators/findbug/templates/initializer.rb +157 -0
  53. data/sig/findbug.rbs +4 -0
  54. 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
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
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
+ [![Gem Version](https://badge.fury.io/rb/findbug.svg)](https://badge.fury.io/rb/findbug)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![GitHub](https://img.shields.io/github/stars/ITSSOUMIT/findbug?style=social)](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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -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