Compare commits

..

1 Commits

Author SHA1 Message Date
Aditya Singh
876d84ac53 fix(sentry): expected 401, 403 and network failures page the frontend (#13510)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
cacheci / tests (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
#### Description

- API calls that nobody catches reach Sentry as unhandled rejections.
Most of these failures are expected: a 403 for a user without a viewer,
editor or admin role, a 401 for an expired session, or a network error
from a dropped connection.
- Since the axios 1.21.0 bump (#13339), these errors carry our call site
in the stack. Sentry opens a new issue for each call site, and each new
issue pages. SIGNOZ-UI-5N4 paged twice this way.
- `beforeSend` now drops axios errors with status 401 or 403, or with
code `ERR_NETWORK`. Sentry still gets 400, 404 and 5xx.

#### Issues closed by this PR

Closes https://github.com/SigNoz/events-pod/issues/261

#### Additional Information

- The filter matches raw axios errors only. A call that goes through
`ErrorResponseHandlerV2`, `ErrorResponseHandlerForGeneratedAPIs` or
`convertToApiError` rethrows an `APIError`. Sentry still reports that
error when nobody catches it.
- The filter drops all 403 and `ERR_NETWORK` errors. A missing
permission check in the UI, or a CORS or base-path regression, no longer
shows in Sentry. This is a deliberate choice.
- The filter also drops 401 errors from the token refresh retry path.
- A separate PR adds an error state for the empty key suggestions in the
query search bar.
2026-11-11 18:32:35 +00:00
3 changed files with 52 additions and 0 deletions

View File

@@ -1,7 +1,9 @@
import type { ErrorEvent, StackFrame } from '@sentry/react';
import { AxiosError, AxiosHeaders } from 'axios';
import {
hasNoAppFrames,
isExpectedRequestFailure,
isExternalIntegrationError,
SENTRY_IGNORE_ERRORS,
} from '../sentryFilters';
@@ -138,3 +140,36 @@ describe('SENTRY_IGNORE_ERRORS', () => {
).toBe(true);
});
});
describe('isExpectedRequestFailure', () => {
function axiosErrorWithStatus(status: number): AxiosError {
const config = { headers: new AxiosHeaders() };
return new AxiosError(
`Request failed with status code ${status}`,
AxiosError.ERR_BAD_REQUEST,
config,
undefined,
{ status, statusText: '', headers: {}, config, data: {} },
);
}
it.each([401, 403])('drops a %i response', (status) => {
expect(isExpectedRequestFailure(axiosErrorWithStatus(status))).toBe(true);
});
it('drops a network error', () => {
expect(
isExpectedRequestFailure(
new AxiosError('Network Error', AxiosError.ERR_NETWORK),
),
).toBe(true);
});
it.each([400, 404, 500, 503])('keeps a %i response', (status) => {
expect(isExpectedRequestFailure(axiosErrorWithStatus(status))).toBe(false);
});
it('keeps an error that is not from axios', () => {
expect(isExpectedRequestFailure(new Error('Network Error'))).toBe(false);
});
});

View File

@@ -40,6 +40,7 @@ import defaultRoutes, {
} from './routes';
import {
hasNoAppFrames,
isExpectedRequestFailure,
isExternalIntegrationError,
SENTRY_IGNORE_ERRORS,
} from './sentryFilters';
@@ -341,6 +342,9 @@ function App(): JSX.Element {
if (error?.name === 'AbortError') {
return null;
}
if (isExpectedRequestFailure(hint?.originalException)) {
return null;
}
// Ignore benign Monaco cancellation errors (name 'Canceled').
if (error?.name === 'Canceled') {
return null;

View File

@@ -1,4 +1,5 @@
import type { ErrorEvent } from '@sentry/react';
import { AxiosError, isAxiosError } from 'axios';
export const SENTRY_IGNORE_ERRORS = [
// A tab that outlived a deploy requests hashed assets the new build no longer
@@ -14,6 +15,18 @@ export const SENTRY_IGNORE_ERRORS = [
/Called on script loaded before session recording is available/,
];
// Not UI bugs: 401/403 come from the session or role, ERR_NETWORK from a
// dropped connection.
export function isExpectedRequestFailure(error: unknown): boolean {
if (!isAxiosError(error)) {
return false;
}
const status = error.response?.status;
return (
error.code === AxiosError.ERR_NETWORK || status === 401 || status === 403
);
}
const APP_ASSETS_PATH = '/assets/';
// Frames without a position are `[native code]`, which says nothing about the