module Bugsnag

rubocop:todo Metrics/ModuleLength

Constants

Event

For now Event is just an alias of Report. This points to the same object so any changes to Report will also affect Event

INTEGRATIONS
LOCK
NIL_EXCEPTION_DESCRIPTION
VERSION

Public Class Methods

add_metadata(section, key_or_data, *args) click to toggle source

Add values to metadata

@overload add_metadata(section, data)

Merges data into the given section of metadata
@param section [String, Symbol]
@param data [Hash]

@overload add_metadata(section, key, value)

Sets key to value in the given section of metadata. If the value is nil
the key will be deleted
@param section [String, Symbol]
@param key [String, Symbol]
@param value

@return [void]

# File lib/bugsnag.rb, line 421
def add_metadata(section, key_or_data, *args)
  configuration.add_metadata(section, key_or_data, *args)
end
add_on_breadcrumb(callback) click to toggle source

Add the given callback to the list of on_breadcrumb callbacks

The on_breadcrumb callbacks will be called when a breadcrumb is left and are passed the {Breadcrumbs::Breadcrumb Breadcrumb} object

Returning false from an on_breadcrumb callback will cause the breadcrumb to be ignored and will prevent any remaining callbacks from being called

@param callback [Proc, Method, call] @return [void]

# File lib/bugsnag.rb, line 357
def add_on_breadcrumb(callback)
  configuration.add_on_breadcrumb(callback)
end
add_on_error(callback) click to toggle source

Add the given callback to the list of on_error callbacks

The on_error callbacks will be called when an error is captured or reported and are passed a {Bugsnag::Report} object

Returning false from an on_error callback will cause the error to be ignored and will prevent any remaining callbacks from being called

@param callback [Proc, Method, call] @return [void]

# File lib/bugsnag.rb, line 330
def add_on_error(callback)
  configuration.add_on_error(callback)
end
at_exit_handler_installed?() click to toggle source

Checks if an at_exit handler has been added.

