Skip to content

Provide fingerprint on error object #11133

Closed as not planned
Closed as not planned
@jeengbe

Description

@jeengbe

Problem Statement

It is not possible for Sentry to always group errors correctly, as "correct" is subjective to the developer. If Sentry provides an option to enrich a thrown error object with a fingerprint, the developer is in full control over the fingerprint.

  • Either Sentry groups to aggressively and groups errors that don't belong together -> Different errors (conceptually) that share the same stack trace.
  • Conceptually the same errors that originate from different call stacks.

The current solutions has the following issues:

  • scope.setFingerprint only works if the error is captured in the scope in which it occurs. If, instead, the error is thrown and caught in one central place, all scope information (and thus the fingerprint) is lost.
  • beforeSend adds needless complexity by requiring both copy-pasta setup code for every project, and at least one custom class that must also be copied alongside.

Solution Brainstorm

Imagine the following simplified code snippet:

export async function getThing() {
  const res = await fetch('https://api.example.com/thing');

  if (!res.ok) {
    throw new Error(
      `Failed to fetch the thing: ${res.status} ${res.statusText}.`,
      {
        cause: {
          status: res.status,
          statusText: res.statusText,
          body: await res.text(),
        },
      },
    );
  }

  try {
    return await res.json();
  } catch (err) {
    throw new Error('Failed to fetch the thing: invalid JSON.', {
      cause: {
        body: await res.text(),
      },
    });
  }
}

Any "Failed to fetch the thing: {status} {text}" error would be grouped as one issue by the same stack trace. There are several solutions to this:

  • Sentry.captureException instead of throwing the error. This, however, is not always an option. If there is no logger instance available, there is no way to otherwise log the error, and depending on the project, the option return types this solution implies, don't always work.
  • Attach fingerprint to the error object:
    Either with an error derivate that Sentry provides and attaches fingerprint from a constructor parameter, or alternatively:
throw Object.assign(
  new Error(`Failed to fetch the thing: ${res.status} ${res.statusText}.`, {
    cause: {
      status: res.status,
      statusText: res.statusText,
      body: await res.text(),
    },
  }),
  {
    fingerprint: ['fetch-thing-failed', res.status],
  },
);

Then, the Sentry SDK would check for .fingerprint properties on errors out of the box.

  • Attach the scope to any thrown errors. The initial problem is that any errors thrown within a scope lose any context. This could be fixed with a try-catch within Sentry.withScope that simply attaches itself to any errors it catches, so that any higher-level error handler still has access to the scope in which the error occurred. This would be the best solution in my opinion, as it also retains any other metadata set on the scope, such as tags and data, and makes those available to Sentry, as well as any other error logger, such as >stderr, which could then also benefit from scope data.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions