Created
July 28, 2026 16:16
-
-
Save stympy/069743afe2169529e6340689c757e3d1 to your computer and use it in GitHub Desktop.
Keep Honeybadger error payloads under the 256KB API limit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ## The problem | |
| # | |
| # The Honeybadger API rejects notice payloads larger than 256KB with a 413 | |
| # ("Payload Too Large") response. When that happens the error is silently | |
| # dropped and never shows up in your dashboard. Oversized payloads are usually | |
| # caused by one of a few things: | |
| # | |
| # - Long backtraces — each frame carries a snippet of surrounding source code | |
| # - A large number of breadcrumbs | |
| # - Big request payloads (params, session, CGI/env data) | |
| # - A huge error message (for example, a shelled-out command's full output) | |
| # - Deeply nested exception causes, each with its own full backtrace | |
| # | |
| # ## The fix | |
| # | |
| # This `before_notify` hook measures each notice and, if it's over the limit, | |
| # trims it down *before* it's sent — discarding the least-useful data first so | |
| # your most important debugging information (error class, message, and the top | |
| # of the backtrace) always survives. It only removes as much as it has to: it | |
| # re-checks the size after each step and stops as soon as the notice fits. | |
| # | |
| # ## Installation | |
| # | |
| # Add the `Honeybadger.configure` block below to an initializer: | |
| # | |
| # - Rails: config/initializers/honeybadger.rb | |
| # - Non-Rails: wherever you already call `Honeybadger.configure` | |
| # | |
| # If you already have a `Honeybadger.configure do |config| ... end` block, just | |
| # paste the `config.before_notify do |notice| ... end` section inside it rather | |
| # than adding a second `configure` call. | |
| # | |
| # No other changes are needed. The hook is a no-op for normally-sized errors | |
| # (a single cheap size check), so it's safe to leave on for everything. | |
| # | |
| # ## Tuning | |
| # | |
| # Adjust `max_payload_size` for a smaller safety margin, or change the 16_384 / | |
| # 20 / 30 limits below to keep more or less of each kind of data. | |
| # | |
| # ## Note | |
| # | |
| # This is a lightweight stopgap that uses only public API. A built-in, more | |
| # thorough version (which can shrink backtraces without dropping whole frames) | |
| # is on the way in a future release of the gem. | |
| # ============================================================================== | |
| Honeybadger.configure do |config| | |
| # Target size in bytes. We aim a little under the API's 256KB hard limit to | |
| # leave headroom for transport differences. | |
| max_payload_size = 250_000 | |
| config.before_notify do |notice| | |
| # Cheap check first: if the payload already fits, do nothing. | |
| next if notice.to_json.bytesize <= max_payload_size | |
| # Each entry below removes progressively more valuable data. After each | |
| # one we re-check the size and stop as soon as the notice fits, so we only | |
| # discard as much as we have to. | |
| reductions = [ | |
| # 1. Cap a huge error message (e.g. a command's full stdout) at 16KB. | |
| -> { | |
| msg = notice.error_message | |
| if msg && msg.bytesize > 16_384 | |
| notice.error_message = msg.byteslice(0, 16_384) << " ... [truncated]" | |
| end | |
| }, | |
| # 2. Drop local variables and custom details — useful but rarely load- | |
| # bearing, and both can be large. | |
| -> { | |
| notice.local_variables = {} | |
| notice.details = {} | |
| }, | |
| # 3. Drop request data: CGI/env headers, session, context, and params. | |
| -> { | |
| notice.cgi_data = {} | |
| notice.session = {} | |
| notice.context = {} | |
| notice.params = {} | |
| }, | |
| # 4. Keep only the 20 most recent breadcrumbs. `trail` returns the live | |
| # breadcrumb objects, and `ignore!` excludes one from the payload — | |
| # this is the supported way to prune breadcrumbs from a hook. | |
| -> { | |
| trail = notice.breadcrumbs.trail | |
| trail[0...-20].each(&:ignore!) if trail.size > 20 | |
| }, | |
| # 5. Keep only the top 30 backtrace frames. This is usually the biggest | |
| # win: fewer frames means far fewer inline source-code snippets, which | |
| # are what bloat most oversized payloads. | |
| -> { | |
| bt = notice.backtrace | |
| notice.backtrace = bt.first(30) if bt && bt.size > 30 | |
| }, | |
| # 6. Last resort: drop the exception's cause chain. Nested causes each | |
| # carry their own full backtrace, so this can reclaim a lot at the | |
| # cost of losing the "caused by" detail. | |
| -> { notice.cause = nil } | |
| ] | |
| reductions.each do |reduce| | |
| break if notice.to_json.bytesize <= max_payload_size | |
| reduce.call | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment