Messaging issues (#12041)

messageImportException
for messaging and calendar
add more context to the Sentry Exceptions

Fixes https://github.com/twentyhq/twenty/issues/11994
This commit is contained in:
Guillim
2025-05-14 18:37:42 +02:00
committed by GitHub
parent 628ba18115
commit fdc7d6c93c
7 changed files with 118 additions and 16 deletions

View File

@ -27,6 +27,10 @@ export class ExceptionHandlerSentryDriver
scope.setExtra('workspace', options.workspace);
}
if (options?.additionalData) {
scope.setExtra('additionalData', options.additionalData);
}
if (options?.user) {
scope.setUser({
id: options.user.id,

View File

@ -9,6 +9,7 @@ export interface ExceptionHandlerOptions {
name: string;
};
document?: string;
additionalData?: Record<string, any>;
user?: ExceptionHandlerUser | null;
workspace?: ExceptionHandlerWorkspace | null;
}

View File

@ -85,10 +85,24 @@ export class CalendarEventImportErrorHandlerService {
workspaceId,
);
throw new CalendarEventImportException(
`Unknown temporary error occurred while importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId} with throttleFailureCount${calendarChannel.throttleFailureCount}`,
const calendarEventImportException = new CalendarEventImportException(
`Temporary error occurred ${CALENDAR_THROTTLE_MAX_ATTEMPTS} times while importing calendar events for calendar channel ${calendarChannel.id.slice(0, 5)}... in workspace ${workspaceId} with throttleFailureCount ${calendarChannel.throttleFailureCount}`,
CalendarEventImportExceptionCode.UNKNOWN,
);
this.exceptionHandlerService.captureExceptions(
[calendarEventImportException],
{
additionalData: {
calendarChannelId: calendarChannel.id,
},
workspace: {
id: workspaceId,
},
},
);
throw calendarEventImportException;
}
const calendarChannelRepository =
@ -149,13 +163,16 @@ export class CalendarEventImportErrorHandlerService {
);
const calendarEventImportException = new CalendarEventImportException(
`Unknown error importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId}: ${exception.message}`,
`Unknown error importing calendar events for calendar channel ${calendarChannel.id.slice(0, 5)}... in workspace ${workspaceId}: ${exception.message}`,
CalendarEventImportExceptionCode.UNKNOWN,
);
this.exceptionHandlerService.captureExceptions(
[calendarEventImportException],
{
additionalData: {
calendarChannelId: calendarChannel.id,
},
workspace: {
id: workspaceId,
},

View File

@ -1,8 +1,10 @@
import { Injectable } from '@nestjs/common';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { MicrosoftImportDriverException } from 'src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception';
import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider';
import { MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface';
import { isMicrosoftClientTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-temporary-error.utils';
@Injectable()
export class MicrosoftFetchByBatchService {
@ -42,11 +44,23 @@ export class MicrosoftFetchByBatchService {
},
}));
const batchResponse = await client
.api('/$batch')
.post({ requests: batchRequests });
try {
const batchResponse = await client
.api('/$batch')
.post({ requests: batchRequests });
batchResponses.push(batchResponse);
batchResponses.push(batchResponse);
} catch (error) {
if (
error.body &&
typeof error.body === 'string' &&
isMicrosoftClientTemporaryError(error.body)
) {
throw new MicrosoftImportDriverException(error.body, error.code, 429);
} else {
throw error;
}
}
}
return {
@ -54,4 +68,21 @@ export class MicrosoftFetchByBatchService {
batchResponses,
};
}
/**
* Microsoft client.api.post sometimes throws (hard to catch) temporary errors like this one:
*
* {
* statusCode: 200,
* code: "SyntaxError",
* requestId: null,
* date: "2025-05-14T11:43:02.024Z",
* body: "SyntaxError: Unexpected token < in JSON at position 19341",
* headers: {
* },
* }
*/
private isTemporaryError(error: any): boolean {
return error?.body?.includes('Unexpected token < in JSON at position');
}
}

View File

@ -0,0 +1,29 @@
import { isMicrosoftClientTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-temporary-error.utils';
describe('isMicrosoftClientTemporaryError', () => {
it('should return true when body contains the expected text', () => {
const error = {
statusCode: 200,
code: 'SyntaxError',
requestId: null,
date: '2025-05-14T11:43:02.024Z',
body: 'SyntaxError: Unexpected token < in JSON at position 19341',
headers: {},
};
expect(isMicrosoftClientTemporaryError(error.body)).toBe(true);
});
it('should return false when body does not contain it', () => {
const error = {
statusCode: 400,
code: 'AuthError',
requestId: '123456',
date: '2025-05-14T11:43:02.024Z',
body: 'Authentication failed',
headers: {},
};
expect(isMicrosoftClientTemporaryError(error.body)).toBe(false);
});
});

View File

@ -0,0 +1,3 @@
export const isMicrosoftClientTemporaryError = (body: string): boolean => {
return body.includes('Unexpected token < in JSON at position');
};

View File

@ -55,6 +55,7 @@ export class MessageImportExceptionHandlerService {
syncStep,
messageChannel,
workspaceId,
exception,
);
break;
case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
@ -89,6 +90,7 @@ export class MessageImportExceptionHandlerService {
'id' | 'throttleFailureCount'
>,
workspaceId: string,
exception: MessageImportDriverException,
): Promise<void> {
if (
messageChannel.throttleFailureCount >= MESSAGING_THROTTLE_MAX_ATTEMPTS
@ -98,8 +100,21 @@ export class MessageImportExceptionHandlerService {
workspaceId,
MessageChannelSyncStatus.FAILED_UNKNOWN,
);
this.exceptionHandlerService.captureExceptions(
[
`Temporary error occurred ${MESSAGING_THROTTLE_MAX_ATTEMPTS} times while importing messages for message channel ${messageChannel.id.slice(0, 5)}... in workspace ${workspaceId}: ${exception?.message}`,
],
{
additionalData: {
messageChannelId: messageChannel.id,
},
workspace: { id: workspaceId },
},
);
throw new MessageImportException(
`Unknown temporary error occurred multiple times while importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}`,
`Temporary error occurred multiple times while importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception?.message}`,
MessageImportExceptionCode.UNKNOWN,
);
}
@ -161,17 +176,19 @@ export class MessageImportExceptionHandlerService {
MessageChannelSyncStatus.FAILED_UNKNOWN,
);
this.exceptionHandlerService.captureExceptions([
new MessageImportException(
`Unknown error importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception.message}`,
MessageImportExceptionCode.UNKNOWN,
),
]);
throw new MessageImportException(
const messageImportException = new MessageImportException(
exception.message,
MessageImportExceptionCode.UNKNOWN,
);
this.exceptionHandlerService.captureExceptions([messageImportException], {
additionalData: {
messageChannelId: messageChannel.id,
},
workspace: { id: workspaceId },
});
throw messageImportException;
}
private async handlePermanentException(