Class: RightSupport::Notifier::Airbrake

Inherits:
Base show all
Defined in:
lib/right_support/notifiers/airbrake.rb

Constant Summary collapse

SAFE_ENVIRONMENT_KEYS =

Rack environment keys that are safe to send to Errbit, et al.

/^(?:HTTP_|REMOTE_|REQUEST_|SERVER_|QUERY_)/
DEFAULT_ENVIRONMENT_BLACKLIST =

default blacklisted request headers.

%w(
  Authorization Cookie
  Proxy-Authorization
  X-Api-Shared-Secret
  X-Authorization X-Http-Authorization
  X-Delegation-Token
)
DEFAULT_PAYLOAD_BLACKLIST =

default blacklisted payload

%w(password)

Constants included from Log::Mixin

Log::Mixin::Decorator, Log::Mixin::UNDELEGATED

Instance Attribute Summary

Attributes inherited from Base

#backtrace_decoder

Instance Method Summary collapse

Methods inherited from Base

#debug_mode?, #notifiable?

Methods included from Log::Mixin

default_logger, default_logger=, included

Constructor Details

#initialize(api_key, options) ⇒ Airbrake

Returns a new instance of Airbrake.

Parameters:

  • api_key (String)

    required API key (a.k.a project key in paid service)

  • options (Hash)

Options Hash (options):

  • :endpoint (String)

    optional for hosted freeware errbit, defaults to paid airbrake service.

  • :project_id (Integer)

    ignored by the freeware errbit, required to use the paid airbrake service.

  • :environment_blacklist (Array|String)

    header names to obfuscate after merging with default

    ‘Authorization’, ‘Cookie’, ‘X-Api-Shared-Secret’

    or nil or empty

  • :environment_blacklister (Blacklister)

    optional custom environment blacklister. supercedes :environment_blacklist

  • :payload_blacklist (Array|String)

    top-level payload keys to obfuscate after merging with default [‘password’] or nil or empty

  • :payload_blacklister (Blacklister)

    optional custom payload blacklister. supercedes :payload_blacklist

  • :root_directory (String)

    as application root directory. default=<working directory>

  • :environment (String)

    as deployment environment. default=<RACK_ENV>

  • :backtrace_limit (Integer)

    as maximum number of stack frames to decode or negative for all. default = 10.

  • :path_blacklist (String|Array)

    for unwanted paths in backtrace. the blacklisted path substring causes the frame to be omitted when it appears anywhere in the traced path. default = none.

  • :notifiable_callback (Proc)

    optional callback to determine if a particular error is worthy of notification. takes an error parameter and returns true if notifiable, false if ignored.

  • :logger (Logger)

    or nil for default



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/right_support/notifiers/airbrake.rb', line 102

def initialize(api_key, options)
  super(options)
  options = {
    endpoint: nil,
    project_id: nil,
    environment_blacklist: nil,
    environment_blacklister: nil,
    payload_blacklist: nil,
    payload_blacklister: nil,
    root_directory: ::Dir.pwd,
    environment: ::ENV['RACK_ENV'] || 'development'
  }.merge(options)

  # resolve environment blacklist.
  unless @environment_blacklister = options[:environment_blacklister]
    environment_blacklist = (options[:environment_blacklist] || []) + DEFAULT_ENVIRONMENT_BLACKLIST
    environment_blacklist.map! do |eb|
      # prefix environment key with 'HTTP_' for a request header match unless
      # prefix is already known.
      if SAFE_ENVIRONMENT_KEYS.match(eb)
        eb
      else
        'HTTP_' + eb
      end
    end
    @environment_blacklister = ::RightSupport::Notifier::Blacklister::Canonical.new(environment_blacklist)
  end

  # resolve payload blacklist.
  unless @payload_blacklister = options[:payload_blacklister]
    payload_blacklist = (options[:payload_blacklist] || []) + DEFAULT_PAYLOAD_BLACKLIST
    @payload_blacklister = ::RightSupport::Notifier::Blacklister::SnakeCase.new(payload_blacklist)
  end

  ::Airbrake::Backtrace.backtrace_decoder = backtrace_decoder
  ::Airbrake.configure do |config|
    config.host = options[:endpoint] if options[:endpoint]  # defaults to paid airbrake service
    config.project_key = api_key
    config.project_id = Integer(options[:project_id] || 1)  # ignored by freeware errbit service but has type integer in gobrake, etc.
    config.root_directory = options[:root_directory]
    config.environment = options[:environment]
    config.queue_size = 100
    config.workers = 5
    config.logger = options[:logger] || ::RightSupport::Log::Mixin.default_logger
  end
  true
end

Instance Method Details

#notify(notification) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/right_support/notifiers/airbrake.rb', line 150

def notify(notification)
  # RACK environment key whitelisting.
  env = notification.env.select { |k, v| k =~ SAFE_ENVIRONMENT_KEYS }

  # environment (request headers, etc.) blacklisting.
  @environment_blacklister.filter(env)

  # always insert error token.
  env['error.token'] = notification.error_token

  # payload blacklisting.
  if payload = notification.payload
    # deep mash payload to avoid modifying original and to normalize keys.
    # also note that the gem has an Airbrake::PayloadTruncator used to
    # abbreviate the payload before sending so do not expect to always see
    # the full detail on the website.
    payload = ::RightSupport::Data::HashTools.deep_mash(payload)
    @payload_blacklister.filter(payload)
  end

  # note we are only interested in the root cause for airbrake purposes.
  # the airbrake gem will iterate over .cause but there is no need to send
  # the entire cause chain in the notification. full details appear in the
  # logger notifier when DEBUG_MODE=true
  cause, _ = backtrace_decoder.walk_error(notification.error, raw_trace: true)
  notice = ::Airbrake.build_notice(
    cause,
    component: notification.component,
    action: notification.action)
  notice[:params] = payload
  notice[:session] = notification.global_session
  notice[:environment] = env

  # notify
  ::Airbrake.notify(notice)
  true
rescue ::Exception => e
  lines = [e.class.name, e.message, e.backtrace].flatten.compact
  msg = "Failed to notify: #{lines.join("\n")}"
  logger.error(msg)
  true
end