Architecture Deep-Dive
This guide explains the architecture of the honeybadger Ruby gem, and how it interacts with your application.
Who Is This Guide For?
This guide is for anyone interested in learning about how our gem works internally or is attempting to debug/rule out a gem-related issue.
The Major Components of the Gem
The honeybadger
gem has the following components:
-
Notice
— Represents a single exception/error report -
Backend
— Responsible for reporting aNotice
to the honeybadger.io API -
Queue
— A first-in-first-out (FIFO) queue which drops items after reaching a maximum size -
Worker
— A single-threaded worker that is responsible for processingNotice
items in theQueue
and notifying theBackend
-
Initializer
— An integration with a detected framework (such as Rails) -
Plugin
— An isolated integration with a Ruby or 3rd party gem feature -
Config
— The user configuration for the gem -
Agent
— An instance of the gem composed of aConfig
and aWorker
(multipleAgents
are supported, but are uncommon) -
Honeybadger
— The global singletonAgent
What Happens When Your App Boots
The gem has two modes of booting:
-
Normal mode: loads
Initializers
,Config
, andPlugins
automatically (this is what we'll be discussing here) -
Plain Ruby Mode: Skips
automatically loading
Initializers
,Config
, andPlugins
If your app is configured to require 'honeybadger'
(the default when you
install our gem), then it boots in Normal Mode. Here is the order of events
in a Rails app:
- When
'honeybadger'
is required, we immediately:- Detect your framework (Rails, Sinatra,
etc.) and load the respective
Initializer
- Load our Rake
Initializer
if Rake is present in your application - Install our global
at_exit
handler
- Detect your framework (Rails, Sinatra,
etc.) and load the respective
- As Rails initializes, our Rack middleware are inserted in the Rails
middleware stack via the
honeybadger.install_middleware
initializer (see our Railtie) - Rails finishes initializing
- Honeybadger reads
Config
from supported sources - Honeybadger loads
Plugins
- Rails finishes booting
The Life Cycle of an Exception
The honeybadger gem integrates with popular frameworks and libraries to automatically report exceptions when they occur. Examples of where this can happen:
- Rails and Sinatra requests
- Background jobs (ActiveJob, Sidekiq, Resque, etc.)
- Rake tasks
- Ruby crashes (via our global
at_exit
handler)
Here is the order of events when an unhandled exception occurs in one of these scenarios:
- The exception is reported to the global singleton
Agent
usingHoneybadger.notify
- A
Notice
is built from the exception and any other data passed toHoneybadger.notify
,Honeybadger.context
,Honeybadger.add_breadcrumb
, etc. - Configured
before_notify
callbacks are executed, passing theNotice
to each callback (which may modify it) - If the
Notice
is ignored viaConfig
,before_notify
callbacks, etc., then it's immediately dropped. Otherwise, it's pushed to theWorker
. - The
Worker
processes eachNotice
in the order that it was added to itsQueue
. TheQueue
holds up to 100Notices
by default (this number is configurable via themax_queue_size
option). If the number ofNotices
in theQueue
equals themax_queue_size
, newNotices
are dropped until the number is reduced. - When the
Worker
processes a notice, it removes it from theQueue
, passes it to theBackend
, and waits for a response from the honeybadger.io API:-
429
,503
(throttled): Applies an exponential throttle of1.05
. When a throttle is added, theWorker
will briefly pause between processing eachNotice
in theQueue
. Additional throttles are added until the server stops throttling the client. Each new throttle multiplies the previous throttle by1.05
; for example, three429
responses would result in a ~0.158-second pause (((1.05*1.05*1.05)-1)
—we subtract 1 to account for the initial throttle). -
402
,403
(payment required/invalid API key): Suspends theWorker
for 1 hour. During this time, allNotices
are dropped. -
201
(success): if throttled, one throttle per201
response is removed until theWorker
is back to processing theQueue
in real-time.
-
What Happens When Your App Shuts Down
Honeybadger performs the following via our global at_exit
handler:
- If there is an exception that is crashing the Ruby process, it's reported to
Honeybadger.notify
, which calls the backend synchronously (it skips theWorker
) - The
Worker
shuts down. By default, it will wait to process remaining exceptions in theQueue
. Seesend_data_at_exit
andmax_queue_size