Remove Sentry fingerprint (#11602)

As discussed this @ijreilly, Fingerprinting is probably not needed and
Sentry will do a better job by itself
This commit is contained in:
Félix Malfait
2025-04-16 16:25:40 +02:00
committed by GitHub
parent c95a84c8e5
commit ab277476a8
4 changed files with 75 additions and 76 deletions

View File

@ -50,12 +50,6 @@ export class ExceptionHandlerSentryDriver
} }
const eventId = Sentry.captureException(exception, { const eventId = Sentry.captureException(exception, {
fingerprint: [
'graphql',
errorPath,
options?.operation?.name,
options?.operation?.type,
],
contexts: { contexts: {
GraphQL: { GraphQL: {
operationName: options?.operation?.name, operationName: options?.operation?.name,

View File

@ -13,6 +13,11 @@ import { generateGraphQLErrorFromError } from 'src/engine/core-modules/graphql/u
import { BaseGraphQLError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { BaseGraphQLError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { shouldCaptureException } from 'src/engine/core-modules/graphql/utils/should-capture-exception.util'; import { shouldCaptureException } from 'src/engine/core-modules/graphql/utils/should-capture-exception.util';
const DEFAULT_EVENT_ID_KEY = 'exceptionEventId';
const SCHEMA_VERSION_HEADER = 'x-schema-version';
const SCHEMA_MISMATCH_ERROR =
'Your workspace has been updated with a new data model. Please refresh the page.';
type GraphQLErrorHandlerHookOptions = { type GraphQLErrorHandlerHookOptions = {
/** /**
* The exception handler service to use. * The exception handler service to use.
@ -30,17 +35,19 @@ export const useGraphQLErrorHandlerHook = <
>( >(
options: GraphQLErrorHandlerHookOptions, options: GraphQLErrorHandlerHookOptions,
): Plugin<PluginContext> => { ): Plugin<PluginContext> => {
const eventIdKey = options.eventIdKey === null ? null : 'exceptionEventId'; const eventIdKey = options.eventIdKey === null ? null : DEFAULT_EVENT_ID_KEY;
function addEventId( function extractWorkspaceInfo(req: GraphQLContext['req']) {
err: GraphQLError, if (!req.workspace) {
eventId: string | undefined | null, return null;
): GraphQLError {
if (eventIdKey !== null && eventId) {
err.extensions[eventIdKey] = eventId;
} }
return err; return {
id: req.workspace.id,
displayName: req.workspace.displayName,
createdAt: req.workspace.createdAt?.toISOString() ?? null,
activationStatus: req.workspace.activationStatus,
};
} }
return { return {
@ -49,6 +56,11 @@ export const useGraphQLErrorHandlerHook = <
const rootOperation = args.document.definitions.find( const rootOperation = args.document.definitions.find(
(o) => o.kind === Kind.OPERATION_DEFINITION, (o) => o.kind === Kind.OPERATION_DEFINITION,
) as OperationDefinitionNode; ) as OperationDefinitionNode;
if (!rootOperation) {
return;
}
const operationType = rootOperation.operation; const operationType = rootOperation.operation;
const user = args.contextValue.req.user; const user = args.contextValue.req.user;
const document = getDocumentString(args.document, print); const document = getDocumentString(args.document, print);
@ -56,6 +68,7 @@ export const useGraphQLErrorHandlerHook = <
args.operationName || args.operationName ||
rootOperation.name?.value || rootOperation.name?.value ||
'Anonymous Operation'; 'Anonymous Operation';
const workspaceInfo = extractWorkspaceInfo(args.contextValue.req);
return { return {
onExecuteDone(payload) { onExecuteDone(payload) {
@ -63,81 +76,81 @@ export const useGraphQLErrorHandlerHook = <
result, result,
setResult, setResult,
}) => { }) => {
if (result.errors && result.errors.length > 0) { if (!result.errors || result.errors.length === 0) {
const originalErrors = result.errors.map((error) => { return;
const originalError = error.originalError; }
return originalError instanceof BaseGraphQLError // Step 1: Flatten errors - extract original errors when available
? error.originalError const originalErrors = result.errors.map((error) => {
: generateGraphQLErrorFromError(error); return error.originalError || error;
}); });
const errorsToCapture = originalErrors.reduce<BaseGraphQLError[]>( // Step 2: Send errors to monitoring service (with stack traces)
(acc, error) => { const errorsToCapture = originalErrors.filter(
if (shouldCaptureException(error)) { shouldCaptureException,
acc.push(error); );
}
return acc; if (errorsToCapture.length > 0) {
}, const eventIds = exceptionHandlerService.captureExceptions(
[], errorsToCapture,
); {
operation: {
if (errorsToCapture.length > 0) { name: opName,
const eventIds = exceptionHandlerService.captureExceptions( type: operationType,
errorsToCapture,
{
operation: {
name: opName,
type: operationType,
},
document,
user,
workspace: {
id: args.contextValue.req.workspace?.id,
displayName: args.contextValue.req.workspace?.displayName,
createdAt:
args.contextValue.req.workspace?.createdAt.toISOString(),
activationStatus:
args.contextValue.req.workspace?.activationStatus,
},
}, },
); document,
user,
errorsToCapture.map((err, i) => addEventId(err, eventIds?.[i])); workspace: workspaceInfo,
} },
const nonCapturedErrors = originalErrors.filter(
(error) => !errorsToCapture.includes(error),
); );
setResult({ errorsToCapture.forEach((_, i) => {
...result, if (eventIds?.[i] && eventIdKey !== null) {
errors: [...nonCapturedErrors, ...errorsToCapture], originalErrors[
originalErrors.indexOf(errorsToCapture[i])
].eventId = eventIds[i];
}
}); });
} }
// Step 3: Transform errors for GraphQL response (clean GraphQL errors)
const transformedErrors = originalErrors.map((error) => {
const graphqlError =
error instanceof BaseGraphQLError
? error
: generateGraphQLErrorFromError(error);
if (error.eventId && eventIdKey) {
graphqlError.extensions[eventIdKey] = error.eventId;
}
return graphqlError;
});
setResult({
...result,
errors: transformedErrors,
});
}; };
return handleStreamOrSingleExecutionResult(payload, handleResult); return handleStreamOrSingleExecutionResult(payload, handleResult);
}, },
}; };
}, },
onValidate: ({ context, validateFn, params: { documentAST, schema } }) => { onValidate: ({ context, validateFn, params: { documentAST, schema } }) => {
const errors = validateFn(schema, documentAST); const errors = validateFn(schema, documentAST);
if (Array.isArray(errors) && errors.length > 0) { if (Array.isArray(errors) && errors.length > 0) {
const headers = context.req.headers; const headers = context.req.headers;
const currentMetadataVersion = context.req.workspaceMetadataVersion; const currentMetadataVersion = context.req.workspaceMetadataVersion;
const requestMetadataVersion = headers[SCHEMA_VERSION_HEADER];
const requestMetadataVersion = headers['x-schema-version'];
if ( if (
requestMetadataVersion && requestMetadataVersion &&
requestMetadataVersion !== `${currentMetadataVersion}` requestMetadataVersion !== `${currentMetadataVersion}`
) { ) {
throw new GraphQLError( throw new GraphQLError(SCHEMA_MISMATCH_ERROR);
`Schema version mismatch, please refresh the page.`,
);
} }
} }
}, },

View File

@ -1,5 +1,3 @@
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
import { import {
BaseGraphQLError, BaseGraphQLError,
ErrorCode, ErrorCode,
@ -11,10 +9,5 @@ export const generateGraphQLErrorFromError = (error: Error) => {
ErrorCode.INTERNAL_SERVER_ERROR, ErrorCode.INTERNAL_SERVER_ERROR,
); );
if (process.env.NODE_ENV === NodeEnvironment.development) {
graphqlError.stack = error.stack;
graphqlError.extensions['response'] = error.message;
}
return graphqlError; return graphqlError;
}; };

View File

@ -15,11 +15,10 @@ export const graphQLErrorCodesToFilterOut = [
]; ];
export const shouldCaptureException = (exception: Error): boolean => { export const shouldCaptureException = (exception: Error): boolean => {
if (!(exception instanceof BaseGraphQLError)) { if (
return true; exception instanceof BaseGraphQLError &&
} graphQLErrorCodesToFilterOut.includes(exception?.extensions?.code)
) {
if (graphQLErrorCodesToFilterOut.includes(exception?.extensions?.code)) {
return false; return false;
} }