Module: Axn::Internal::ExceptionContext

Defined in:
lib/axn/internal/exception_context.rb

Overview

Builds context data for exception reporting to error tracking services (e.g., Honeybadger, Sentry).

Class Method Summary collapse

Class Method Details

.build(action:, retry_context: nil, tags: {}, dimensions: {}) ⇒ Hash

Build enhanced context for global on_exception handler. This method is called internally by Axn's exception handling system.

Parameters:

  • action (Axn::Core)

    The action instance

  • retry_context (Axn::Async::RetryContext, nil) (defaults to: nil)

    Optional async retry context

  • tags (Hash) (defaults to: {})

    Optional declared observability tags (attached verbatim, omitted if empty)

  • dimensions (Hash) (defaults to: {})

    Optional declared observability dimensions (attached verbatim, omitted if empty)

Returns:

  • (Hash)

    Enhanced context with structure: { inputs: { ... }, # User's action inputs (filtered for sensitive data, always formatted) outputs: { ... }, # Action outputs (filtered for sensitive data, always formatted) ...extra_keys..., # Additional context from set_execution_context / hook (formatted) ambient_context: { ... }, # Optional: declared, sensitive-filtered ambient_context if present async: { ... } # Optional: async retry context if applicable }



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/axn/internal/exception_context.rb', line 32

def build(action:, retry_context: nil, tags: {}, dimensions: {})
  # Get structured execution context (inputs, outputs, and extra keys at top level)
  exec_ctx = action.execution_context

  # Start building the context with formatted execution context
  context = {
    inputs: format_hash_values(exec_ctx[:inputs]),
    outputs: format_hash_values(exec_ctx[:outputs] || {}),
  }

  # Add any extra keys from execution context (from set_execution_context / hook)
  extra_keys = exec_ctx.except(:inputs, :outputs)
  context.merge!(format_hash_values(extra_keys)) if extra_keys.any?

  # When this ran nested inside other actions, record the call! chain (outermost → innermost)
  # so a report shows which path reached the failure — the structured breadcrumb the
  # user-facing result.error aggregation deliberately keeps out of the message. The live stack
  # is the full path here because the global report fires once, at the innermost action (which
  # is still on the stack). Omitted for a single (non-nested) action. :axn_stack is a
  # RESERVED_EXECUTION_CONTEXT_KEY, so this never clobbers a user-supplied value.
  stack = Core::NestingTracking._current_axn_stack
  context[:axn_stack] = stack.map { |a| a.class.resolved_axn_name } if stack.length > 1

  # Add async information if available
  context[:async] = retry_context.to_h if retry_context

  # Declared observability facets (PRO-2853), attached under reserved namespaced keys so a
  # consumer's on_exception can route tag → freeform extra, dimension → indexed tags. Values
  # arrive already coerced (Core::Tagging.coerce) and pre-duped (Core::Tagging.dup_facets) by
  # the Executor, so they are attached verbatim — NOT re-run through format_hash_values (which
  # would diverge from what the span/metrics observe) — and a handler mutating them can't
  # corrupt the memoized maps. Omitted when empty, mirroring the other optional keys above.
  context[:tags] = tags if tags.any?
  context[:dimensions] = dimensions if dimensions.any?

  context
end