The {Bugsnag#configure} method will add this automatically, but it can be added manually using {Bugsnag#register_at_exit}.

@return [Boolean]

# File lib/bugsnag.rb, line 176
def at_exit_handler_installed?
  @exit_handler_added ||= false
end
before_notify_callbacks() click to toggle source

Allow access to “before notify” callbacks as an array.

These callbacks will be called whenever an error notification is being made.

@deprecated Use {Bugsnag#add_on_error} instead

# File lib/bugsnag.rb, line 235
def before_notify_callbacks
  Bugsnag.configuration.request_data[:before_callbacks] ||= []
end
breadcrumbs() click to toggle source

Returns the current list of breadcrumbs

This is a per-thread circular buffer, containing at most ‘max_breadcrumbs’ breadcrumbs

@return [Bugsnag::Utility::CircularBuffer]

cleaner() click to toggle source

Returns the client’s Cleaner object, or creates one if not yet created.

@api private

@return [Cleaner]

# File lib/bugsnag.rb, line 390
def cleaner
  @cleaner = nil unless defined?(@cleaner)
  @cleaner || LOCK.synchronize do
    @cleaner ||= Bugsnag::Cleaner.new(configuration)
  end
end
clear_metadata(section, *args) click to toggle source

Clear values from metadata

@overload clear_metadata(section)

Clears the given section of metadata
@param section [String, Symbol]

@overload clear_metadata(section, key)

Clears the key in the given section of metadata
@param section [String, Symbol]
@param key [String, Symbol]

@return [void]

# File lib/bugsnag.rb, line 438
def clear_metadata(section, *args)
  configuration.clear_metadata(section, *args)
end
configuration() click to toggle source

Returns the client’s Configuration object, or creates one if not yet created.

@return [Configuration]

# File lib/bugsnag.rb, line 184
def configuration
  @configuration = nil unless defined?(@configuration)
  @configuration || LOCK.synchronize { @configuration ||= Bugsnag::Configuration.new }
end
configure(validate_api_key=true) { |configuration| ... } click to toggle source

Configure the Bugsnag notifier application-wide settings.

Yields a {Configuration} object to use to set application settings.

@yieldparam configuration [Configuration] @return [void]

# File lib/bugsnag.rb, line 54
def configure(validate_api_key=true)
  yield(configuration) if block_given?

  # Create the session tracker if sessions are enabled to avoid the overhead
  # of creating it on the first request. We skip this if we're not validating
  # the API key as we use this internally before the user's configure block
  # has run, so we don't know if sessions are enabled yet.
  session_tracker if validate_api_key && configuration.auto_capture_sessions

  check_key_valid if validate_api_key
  check_endpoint_setup

  register_at_exit
end
feature_flag_delegate() click to toggle source

Expose the feature flag delegate internally for use when creating new Events

The Bugsnag module’s feature_flag_delegate is request-specific

@return [Bugsnag::Utility::FeatureFlagDelegate] @api private

# File lib/bugsnag.rb, line 448
def feature_flag_delegate
  configuration.request_data[:feature_flag_delegate] ||= Utility::FeatureFlagDelegate.new
end
leave_breadcrumb(name, meta_data={}, type=Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE, auto=:manual) click to toggle source

Leave a breadcrumb to be attached to subsequent reports

@param name [String] the main breadcrumb name/message @param meta_data [Hash] String, Numeric, or Boolean meta data to attach @param type [String] the breadcrumb type, see {Bugsnag::BreadcrumbType} @param auto [Symbol] set to :auto if the breadcrumb is automatically created @return [void]

# File lib/bugsnag.rb, line 275
def leave_breadcrumb(name, meta_data={}, type=Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE, auto=:manual)
  breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(name, type, meta_data, auto)
  validator = Bugsnag::Breadcrumbs::Validator.new(configuration)

  # Initial validation
  validator.validate(breadcrumb)

  # Skip if it's already invalid
  return if breadcrumb.ignore?

  # Run before_breadcrumb_callbacks
  configuration.before_breadcrumb_callbacks.each do |c|
    c.arity > 0 ? c.call(breadcrumb) : c.call
    break if breadcrumb.ignore?
  end

  # Return early if ignored
  return if breadcrumb.ignore?

  # Run on_breadcrumb callbacks
  configuration.on_breadcrumb_callbacks.call(breadcrumb)
  return if breadcrumb.ignore?

  # Validate again in case of callback alteration
  validator.validate(breadcrumb)

  # Add to breadcrumbs buffer if still valid
  configuration.breadcrumbs << breadcrumb unless breadcrumb.ignore?
end
load_integration(integration) click to toggle source

Load a specific integration.

@param integration [Symbol] One of the integrations in {INTEGRATIONS} @return [void]

# File lib/bugsnag.rb, line 258
def load_integration(integration)
  integration = :railtie if integration == :rails
  if INTEGRATIONS.include?(integration) || integration == :railtie
    require "bugsnag/integrations/#{integration}"
  else
    configuration.debug("Integration #{integration} is not currently supported")
  end
end
load_integrations() click to toggle source

Attempts to load all integrations through auto-discovery.

@return [void]

# File lib/bugsnag.rb, line 243
def load_integrations
  require "bugsnag/integrations/railtie" if defined?(Rails::Railtie)
  INTEGRATIONS.each do |integration|
    begin
      require "bugsnag/integrations/#{integration}"
    rescue LoadError
    end
  end
end
metadata() click to toggle source

Global metadata added to every event

@return [Hash]

# File lib/bugsnag.rb, line 401
def metadata
  configuration.metadata
end
notify(exception, auto_notify=false) { |report| ... } click to toggle source

Explicitly notify of an exception.

Optionally accepts a block to append metadata to the yielded report.

# File lib/bugsnag.rb, line 73
def notify(exception, auto_notify=false, &block)
  unless false.equal? auto_notify or true.equal? auto_notify
    configuration.warn("Adding metadata/severity using a hash is no longer supported, please use block syntax instead")
    auto_notify = false
  end

  return unless should_deliver_notification?(exception, auto_notify)

  exception = NIL_EXCEPTION_DESCRIPTION if exception.nil?

  report = Report.new(exception, configuration, auto_notify)

  # If this is an auto_notify we yield the block before the any middleware is run
  begin
    yield(report) if block_given? && auto_notify
  rescue StandardError => e
    configuration.warn("Error in internal notify block: #{e}")
    configuration.warn("Error in internal notify block stacktrace: #{e.backtrace.inspect}")
  end

  if report.ignore?
    configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in auto_notify block")
    return
  end

  # Run internal middleware
  configuration.internal_middleware.run(report)
  if report.ignore?
    configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in internal middlewares")
    return
  end

  # Store before_middleware severity reason for future reference
  initial_severity = report.severity
  initial_reason = report.severity_reason

  # Run users middleware
  configuration.middleware.run(report) do
    if report.ignore?
      configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in user provided middleware")
      return
    end

    # If this is not an auto_notify then the block was provided by the user. This should be the last
    # block that is run as it is the users "most specific" block.
    begin
      yield(report) if block_given? && !auto_notify
    rescue StandardError => e
      configuration.warn("Error in notify block: #{e}")
      configuration.warn("Error in notify block stacktrace: #{e.backtrace.inspect}")
    end

    if report.ignore?
      configuration.debug("Not notifying #{report.exceptions.last[:errorClass]} due to ignore being signified in user provided block")
      return
    end

    # Test whether severity has been changed and ensure severity_reason is consistant in auto_notify case
    if report.severity != initial_severity
      report.severity_reason = {
        :type => Report::USER_CALLBACK_SET_SEVERITY
      }
    else
      report.severity_reason = initial_reason
    end

    if report.unhandled_overridden?
      # let the dashboard know that the unhandled flag was overridden
      report.severity_reason[:unhandledOverridden] = true
    end

    deliver_notification(report)
  end
end
on_error(&block) click to toggle source

Add the given block to the list of on_error callbacks

The on_error callbacks will be called when an error is captured or reported and are passed a {Bugsnag::Report} object

Returning false from an on_error callback will cause the error to be ignored and will prevent any remaining callbacks from being called

@return [void]

# File lib/bugsnag.rb, line 315
def on_error(&block)
  configuration.on_error(&block)
end
pause_session() click to toggle source

Stop any events being attributed to the current session until it is resumed or a new session is started

@see resume_session

@return [void]

# File lib/bugsnag.rb, line 214
def pause_session
  session_tracker.pause_session
end
register_at_exit() click to toggle source

Registers an at_exit function to automatically catch errors on exit.

@return [void]

# File lib/bugsnag.rb, line 152
def register_at_exit
  return if at_exit_handler_installed?
  @exit_handler_added = true
  at_exit do
    if $!
      exception = unwrap_bundler_exception($!)

      Bugsnag.notify(exception, true) do |report|
        report.severity = 'error'
        report.severity_reason = {
          :type => Bugsnag::Report::UNHANDLED_EXCEPTION
        }
      end
    end
  end
end
remove_on_breadcrumb(callback) click to toggle source

Remove the given callback from the list of on_breadcrumb callbacks

Note that this must be the same instance that was passed to {add_on_breadcrumb}, otherwise it will not be removed

@param callback [Proc, Method, call] @return [void]

# File lib/bugsnag.rb, line 369
def remove_on_breadcrumb(callback)
  configuration.remove_on_breadcrumb(callback)
end
remove_on_error(callback) click to toggle source

Remove the given callback from the list of on_error callbacks

Note that this must be the same Proc instance that was passed to {Bugsnag#add_on_error}, otherwise it will not be removed

@param callback [Proc] @return [void]

# File lib/bugsnag.rb, line 342
def remove_on_error(callback)
  configuration.remove_on_error(callback)
end
resume_session() click to toggle source

Resume the current session if it was previously paused. If there is no current session, a new session will be started

@see pause_session

@return [Boolean] true if a paused session was resumed

# File lib/bugsnag.rb, line 225
def resume_session
  session_tracker.resume_session
end
session_tracker() click to toggle source

Returns the client’s SessionTracker object, or creates one if not yet created.

@return [SessionTracker]

# File lib/bugsnag.rb, line 193
def session_tracker
  @session_tracker = nil unless defined?(@session_tracker)
  @session_tracker || LOCK.synchronize { @session_tracker ||= Bugsnag::SessionTracker.new}
end
start_session() click to toggle source

Starts a new session, which allows Bugsnag to track error rates across releases

@return [void]

# File lib/bugsnag.rb, line 203
def start_session
  session_tracker.start_session
end

Private Class Methods

abort_reason(exception, auto_notify) click to toggle source
# File lib/bugsnag.rb, line 462
def abort_reason(exception, auto_notify)
  if !configuration.auto_notify && auto_notify
    "Not notifying because auto_notify is disabled"
  elsif !configuration.valid_api_key?
    "Not notifying due to an invalid api_key"
  elsif !configuration.should_notify_release_stage?
    "Not notifying due to notify_release_stages :#{configuration.notify_release_stages.inspect}"
  elsif exception.respond_to?(:skip_bugsnag) && exception.skip_bugsnag
    "Not notifying due to skip_bugsnag flag"
  end
end
check_endpoint_setup() click to toggle source

Verifies the current endpoint setup

If only a notify_endpoint has been set, session tracking will be disabled If only a session_endpoint has been set, and ArgumentError will be raised

# File lib/bugsnag.rb, line 524
def check_endpoint_setup
  notify_set = configuration.notify_endpoint && configuration.notify_endpoint != Bugsnag::Configuration::DEFAULT_NOTIFY_ENDPOINT
  session_set = configuration.session_endpoint && configuration.session_endpoint != Bugsnag::Configuration::DEFAULT_SESSION_ENDPOINT
  if notify_set && !session_set
    configuration.warn("The session endpoint has not been set, all further session capturing will be disabled")
    configuration.disable_sessions
  elsif !notify_set && session_set
    raise ArgumentError, "The session endpoint cannot be modified without the notify endpoint"
  end
end
check_key_valid() click to toggle source

Check if the API key is valid and warn (once) if it is not

# File lib/bugsnag.rb, line 511
def check_key_valid
  @key_warning = false unless defined?(@key_warning)
  if !configuration.valid_api_key? && !@key_warning
    configuration.warn("No valid API key has been set, notifications will not be sent")
    @key_warning = true
  end
end
deliver_notification(report) click to toggle source

Deliver the notification to Bugsnag

@param report [Report] @return void

# File lib/bugsnag.rb, line 479
def deliver_notification(report)
  configuration.info("Notifying #{configuration.notify_endpoint} of #{report.exceptions.last[:errorClass]}")

  options = { headers: report.headers }

  delivery_method = Bugsnag::Delivery[configuration.delivery_method]

  if delivery_method.respond_to?(:serialize_and_deliver)
    delivery_method.serialize_and_deliver(
      configuration.notify_endpoint,
      proc { report_to_json(report) },
      configuration,
      options
    )
  else
    delivery_method.deliver(
      configuration.notify_endpoint,
      report_to_json(report),
      configuration,
      options
    )
  end

  leave_breadcrumb(
    report.summary[:error_class],
    report.summary,
    Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE,
    :auto
  )
end
report_to_json(report) click to toggle source

Convert the Report object to JSON

We ensure the report is safe to send by removing recursion, fixing encoding errors and redacting metadata according to “meta_data_filters”

@param report [Report] @return [String]

# File lib/bugsnag.rb, line 543
def report_to_json(report)
  cleaned = cleaner.clean_object(report.as_json)
  trimmed = Bugsnag::Helpers.trim_if_needed(cleaned)

  ::JSON.dump(trimmed)
end
should_deliver_notification?(exception, auto_notify) click to toggle source
# File lib/bugsnag.rb, line 454
def should_deliver_notification?(exception, auto_notify)
  return false unless configuration.enable_events

  reason = abort_reason(exception, auto_notify)
  configuration.debug(reason) unless reason.nil?
  reason.nil?
end
unwrap_bundler_exception(exception) click to toggle source

When running a script with ‘bundle exec’, uncaught exceptions will be converted to “friendly errors” which has the side effect of wrapping them in a SystemExit

By default we ignore SystemExit, so need to unwrap the original exception in order to avoid ignoring real errors

@param exception [Exception] @return [Exception]

# File lib/bugsnag.rb, line 560
def unwrap_bundler_exception(exception)
  running_in_bundler = ENV.include?('BUNDLE_BIN_PATH')

  # See if this exception came from Bundler's 'with_friendly_errors' method
  return exception unless running_in_bundler
  return exception unless exception.is_a?(SystemExit)
  return exception unless exception.respond_to?(:cause)
  return exception unless exception.backtrace.first.include?('/bundler/friendly_errors.rb')
  return exception if exception.cause.nil?

  unwrapped = exception.cause

  # We may need to unwrap another level if the exception came from running
  # an executable file directly (i.e. 'bundle exec <file>'). In this case
  # there can be a SystemExit from 'with_friendly_errors' _and_ a SystemExit
  # from 'kernel_load'
  return unwrapped unless unwrapped.is_a?(SystemExit)
  return unwrapped unless unwrapped.backtrace.first.include?('/bundler/cli/exec.rb')
  return unwrapped if unwrapped.cause.nil?

  unwrapped.cause
end