Bullet

repository·main·Indexed 27 days ago

https://github.com/flyerhzm/bullet

A performance monitoring tool for Ruby applications that detects N+1 queries, unused eager loading, and missing counter caches during development. It supports ActiveRecord and Mongoid, providing notifications via JavaScript alerts, logs, Slack, and various error tracking services like Sentry, Honeybadger, and Bugsnag. Bullet can be integrated into Rails, Sinatra, ActiveJob, and test suites using RSpec or Minitest.

Tokens
3.7K
Snippets
12
Records
30
Agent score
89%

What's inside bullet

  1. Understand Bullet's internal control flow

    main

    Bullet integrates with Rails by extending ActiveRecord (and ActiveController for Rails 2.x) or by inserting itself as Rack middleware in Rails 3+.

    The request lifecycle follows these steps:

    1. Bullet.start_request is called, resetting detectors and clearing the notification collector.
    2. Rails handles the request; ActiveRecord extensions trigger Detector callbacks.
    3. Detectors identify noteworthy events and create Notification instances, which are stored in the main Bullet module's collector.
    4. After the request is handled, Bullet iterates over each notification using a Presenter to generate inline messages appended to the response body.
    5. Bullet generates out-of-channel messages for each notification.
    6. Bullet calls end_request for each detector.
    7. The cycle repeats for the next request.
  2. Run Bullet in Minitest

    main

    For Minitest, wrap tests by overriding before_setup and after_teardown in your TestCase class to ensure Bullet.start_request and Bullet.end_request are called.

    # test/test_helper.rb
    module ActiveSupport
      class TestCase
        def before_setup
          Bullet.start_request
          super
        end
    
        def after_teardown
          super
          Bullet.perform_out_of_channel_notifications if Bullet.notification?
          Bullet.end_request
        end
      end
    end
  3. Integrate Bullet with Sinatra

    main

    To use Bullet in a Sinatra application, configure it and use the Bullet::Rack middleware. If your application uses a separate middleware for Content-Security-Policy, ensure Bullet::Rack is loaded before that middleware.

    configure :development do
      Bullet.enable = true
      Bullet.bullet_logger = true
      use Bullet::Rack
    end
  4. Integrate Bullet with ActiveJob

    main

    To detect N+1 queries within background jobs, include Bullet::ActiveJob in your ApplicationJob class.

    class ApplicationJob < ActiveJob::Base
      include Bullet::ActiveJob if Rails.env.development?
    end
  5. Add new notification types to Bullet

    main

    To extend Bullet with new detection capabilities, follow these steps:

    1. Add your new detector class to the DETECTORS constant in the main Bullet module.
    2. Add any necessary Rails monkey patches to Bullet.enable.
    3. Add an autoload directive to lib/bullet/detector.rb.
    4. Create a corresponding notification class within the Bullet::Notification namespace.
    5. Add an autoload directive to lib/bullet/notification.rb.

    Note: It is recommended that each Detector has its own dedicated Notification class. For implementation guidance, refer to the existing counter cache detector and its associated notification.

  6. Integrate Bullet with other background job solutions

    main

    For background job solutions other than ActiveJob, use the Bullet.profile method to wrap the job execution logic.

    class ApplicationJob < ActiveJob::Base
      around_perform do |_job, block|
        Bullet.profile do
          block.call
        end
      end
    end
  7. Configure Bullet for Test environments

    main

    Enable Bullet in your test environment configuration (e.g., config/environments/test.rb) to catch N+1 queries during automated tests. You can also set Bullet.raise = true to raise an error immediately when an N+1 query is detected.

    # config/environments/test.rb
    config.after_initialize do
      Bullet.enable = true
      Bullet.bullet_logger = true
      Bullet.raise = true # raise an error if n+1 query occurs
    end
  8. Run Bullet in RSpec tests

    main

    While Controller and integration tests work automatically via Rack middleware, model tests and other non-HTTP tests require manual wrapping using Bullet.start_request and Bullet.end_request. Use the following configuration in spec/rails_helper.rb to wrap every test.

    # spec/rails_helper.rb
    RSpec.configure do |config|
      config.before(:each) do
        Bullet.start_request
      end
    
      config.after(:each) do
        Bullet.perform_out_of_channel_notifications if Bullet.notification?
        Bullet.end_request
      end
    end
  9. Install the Bullet gem

    main

    You can install Bullet as a standalone gem or add it to your Gemfile. If using a Gemfile, ensure bullet is added after activerecord (Rails) or mongoid.

    To install via command line:

    gem install bullet

    To add to a Gemfile (recommended for Rails/Mongoid apps):

    gem 'bullet', group: 'development'

    After adding to your Gemfile, run the generator to create default configuration files:

    bundle exec rails g bullet:install
  10. Configure Bullet detectors

    main

    You can enable or disable specific query detection types. All settings default to true.

    • Bullet.n_plus_one_query_enable: Detects N+1 queries.
    • Bullet.unused_eager_loading_enable: Detects eager-loaded associations that are not used.
    • Bullet.counter_cache_enable: Detects unnecessary COUNT queries that could be avoided with a counter_cache.
  11. Configure Bullet notification systems

    main

    Bullet does not enable notifications by default. You must explicitly enable them in your environment configuration (e.g., config/environments/development.rb).

    Common configuration options include:

    • Bullet.enable: Enables the gem.
    • Bullet.alert: Shows a JavaScript alert in the browser.
    • Bullet.bullet_logger: Logs to Rails.root/log/bullet.log.
    • Bullet.console: Logs to the browser's console.log.
    • Bullet.rails_logger: Adds warnings to the Rails log.
    • Bullet.add_footer: Adds a details footer to the page.
    • Bullet.raise: Raises errors (useful for failing tests in specs).
    • Bullet.slack: Sends notifications to a Slack channel via webhook_url.
    • Bullet.sentry, Bullet.honeybadger, Bullet.bugsnag, Bullet.appsignal, Bullet.airbrake, Bullet.rollbar: Integrates with various error tracking services.
    • Bullet.opentelemetry: Enables OpenTelemetry support.
    config.after_initialize do
      Bullet.enable = true
      Bullet.alert = true
      Bullet.bullet_logger = true
      Bullet.console = true
      Bullet.rails_logger = true
      Bullet.add_footer = true
      Bullet.footer_position = 'bottom_left' # or 'bottom_right', 'top_left', 'top_right'
    end