class Bugsnag::Event

rubocop:todo Metrics/ClassLength

Constants

CURRENT_PAYLOAD_VERSION
ERROR_CLASS
HANDLED_EXCEPTION
MAX_EXCEPTIONS_TO_UNWRAP
NOTIFIER_NAME
NOTIFIER_URL
NOTIFIER_VERSION
UNHANDLED_EXCEPTION
UNHANDLED_EXCEPTION_MIDDLEWARE
USER_CALLBACK_SET_SEVERITY
USER_SPECIFIED_SEVERITY

Attributes

api_key[RW]

Your Integration API Key @see Configuration#api_key @return [String, nil]

app_type[RW]

The type of application executing the current code @see Configuration#app_type @return [String, nil]

app_version[RW]

The current version of your application @return [String, nil]

automatic_context[RW]

Context set automatically by Bugsnag uses this attribute, which prevents it from overwriting the user-supplied context @api private @return [String, nil]

breadcrumbs[RW]

The list of breadcrumbs attached to this report @return [Array<Breadcrumb>]

configuration[RW]

@api private @return [Configuration]

context[W]
delivery_method[RW]

The delivery method that will be used for this report @see Configuration#delivery_method @return [Symbol]

errors[R]

A list of errors in this report @return [Array<Error>]

exceptions[RW]

