Skip to content

Instantly share code, notes, and snippets.

@stympy
Created July 28, 2026 16:16
Show Gist options
  • Select an option

  • Save stympy/069743afe2169529e6340689c757e3d1 to your computer and use it in GitHub Desktop.

Select an option

Save stympy/069743afe2169529e6340689c757e3d1 to your computer and use it in GitHub Desktop.
Keep Honeybadger error payloads under the 256KB API limit
# ## 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