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?
Section titled “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
Section titled “The major components of the gem”The honeybadger gem has the following components:
Notice— Represents a single exception/error reportBackend— Responsible for reporting aNoticeto the honeybadger.io APIQueue— A first-in-first-out (FIFO) queue which drops items after reaching a maximum sizeWorker— A single-threaded worker that is responsible for processingNoticeitems in theQueueand notifying theBackendInitializer— An integration with a detected framework (such as Rails)Plugin— An isolated integration with a Ruby or 3rd party gem featureConfig— The user configuration for the gemAgent— An instance of the gem composed of aConfigand aWorker(multipleAgentsare supported, but are uncommon)Honeybadger— The global singletonAgent
What happens when your app boots
Section titled “What happens when your app boots”The gem has two modes of booting:
- Normal mode: loads
Initializers,Config, andPluginsautomatically (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
Initializerif Rake is present in your application - Install our global
at_exithandler - As Rails initializes, our Rack middleware are inserted in the Rails
middleware stack via the
honeybadger.install_middlewareinitializer (see our Railtie) - Rails finishes initializing
- Honeybadger reads
Configfrom supported sources - Honeybadger loads
Plugins - Rails finishes booting
The life cycle of an exception
Section titled “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_exithandler)
Here is the order of events when an unhandled exception occurs in one of these scenarios:
- The exception is reported to the global singleton
AgentusingHoneybadger.notify - A
Noticeis built from the exception and any other data passed toHoneybadger.notify,Honeybadger.context,Honeybadger.add_breadcrumb, etc. - Configured
before_notifycallbacks are executed, passing theNoticeto each callback (which may modify it) - If the
Noticeis ignored viaConfig,before_notifycallbacks, etc., then it’s immediately dropped. Otherwise, it’s pushed to theWorker. - The
Workerprocesses eachNoticein the order that it was added to itsQueue. TheQueueholds up to 100Noticesby default (this number is configurable via themax_queue_sizeoption). If the number ofNoticesin theQueueequals themax_queue_size, newNoticesare dropped until the number is reduced. - When the
Workerprocesses 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, theWorkerwill briefly pause between processing eachNoticein theQueue. Additional throttles are added until the server stops throttling the client. Each new throttle multiplies the previous throttle by1.05; for example, three429responses 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 theWorkerfor 1 hour. During this time, allNoticesare dropped.201(success): if throttled, one throttle per201response is removed until theWorkeris back to processing theQueuein real-time.
What happens when your app shuts down
Section titled “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
Workershuts down. By default, it will wait to process remaining exceptions in theQueue. Seesend_data_at_exitandmax_queue_size