The list of exceptions in this report @deprecated Use {#errors} instead @return [Array<Hash>]

feature_flag_delegate[R]
grouping_hash[RW]

All errors with the same grouping hash will be grouped in the Bugsnag app @return [String]

hostname[RW]

@see Configuration#hostname @return [String]

meta_data[RW]

Arbitrary metadata attached to this report @deprecated Use {#metadata} instead @return [Hash]

original_error[R]

The Exception instance this report was created for @return [Exception]

raw_exceptions[RW]

The raw Exception instances for this report @deprecated Use {#original_error} instead @see exceptions @return [Array<Exception>]

release_stage[RW]

The current stage of the release process, e.g. ‘development’, production’ @see Configuration#release_stage @return [String, nil]

runtime_versions[RW]

@api private @see Configuration#runtime_versions @return [Hash{String => String}]

session[RW]

The session that active when this report was generated @see SessionTracker#get_current_session @return [Hash]

severity[RW]

The severity of this report, e.g. ‘error’, ‘warning’ @return [String]

severity_reason[RW]

@api private @return [Hash]

unhandled[R]

Whether this report is for a handled or unhandled error @return [Boolean]

user[RW]

The current user when this report was generated @return [Hash]

Public Class Methods

new(exception, passed_configuration, auto_notify=false) click to toggle source

Initializes a new report from an exception.

# File lib/bugsnag/report.rb, line 117
def initialize(exception, passed_configuration, auto_notify=false)
  # store the creation time for use as device.time
  @created_at = Time.now.utc.iso8601(3)

  @should_ignore = false
  @unhandled = auto_notify
  @initial_unhandled = @unhandled

  self.configuration = passed_configuration

  @original_error = exception
  self.raw_exceptions = generate_raw_exceptions(exception)
  self.exceptions = generate_exception_list
  @errors = generate_error_list

  self.api_key = configuration.api_key
  self.app_type = configuration.app_type
  self.app_version = configuration.app_version
  self.breadcrumbs = []
  self.context = configuration.context if configuration.context_set?
  self.delivery_method = configuration.delivery_method
  self.hostname = configuration.hostname
  self.runtime_versions = configuration.runtime_versions.dup
  self.meta_data = Utility::Duplicator.duplicate(configuration.metadata)
  self.release_stage = configuration.release_stage
  self.severity = auto_notify ? "error" : "warning"
  self.severity_reason = auto_notify ? {:type => UNHANDLED_EXCEPTION} : {:type => HANDLED_EXCEPTION}
  self.user = {}

  @metadata_delegate = Utility::MetadataDelegate.new
  @feature_flag_delegate = Bugsnag.feature_flag_delegate.dup
end

Public Instance 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/report.rb, line 343
def add_metadata(section, key_or_data, *args)
  @metadata_delegate.add_metadata(@meta_data, section, key_or_data, *args)
end
add_tab(name, value) click to toggle source

Add a new metadata tab to this notification.

@param name [String, to_s] The name of the tab to add @param value [Hash, Object] The value to add to the tab. If the tab already

exists, this will be merged with the existing values. If a Hash is not
given, the value will be placed into the 'custom' tab

@return [void]

@deprecated Use {#add_metadata} instead

# File lib/bugsnag/report.rb, line 179
def add_tab(name, value)
  return if name.nil?

  if value.is_a? Hash
    meta_data[name] ||= {}
    meta_data[name].merge! value
  else
    meta_data["custom"] = {} unless meta_data["custom"]

    meta_data["custom"][name.to_s] = value
  end
end
as_json() click to toggle source

Builds and returns the exception payload for this notification.

@return [Hash]

# File lib/bugsnag/report.rb, line 209
def as_json
  # Build the payload's exception event
  payload_event = {
    app: {
      version: app_version,
      releaseStage: release_stage,
      type: app_type
    },
    breadcrumbs: breadcrumbs.map(&:to_h),
    context: context,
    device: {
      hostname: hostname,
      runtimeVersions: runtime_versions,
      time: @created_at
    },
    exceptions: exceptions,
    featureFlags: @feature_flag_delegate.as_json,
    groupingHash: grouping_hash,
    metaData: meta_data,
    session: session,
    severity: severity,
    severityReason: severity_reason,
    unhandled: @unhandled,
    user: user
  }

  payload_event.reject! {|k, v| v.nil? }

  # return the payload hash
  {
    :apiKey => api_key,
    :notifier => {
      :name => NOTIFIER_NAME,
      :version => NOTIFIER_VERSION,
      :url => NOTIFIER_URL
    },
    :payloadVersion => CURRENT_PAYLOAD_VERSION,
    :events => [payload_event]
  }
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/report.rb, line 360
def clear_metadata(section, *args)
  @metadata_delegate.clear_metadata(@meta_data, section, *args)
end
context() click to toggle source

Additional context for this report @!attribute context @return [String, nil]

# File lib/bugsnag/report.rb, line 154
def context
  return @context if defined?(@context)

  @automatic_context
end
feature_flags() click to toggle source

Get the array of stored feature flags

@return [Array<Bugsnag::FeatureFlag>]

# File lib/bugsnag/report.rb, line 367
def feature_flags
  @feature_flag_delegate.to_a
end
headers() click to toggle source

Returns the headers required for the notification.

@return [Hash{String => String}]

# File lib/bugsnag/report.rb, line 254
def headers
  {
    "Bugsnag-Api-Key" => api_key,
    "Bugsnag-Payload-Version" => CURRENT_PAYLOAD_VERSION,
    "Bugsnag-Sent-At" => Time.now.utc.iso8601(3)
  }
end
ignore!() click to toggle source

Tells the client this report should not be sent.

@return [void]

# File lib/bugsnag/report.rb, line 282
def ignore!
  @should_ignore = true
end
ignore?() click to toggle source

Whether this report should be ignored and not sent.

@return [Boolean]

# File lib/bugsnag/report.rb, line 266
def ignore?
  @should_ignore
end
metadata() click to toggle source

A Hash containing arbitrary metadata @!attribute metadata @return [Hash]

# File lib/bugsnag/report.rb, line 309
def metadata
  @meta_data
end
metadata=(metadata) click to toggle source

@param metadata [Hash] @return [void]

# File lib/bugsnag/report.rb, line 315
def metadata=(metadata)
  @meta_data = metadata
end
remove_tab(name) click to toggle source

Removes a metadata tab from this notification.

@param name [String] @return [void]

@deprecated Use {#clear_metadata} instead

# File lib/bugsnag/report.rb, line 199
def remove_tab(name)
  return if name.nil?

  meta_data.delete(name)
end
request() click to toggle source

Data from the current HTTP request. May be nil if no data has been recorded

@return [Hash, nil]

# File lib/bugsnag/report.rb, line 323
def request
  @meta_data[:request]
end
request_data() click to toggle source

Data set on the configuration to be attached to every error notification.

@return [Hash]

# File lib/bugsnag/report.rb, line 274
def request_data
  configuration.request_data
end
set_user(id = nil, email = nil, name = nil) click to toggle source

Set information about the current user

Additional user fields can be added as metadata in a “user” section

Setting a field to ‘nil’ will remove it from the user data

@param id [String, nil] @param email [String, nil] @param name [String, nil] @return [void]

# File lib/bugsnag/report.rb, line 382
def set_user(id = nil, email = nil, name = nil)
  new_user = { id: id, email: email, name: name }
  new_user.reject! { |key, value| value.nil? }

  @user = new_user
end
summary() click to toggle source

Generates a summary to be attached as a breadcrumb

@return [Hash] a Hash containing the report’s error class, error message, and severity

# File lib/bugsnag/report.rb, line 290
def summary
  # Guard against the exceptions array being removed/changed or emptied here
  if exceptions.respond_to?(:first) && exceptions.first
    {
      :error_class => exceptions.first[:errorClass],
      :message => exceptions.first[:message],
      :severity => severity
    }
  else
    {
      :error_class => "Unknown",
      :severity => severity
    }
  end
end
unhandled=(new_unhandled) click to toggle source
# File lib/bugsnag/report.rb, line 389
def unhandled=(new_unhandled)
  # fix the handled/unhandled counts in the current session
  update_handled_counts(new_unhandled, @unhandled)

  @unhandled = new_unhandled
end
unhandled_overridden?() click to toggle source

Returns true if the unhandled flag has been changed from its initial value

@api private @return [Boolean]

# File lib/bugsnag/report.rb, line 401
def unhandled_overridden?
  @unhandled != @initial_unhandled
end

Private Instance Methods

error_class(exception) click to toggle source
# File lib/bugsnag/report.rb, line 446
def error_class(exception)
  # The "Class" check is for some strange exceptions like Timeout::Error
  # which throw the error class instead of an instance
  (exception.is_a? Class) ? exception.name : exception.class.name
end
error_message(exception, class_name) click to toggle source
# File lib/bugsnag/report.rb, line 452
def error_message(exception, class_name)
  # Ruby 3.2 added Exception#detailed_message for Gems like "Did you mean"
  # to annotate an exception's message
  return exception.message unless exception.respond_to?(:detailed_message)

  # the "highlight" argument may add terminal escape codes to the output,
  # which we don't want to include
  # it _should_ always be present but it's possible to forget to add it or
  # to have implemented this method before Ruby 3.2
  message =
    begin
      exception.detailed_message(highlight: false)
    rescue ArgumentError
      exception.detailed_message
    end

  # the string returned by 'detailed_message' defaults to 'ASCII_8BIT' but
  # is actually UTF-8 encoded; we can't convert the encoding normally as its
  # internal encoding doesn't match its actual encoding
  message.force_encoding(::Encoding::UTF_8) if message.encoding == ::Encoding::ASCII_8BIT

  # remove the class name to be consistent with Exception#message
  message.sub!(" (#{class_name})".encode(message.encoding), "") rescue nil

  message
end
generate_error_list() click to toggle source
# File lib/bugsnag/report.rb, line 440
def generate_error_list
  exceptions.map do |exception|
    Error.new(exception[:errorClass], exception[:message], exception[:stacktrace])
  end
end
generate_exception_list() click to toggle source
# File lib/bugsnag/report.rb, line 428
def generate_exception_list
  raw_exceptions.map do |exception|
    class_name = error_class(exception)

    {
      errorClass: class_name,
      message: error_message(exception, class_name),
      stacktrace: Stacktrace.process(exception.backtrace, configuration)
    }
  end
end
generate_raw_exceptions(exception) click to toggle source
# File lib/bugsnag/report.rb, line 479
def generate_raw_exceptions(exception)
  exceptions = []

  ex = exception
  while ex != nil && !exceptions.include?(ex) && exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP

    unless ex.is_a? Exception
      if ex.respond_to?(:to_exception)
        ex = ex.to_exception
      elsif ex.respond_to?(:exception)
        ex = ex.exception
      end
    end

    unless ex.is_a?(Exception) || (defined?(Java::JavaLang::Throwable) && ex.is_a?(Java::JavaLang::Throwable))
      configuration.warn("Converting non-Exception to RuntimeError: #{ex.inspect}")
      ex = RuntimeError.new(ex.to_s)
      ex.set_backtrace caller
    end

    exceptions << ex

    if ex.respond_to?(:cause) && ex.cause
      ex = ex.cause
    elsif ex.respond_to?(:continued_exception) && ex.continued_exception
      ex = ex.continued_exception
    elsif ex.respond_to?(:original_exception) && ex.original_exception
      ex = ex.original_exception
    else
      ex = nil
    end
  end

  exceptions
end
update_handled_counts(is_unhandled, was_unhandled) click to toggle source
# File lib/bugsnag/report.rb, line 409
def update_handled_counts(is_unhandled, was_unhandled)
  # do nothing if there is no session to update
  return if @session.nil?

  # increment the counts for the current unhandled value
  if is_unhandled
    @session[:events][:unhandled] += 1
  else
    @session[:events][:handled] += 1
  end

  # decrement the counts for the previous unhandled value
  if was_unhandled
    @session[:events][:unhandled] -= 1
  else
    @session[:events][:handled] -= 1
  end
end