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:
@ -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,
|
||||||
|
|||||||
@ -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.`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user