Draft: Airbrake v11 config example for Errbit - 2called-chaos/errbit GitHub Wiki

Errbit currently instructs you to use an airbrake 5.x release which hasn't seen a release since 2017. It's on you to decide if you want to update to a more recent version. Airbrake (v11 by now) has added a lot of functionality since then but Errbit does not support most of it (like performance metrics and job stats). Airbrake also removed their rather sensible default settings which may result in rather spammy notifications when left unconfigured. So why even update? Well as I said, your decision.

The following is an annotated version of an airbrake config currently being used in a production Rails application which includes important settings and sensible defaults. It should serve as a starting point to make your Errbit experience as enjoyable as possible.

Note that if you attempt to use the described method concerning "filter_parameter_logging" the airbrake config file should be named in a way that it is being executed after filter_parameter_logging.rb. Feel free to rename either or both, Rails guarantees that initializers will be executed in sorted order.

Note: only project_key, project_id and host are strictly necessary but it's strongly advised to also disable unsupported features as they impact performance, spam your log and phone home to airbrake.io! Everything else is optional and can be omitted.

Airbrake.configure do |config|
  # disable features errbit cannot handle (log spam and performance impact)
  config.job_stats           = false
  config.query_stats         = false
  config.performance_stats   = false
  config.remote_config       = false

  # project specific, must be set
  config.project_key         = 'YOUR_KEY_HERE_OR_USE_ENV'

  # set app_version to something telling, we use the following:
  config.app_version         = "Ruby: #{RUBY_VERSION} » Rails: #{Rails::VERSION::STRING} » " << `cd #{Rails.root.shellescape} && git log -1 --pretty="%h - %B" HEAD`

  # can always be 1, must be set
  config.project_id          = 1

  # your errbit host, must be set (or you send to airbrake.io)
  config.host                = 'https://errbit.example.com'

  # ignore exceptions in test/dev
  config.ignore_environments = %w[development test]

  # set app root directory
  config.root_directory      = Rails.root

  # set current environment
  config.environment         = Rails.env

  # log to own file
  config.logger              = Logger.new(Rails.root.join("log/airbrake.log"))
  config.logger.level        = Logger::INFO

  # filter_parameter_logging.rb must run first or it's empty!
  # (use naming, e.g. z_airbrake.rb or 01-airbrake/02-filter)
  config.blocklist_keys      = Rails.application.config.filter_parameters
end



# =========================
# = Ignore exceptions/env =
# =========================

AIRBRAKE_IGNORE_ENV = [
  'ACTION_CONTROLLER_INSTANCE',
  'ACTION_DISPATCH_BACKTRACE_CLEANER',
  'ACTION_DISPATCH_COOKIES',
  'ACTION_DISPATCH_COOKIES_DIGEST',
  'ACTION_DISPATCH_COOKIES_SERIALIZER',
  'ACTION_DISPATCH_ENCRYPTED_COOKIE_SALT',
  'ACTION_DISPATCH_ENCRYPTED_SIGNED_COOKIE_SALT',
  'ACTION_DISPATCH_HTTP_AUTH_SALT',
  'ACTION_DISPATCH_KEY_GENERATOR',
  'ACTION_DISPATCH_LOGGER',
  'ACTION_DISPATCH_ROUTES',
  'ACTION_DISPATCH_SECRET_KEY_BASE',
  'ACTION_DISPATCH_SECRET_TOKEN',
  'ACTION_DISPATCH_SIGNED_COOKIE_SALT',
]

AIRBRAKE_IGNORE_EXCEPTIONS = [
  'ActiveRecord::RecordNotFound',
  'ActionController::RoutingError',
  'ActionController::UnknownFormat',
  'ActionController::UnknownAction',
  'ActionController::InvalidCrossOriginRequest',
  'ActionController::InvalidAuthenticityToken',
  'Mime::Type::InvalidMimeType',
]

# Ignore exceptions by class
Airbrake.add_filter do |notice|
  if notice[:errors].any? { |error| AIRBRAKE_IGNORE_EXCEPTIONS.include?(error[:type]) }
    notice.ignore!
  end
end

# Example to ignore errors based on type or message
Airbrake.add_filter do |notice|
  notice.ignore! if notice[:errors].any?{|error|
    error[:type] == "ActionController::BadRequest" &&
    (
      error[:message].to_s.downcase.start_with?("invalid query parameters: non utf-8 value:") ||
      error[:message].to_s.downcase.start_with?("invalid query parameters: invalid encoding for parameter")
    )
  }
end



# ==========================
# = Annotate notifications =
# ==========================

# A filter that collects request body information. Enable it if you are sure you
# don't send sensitive information to Airbrake in your body (such as passwords).
# https://github.com/airbrake/airbrake#requestbodyfilter
# Note: This patched version checks if body responds_to?(:read)
class PatchedAirbrakeBodyFilter < Airbrake::Rack::RequestBodyFilter
  # @see Airbrake::FilterChain#refine
  def call(notice)
    return unless (request = notice.stash[:rack_request])
    return unless request.body

    if request.body.respond_to?(:read)
      notice[:environment][:body] = request.body.read(@length)
      request.body.rewind
    else
      notice[:environment][:body] = request.body.to_s
    end
  end
end
Airbrake.add_filter(PatchedAirbrakeBodyFilter.new)



# Annotate via filter
Airbrake.add_filter do |notice|
  ctx = notice[:context]

  # add link to user, will autolink within errbit
  # (user context was discovered by airbrake via current_user method)
  if uid = ctx.dig(:user, :id)
    ctx[:user][:backend_link] = "https://example.com/admin/users/#{uid}"
  end

  # add headers to environment
  if headers = ctx[:headers]
    headers.each do |k, v|
      nk = k.to_s.gsub(".", "_").upcase
      next if AIRBRAKE_IGNORE_ENV.include?(nk)
      notice[:environment][nk] = v.to_s
    end
  end

  # add request env to environment
  if env = notice.stash[:rack_request]&.env
    env.each do |k, v|
      nk = k.to_s.gsub(".", "_").upcase
      next if AIRBRAKE_IGNORE_ENV.include?(nk)
      notice[:environment][nk] = v.to_s
    end
  end

  # cleanup env hash (remove blank values, sort by key)
  notice[:environment] = notice[:environment].reject{|k, v| v.blank? }.sort_by{|k, v| k.to_s }.to_h
end

Reference: https://www.rubydoc.info/gems/airbrake-ruby/Airbrake/Config