FindBug
Self-hosted error tracking and performance monitoring for Rails applications.
FindBug provides Sentry-like functionality with all data stored on your own infrastructure using Redis and your database. Zero external dependencies, full data ownership.
Features
- Error Tracking - Capture exceptions with full context, stack traces, and request data
- Performance Monitoring - Track request timing, SQL queries, and automatic N+1 detection
- Self-Hosted - All data stays on your infrastructure (Redis + PostgreSQL/MySQL)
- Zero Performance Impact - Async writes via Redis buffer, never blocks your requests
- Built-in Dashboard - Beautiful web UI for viewing errors and performance metrics
- Multi-channel Alerts - Email, Slack, Discord, and custom webhooks
- Works Out of the Box - Built-in background persister, no job scheduler required
- Rails 7+ Native - Designed for modern Rails applications
Why FindBug?
| Feature | Sentry/Bugsnag | FindBug |
|---|---|---|
| Data Location | Third-party servers | Your infrastructure |
| Monthly Cost | $26+ per seat | Free |
| Privacy/Compliance | Requires DPA | Full control |
| Network Dependency | Required | None |
| Setup Complexity | API keys, SDKs | One gem, one command |
Requirements
- Ruby 3.1+
- Rails 7.0+
- Redis 4.0+
- PostgreSQL or MySQL
Installation
Add to your Gemfile:
gem 'findbug'Run the installer:
bundle install
rails generate findbug:install
rails db:migrateQuick Start
1. Configure Redis (Optional)
FindBug uses Redis as a high-speed buffer. By default, it connects to redis://localhost:6379/1.
To use a different Redis URL, set the environment variable:
export FINDBUG_REDIS_URL=redis://localhost:6379/1Or configure in config/initializers/findbug.rb:
config.redis_url = ENV.fetch("FINDBUG_REDIS_URL", "redis://localhost:6379/1")2. Enable the Dashboard
Set credentials via environment variables:
export FINDBUG_USERNAME=admin
export FINDBUG_PASSWORD=your-secure-passwordAccess the dashboard at: http://localhost:3000/findbug
3. That's It!
FindBug automatically:
- Captures unhandled exceptions
- Monitors request performance
- Persists data to your database (via built-in background thread)
- No additional job scheduler required
Configuration
All configuration options in config/initializers/findbug.rb:
Findbug.configure do |config|
# ===================
# Core Settings
# ===================
config.enabled = !Rails.env.test?
config.redis_url = ENV.fetch("FINDBUG_REDIS_URL", "redis://localhost:6379/1")
config.redis_pool_size = 5
# ===================
# Error Capture
# ===================
config.sample_rate = 1.0 # Capture 100% of errors
config.ignored_exceptions = [
ActiveRecord::RecordNotFound,
ActionController::RoutingError
]
config.ignored_paths = [/^\/health/, /^\/assets/]
# ===================
# Performance Monitoring
# ===================
config.performance_enabled = true
config.performance_sample_rate = 0.1 # Sample 10% of requests
config.slow_request_threshold_ms = 0
config.slow_query_threshold_ms = 100
# ===================
# Data Security
# ===================
config.scrub_fields = %w[password api_key credit_card ssn token secret]
config.scrub_headers = true
# ===================
# Storage & Retention
# ===================
config.retention_days = 30
config.max_buffer_size = 10_000
# ===================
# Dashboard
# ===================
config.web_username = ENV["FINDBUG_USERNAME"]
config.web_password = ENV["FINDBUG_PASSWORD"]
config.web_path = "/findbug"
# ===================
# Alerts (Optional)
# ===================
config.alerts do |alerts|
alerts.throttle_period = 5.minutes
# Slack
# alerts.slack(
# enabled: true,
# webhook_url: ENV["SLACK_WEBHOOK_URL"],
# channel: "#errors"
# )
# Email
# alerts.email(
# enabled: true,
# recipients: ["team@example.com"]
# )
# Discord
# alerts.discord(
# enabled: true,
# webhook_url: ENV["DISCORD_WEBHOOK_URL"]
# )
# Custom Webhook
# alerts.webhook(
# enabled: true,
# url: "https://your-service.com/webhook",
# headers: { "Authorization" => "Bearer token" }
# )
end
endUsage
Automatic Error Capture
FindBug automatically captures:
- Unhandled exceptions in controllers
- Errors reported via
Rails.error.handle/Rails.error.report - Any exception that bubbles up through the middleware stack
Manual Error Capture
# Capture an exception with context
begin
risky_operation
rescue => e
Findbug.capture_exception(e, user_id: current_user.id)
# Handle gracefully...
end
# Capture a message (non-exception event)
Findbug.capture_message("Rate limit exceeded", :warning, user_id: 123)Adding Context
In your ApplicationController:
class ApplicationController < ActionController::Base
before_action :set_findbug_context
private
def set_findbug_context
findbug_set_user(current_user)
findbug_set_context(
plan: current_user&.plan,
organization_id: current_org&.id
)
end
endBreadcrumbs
Track events leading up to an error:
findbug_breadcrumb("User clicked checkout", category: "ui")
findbug_breadcrumb("Payment API called", category: "http", data: { amount: 99.99 })Performance Tracking
Automatic tracking includes:
- HTTP request duration
- SQL query count and timing
- N+1 query detection
- View rendering time
Manual tracking for custom operations:
Findbug.track_performance("external_api_call") do
ExternalAPI.fetch_data
endArchitecture
┌─────────────────────────────────────────────────────────────────┐
│ Your Rails App │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Request ──► Middleware ──► Exception? ──► Redis Buffer │
│ (async, ~1ms) │
│ │ │
│ Request ──► Instrumentation ──► Perf Data ──► Redis Buffer │
│ (async, ~1ms) │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ BackgroundThread │ │
│ │ (every 30s) │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ Dashboard ◄──────────────────── Database (PostgreSQL/MySQL) │
│ (/findbug) │
│ │
└─────────────────────────────────────────────────────────────────┘
Performance Guarantees:
- Error capture: ~1-2ms (async Redis write)
- Never blocks your HTTP requests
- Circuit breaker auto-disables if Redis is unavailable
- Dedicated connection pool (won't affect your app's Redis usage)
Rake Tasks
# Show configuration and system status
rails findbug:status
# Test error capture
rails findbug:test
# Manually flush Redis buffer to database
rails findbug:flush
# Run retention cleanup
rails findbug:cleanup
# Clear Redis buffers (use with caution)
rails findbug:clear_buffers
# Show database statistics
rails findbug:db:statsMulti-Tenant Applications (Apartment/ros-apartment)
If you're using ros-apartment or similar multi-tenant gems with PostgreSQL schemas, FindBug's tables need to stay in the public schema and the dashboard path should be excluded from tenant switching.
1. Exclude FindBug Models
Add FindBug models to the excluded_models list in config/initializers/apartment.rb:
Apartment.configure do |config|
config.excluded_models = %w[
# Your existing excluded models...
Findbug::ErrorEvent
Findbug::PerformanceEvent
]
end2. Exclude FindBug Dashboard Path
Add /findbug to your tenant switching middleware's excluded paths:
class SwitchTenantMiddleware < Apartment::Elevators::Generic
EXCLUDED_PATHS = %w[
/findbug
# Your other excluded paths...
].freeze
def parse_tenant_name(request)
return nil if excluded_path?(request.path)
# ... rest of your tenant logic
end
private
def excluded_path?(path)
EXCLUDED_PATHS.any? { |excluded| path.start_with?(excluded) }
end
end3. Run Migrations in Public Schema
Ensure FindBug migrations run in the public schema:
# Run migrations in public schema only (not per-tenant)
rails db:migrateThe FindBug tables (findbug_error_events, findbug_performance_events) will be created in the public schema and shared across all tenants.
Advanced: Using ActiveJob Instead of Built-in Thread
By default, FindBug uses a built-in background thread for persistence. If you prefer to use ActiveJob with your own job backend:
# config/initializers/findbug.rb
config.auto_persist = false # Disable built-in threadThen schedule the jobs with your preferred scheduler:
# With any scheduler (Sidekiq, GoodJob, Solid Queue, etc.)
Findbug::PersistJob.perform_later # Run every 30 seconds
Findbug::CleanupJob.perform_later # Run dailyAPI Reference
Error Capture
Findbug.capture_exception(exception, context = {})
Findbug.capture_message(message, level = :info, context = {})Performance Tracking
Findbug.track_performance(name) { ... }Controller Helpers
findbug_set_user(user)
findbug_set_context(hash)
findbug_breadcrumb(message, category:, data: {})Configuration
Findbug.config # Access configuration
Findbug.enabled? # Check if enabled
Findbug.reset! # Reset configuration (for testing)Development
git clone https://github.com/ITSSOUMIT/findbug.git
cd findbug
bin/setup
bundle exec rspecContributing
Bug reports and pull requests are welcome on GitHub at https://github.com/ITSSOUMIT/findbug.
If you encounter any bugs, please open an issue or send an email to hey@soumit.in.
- Fork it
- Create your feature branch (
git checkout -b feature/my-feature) - Commit your changes (
git commit -am 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Create a Pull Request
License
The gem is available as open source under the terms of the MIT License.
Credits
Built by Soumit Das.