Formatting Context for Error Tracking Systems
The context hash passed to the global on_exception handler may contain complex objects (like ActiveRecord models, ActionController::Parameters, or Axn::FormObject instances) that aren't easily serialized by error tracking systems. You can format these values to make them more readable.
Basic Example
Axn.configure do |c|
c.on_exception = proc do |e, action:, context:|
formatted_context = format_hash_values(context)
Honeybadger.notify(e, context: { axn_context: formatted_context })
end
end
def format_hash_values(hash)
hash.transform_values do |v|
if v.respond_to?(:to_global_id)
v.to_global_id.to_s
elsif v.is_a?(ActionController::Parameters)
v.to_unsafe_h
elsif v.is_a?(Axn::FormObject)
v.to_h
else
v
end
end
endWhat This Converts
- ActiveRecord objects → Their global ID string (via
to_global_id) ActionController::Parameters→ A plain hashAxn::FormObjectinstances → Their hash representation- Other values → Remain unchanged
This ensures that your error tracking system receives serializable, readable context data instead of complex objects that may not serialize properly.
Recursive Formatting
If your context contains nested hashes with complex objects, you may want to recursively format the entire structure:
def format_hash_values(hash)
hash.transform_values do |v|
case v
when Hash
format_hash_values(v)
when Array
v.map { |item| item.is_a?(Hash) ? format_hash_values(item) : format_value(item) }
else
format_value(v)
end
end
end
def format_value(v)
if v.respond_to?(:to_global_id)
v.to_global_id.to_s
elsif v.is_a?(ActionController::Parameters)
v.to_unsafe_h
elsif v.is_a?(Axn::FormObject)
v.to_h
else
v
end
endAdvanced Example: Production Implementation
ALPHA
The following example is from a production codebase and includes advanced features like retry command generation and error fingerprinting. These patterns are still evolving and may change.
Here's a more comprehensive example that includes additional context and a retry command generator:
Axn.configure do |c|
def format_hash_values(hash)
hash.transform_values do |v|
if v.respond_to?(:to_global_id)
v.to_global_id.to_s
elsif v.is_a?(ActionController::Parameters)
v.to_unsafe_h
elsif v.is_a?(Axn::FormObject)
v.to_h
else
v
end
end
end
def retry_command(action:, context:)
action_name = action.class.name
return nil if action_name.nil?
expected_fields = action.internal_field_configs.map(&:field)
return "#{action_name}.call()" if expected_fields.empty?
args = expected_fields.map do |field|
value = context[field]
"#{field}: #{value.inspect}"
end.join(", ")
"#{action_name}.call(#{args})"
end
c.on_exception = proc do |e, action:, context:|
axn_name = action.class.name || "AnonymousClass"
message = "[#{axn_name}] Raised #{e.class.name}: #{e.message}"
hb_context = {
axn: axn_name,
axn_context: format_hash_values(context),
current_attributes: format_hash_values(Current.attributes),
retry_command: retry_command(action:, context:),
exception: e,
}
fingerprint = [axn_name, e.class.name, e.message].join(" - ")
Honeybadger.notify(message, context: hb_context, backtrace: e.backtrace, fingerprint:)
rescue StandardError => rep
Rails.logger.warn "!! Axn failed to report action failure to honeybadger!\nOriginal exception: #{e}\nReporting exception: #{rep}"
end
endThis example includes:
- Formatted context: Uses
format_hash_valuesto serialize complex objects - Additional context: Includes
Current.attributes(if using a Current pattern) for request-level context - Retry command: Generates a command string that can be used to retry the action with the same arguments
- Error fingerprinting: Creates a fingerprint from action name, exception class, and message to group similar errors
- Error handling: Wraps the Honeybadger notification in a rescue block to prevent reporting failures from masking the original exception
The retry_command function generates a string like MyAction.call(user_id: 123, name: "John") that can be copied from your error tracking system and executed in a Rails console to reproduce the error.