Module: Findbug

Defined in:
lib/findbug.rb,
lib/findbug/engine.rb,
lib/findbug/railtie.rb,
lib/findbug/version.rb,
lib/findbug/configuration.rb,
app/jobs/findbug/alert_job.rb,
lib/findbug/adapter_helper.rb,
lib/findbug/capture/context.rb,
app/jobs/findbug/cleanup_job.rb,
app/jobs/findbug/persist_job.rb,
lib/findbug/alerts/throttler.rb,
lib/findbug/alerts/dispatcher.rb,
app/models/findbug/error_event.rb,
lib/findbug/capture/middleware.rb,
app/models/findbug/alert_channel.rb,
lib/findbug/alerts/channels/base.rb,
lib/findbug/background_persister.rb,
lib/findbug/storage/redis_buffer.rb,
lib/findbug/alerts/channels/email.rb,
lib/findbug/alerts/channels/slack.rb,
lib/findbug/alerts/channels/discord.rb,
lib/findbug/alerts/channels/webhook.rb,
lib/findbug/capture/message_handler.rb,
lib/findbug/performance/transaction.rb,
lib/findbug/storage/circuit_breaker.rb,
lib/findbug/storage/connection_pool.rb,
app/models/findbug/performance_event.rb,
lib/findbug/processing/data_scrubber.rb,
lib/findbug/rails/controller_methods.rb,
lib/findbug/capture/exception_handler.rb,
lib/findbug/performance/instrumentation.rb,
lib/findbug/capture/exception_subscriber.rb,
lib/generators/findbug/install_generator.rb,
lib/generators/findbug/upgrade_generator.rb,
app/controllers/findbug/alerts_controller.rb,
app/controllers/findbug/errors_controller.rb,
app/controllers/findbug/dashboard_controller.rb,
app/controllers/findbug/application_controller.rb,
app/controllers/findbug/performance_controller.rb

Overview

Findbug - Self-hosted error tracking and performance monitoring for Rails

ARCHITECTURE OVERVIEW

Findbug is designed with ONE critical goal: NEVER slow down your application.

How we achieve this:

  1. ASYNC WRITES When an error occurs, we don’t write to the database immediately. Instead, we push to a Redis buffer in a background thread. This takes ~1-2ms and doesn’t block your request.

  2. BACKGROUND PERSISTENCE A periodic job (via ActiveJob or built-in thread) pulls events from Redis and batch-inserts them to the database. This happens outside your request cycle.

  3. CIRCUIT BREAKER If Redis is down, we don’t keep retrying and slowing down your app. The circuit breaker “opens” after 5 failures and stops attempting writes for 30 seconds.

  4. CONNECTION POOLING We maintain our OWN Redis connection pool, separate from your app’s Redis/Sidekiq. This prevents connection contention.

  5. SAMPLING For high-traffic apps, you can sample errors (e.g., capture 50%) to reduce overhead further.

DATA FLOW

[Exception occurs]
       |
       v
[Middleware catches it]
       |
       v
[Scrub sensitive data]
       |
       v
[Push to Redis buffer] <-- Async, non-blocking (Thread.new)
       |
       v
[BackgroundPersister runs every 30s]
       |
       v
[Batch insert to Database]
       |
       v
[Dashboard displays data]

Defined Under Namespace

Modules: AdapterHelper, Alerts, Capture, Generators, Performance, Processing, RailsExt, Storage Classes: AlertChannel, AlertConfiguration, AlertJob, AlertsController, ApplicationController, BackgroundPersister, CleanupJob, Configuration, ConfigurationError, DashboardController, Engine, Error, ErrorEvent, ErrorsController, PerformanceController, PerformanceEvent, PersistJob, Railtie

Constant Summary collapse

VERSION =
"0.5.0"

Class Method Summary collapse

Class Method Details

.capture_exception(exception, context = {}) ⇒ Object

Capture an exception manually

WHY A MANUAL CAPTURE METHOD?


Sometimes you want to capture an exception without crashing. For example, in a rescue block where you handle the error gracefully but still want to track it.

Examples:

begin
  risky_operation
rescue => e
  Findbug.capture_exception(e, user_id: current_user.id)
  raise # re-raise to let Rails handle it
end

Parameters:

  • exception (Exception)

    the exception to capture

  • context (Hash) (defaults to: {})

    additional context to attach



157
158
159
160
161
162
163
164
165
# File 'lib/findbug.rb', line 157

def capture_exception(exception, context = {})
  return unless enabled?
  return unless config.should_capture_exception?(exception)

  Capture::ExceptionHandler.capture(exception, context)
rescue StandardError => e
  # CRITICAL: Never let Findbug crash your app
  logger.error("[Findbug] Failed to capture exception: #{e.message}")
end

.capture_message(message, level = :info, context = {}) ⇒ Object

Capture a message (non-exception event)

Examples:

Findbug.capture_message("User exceeded rate limit", :warning, user_id: 123)

Parameters:

  • message (String)

    the message to capture

  • level (Symbol) (defaults to: :info)

    severity level (:info, :warning, :error)

  • context (Hash) (defaults to: {})

    additional context



176
177
178
179
180
181
182
# File 'lib/findbug.rb', line 176

def capture_message(message, level = :info, context = {})
  return unless enabled?

  Capture::MessageHandler.capture(message, level, context)
rescue StandardError => e
  logger.error("[Findbug] Failed to capture message: #{e.message}")
end

.configConfiguration

Access the configuration object

WHY A CLASS METHOD?


We use ‘Findbug.config` instead of a global variable because:

  1. It’s lazily initialized (created on first access)

  2. It’s thread-safe (||= is atomic in MRI Ruby)

  3. It’s mockable in tests

  4. It follows Ruby conventions (like Rails.config)

Returns:



79
80
81
# File 'lib/findbug.rb', line 79

def config
  @config ||= Configuration.new
end

.configure {|Configuration| ... } ⇒ Object

Configure Findbug with a block

Examples:

Findbug.configure do |config|
  config.redis_url = "redis://localhost:6379/1"
  config.sample_rate = 0.5

  config.alerts do |alerts|
    alerts.slack enabled: true, webhook_url: ENV["SLACK_WEBHOOK"]
  end
end

Yields:



97
98
99
100
101
# File 'lib/findbug.rb', line 97

def configure
  yield(config) if block_given?
  config.validate!
  config
end

.enabled?Boolean

Check if Findbug is enabled

This is a convenience method used throughout the codebase. It checks both the enabled flag AND validates we’re properly configured.

Returns:

  • (Boolean)


134
135
136
# File 'lib/findbug.rb', line 134

def enabled?
  config.enabled && config.redis_url.present?
end

.loggerObject

Get the logger instance

Falls back to Rails.logger, then to a null logger



120
121
122
# File 'lib/findbug.rb', line 120

def logger
  @logger ||= config.logger || (defined?(Rails) && Rails.logger) || Logger.new(IO::NULL)
end

.logger=(new_logger) ⇒ Object

Set a custom logger



125
126
127
# File 'lib/findbug.rb', line 125

def logger=(new_logger)
  @logger = new_logger
end

.reset!Object

Reset configuration to defaults (useful for testing)

WHY EXPOSE THIS?


In tests, you often want to reset state between examples. This makes Findbug test-friendly.



110
111
112
113
114
# File 'lib/findbug.rb', line 110

def reset!
  @config = nil
  @redis_pool = nil
  @logger = nil
end

.track_performance(name) { ... } ⇒ Object

Wrap a block with performance tracking

Examples:

Findbug.track_performance("external_api_call") do
  ExternalAPI.fetch_data
end

Parameters:

  • name (String)

    name for this operation

Yields:

  • the block to track



194
195
196
197
198
199
200
201
# File 'lib/findbug.rb', line 194

def track_performance(name, &block)
  return yield unless enabled? && config.performance_enabled

  Performance::Transaction.track(name, &block)
rescue StandardError => e
  logger.error("[Findbug] Performance tracking failed: #{e.message}")
  yield # Still execute the block even if tracking fails
end