Exception grouping

Errors are automatically grouped based on their stack trace and other identifying characteristics. This helps you see a consolidated view of similar issues, reducing noise and making it easier to prioritize and resolve problems.

When you view the Errors, you'll see a list of these grouped errors. Each group displays the total number of occurrences, affected users, and a trend chart so you can quickly assess its impact.

Click on any error group to see its detailed view:

  • Exceptions: Individual occurrences with timestamps and user details.
  • Users: A list of all affected users.
  • Tags: Metadata and context extracted from the errors.
  • Replays: Session replays linked to the captured errors.

Open an individual exception to see its AI prompt tab with AI-generated explanations and debugging assistance.

See error details

Manage the errors

  • Resolve: Mark errors as fixed once you've addressed them.
  • Ignore: Silence errors you don't need to see, with options to ignore a specific number of future occurrences.
  • Unresolve: Reopen an error if it reoccurs or if you change your mind.
  • Bulk actions: Apply these actions to multiple error groups at once for efficient management.

Bulk error actions

How does it work

Better Stack generates a ._pattern field, which is a hash generated from the characteristics of an error using Vector Remap Language (VRL). Errors with the same ._pattern belong to the same group. For complete details on VRL syntax and capabilities, please refer to the official VRL documentation.

Choose how errors are grouped in ErrorsApplications → Your application → Group & transform. Three presets are available:

  • Group by error name & call stack: The default. Combines the error's call_stack_hash, type, and normalized message.
  • Group by call stack: Uses the call_stack_hash alone, falling back to the error name and normalized message when no call stack is available.
  • Group by error name: Combines the error's type and normalized message.

Group & transform tab of an errors application

The presets normalize the message before grouping. UUIDs, hex strings with 8 or more characters, e-mail addresses, IP addresses, and numbers with 4 or more digits are replaced with *. Thanks to this, errors like these end up in the same group:

  • User '12345' not found.
  • User '67890' not found.

If the error payload contains a fingerprint array, it overrides the calculated grouping. This gives you direct control from your application code. The fingerprint values are joined with :, and any {{ default }} value is replaced with the preset's calculated grouping, so you can extend the default grouping instead of replacing it.

This is the default preset:

Group by error name & call stack
# Exceptions grouping
# -------------------

# Step 1: Normalize the message — strip out IDs and other per-occurrence
#         variable bits so similar errors group together when we have to fall
#         back to message-based grouping (e.g. "User 78901 does not have
#         permission" and "User 15678 does not have permission" end up in the
#         same group).
normalized_message = to_string(.summary.message) ?? ""
normalized_message = replace(normalized_message, r'\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b', "*")
normalized_message = replace(normalized_message, r'\b[0-9a-fA-F]{8,}\b', "*")
normalized_message = replace(normalized_message, r'[\w.+\-]+@[\w.\-]+\.[a-zA-Z]{2,}', "*")
normalized_message = replace(normalized_message, r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', "*")
normalized_message = replace(normalized_message, r'\b\d{4,}\b', "*")

# Step 2: Build the grouping input from call_stack + type + normalized_message together. Any field
#         that's missing is contributed as an empty segment, which still hashes distinctly from a
#         key where that field is present, so groups don't collapse spuriously.
type_part = to_string(.summary.type) ?? ""
call_stack_part = to_string(.summary.call_stack_hash) ?? ""

grouping = call_stack_part + ":" + type_part + ":" + normalized_message

# Step 3: If a custom fingerprint has been supplied it will override the default grouping.
#         The fingerprint can reference the default grouping with {{ default }}.
if is_array(.fingerprint) && length!(.fingerprint) > 0 {
  actual_fingerprint = map_values(array!(.fingerprint)) -> |value| {
      if value == "\{{ default \}}" {
          grouping
      } else {
          value
      }
  }
  grouping = join(actual_fingerprint, ":") ?? ""
}

# Step 4: Output the final grouping/pattern value.
._pattern = slice!(sha2(grouping), 0, 24)

Customize exception grouping

In ErrorsApplications → Your application → Group & transform, you can switch between the presets, or modify the VRL script to change what information is included in the grouping.

For example, add a custom tag such as service_name to differentiate between your services. Insert this before the final ._pattern line:

Add a custom tag to the grouping
if exists(.tags.service_name) && .tags.service_name != null {
  grouping = grouping + ":" + to_string!(.tags.service_name)
}