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); scope.setExtra('workspace', options.workspace);
} }
if (options?.additionalData) {
scope.setExtra('additionalData', options.additionalData);
}
if (options?.user) { if (options?.user) {
scope.setUser({ scope.setUser({
id: options.user.id, id: options.user.id,

View File

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

View File

@ -85,10 +85,24 @@ export class CalendarEventImportErrorHandlerService {
workspaceId, workspaceId,
); );
throw new CalendarEventImportException( const calendarEventImportException = new CalendarEventImportException(
`Unknown temporary error occurred while importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId} with throttleFailureCount${calendarChannel.throttleFailureCount}`, `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, CalendarEventImportExceptionCode.UNKNOWN,
); );
this.exceptionHandlerService.captureExceptions(
[calendarEventImportException],
{
additionalData: {
calendarChannelId: calendarChannel.id,
},
workspace: {
id: workspaceId,
},
},
);
throw calendarEventImportException;
} }
const calendarChannelRepository = const calendarChannelRepository =
@ -149,13 +163,16 @@ export class CalendarEventImportErrorHandlerService {
); );
const calendarEventImportException = new CalendarEventImportException( 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, CalendarEventImportExceptionCode.UNKNOWN,
); );
this.exceptionHandlerService.captureExceptions( this.exceptionHandlerService.captureExceptions(
[calendarEventImportException], [calendarEventImportException],
{ {
additionalData: {
calendarChannelId: calendarChannel.id,
},
workspace: { workspace: {
id: workspaceId, id: workspaceId,
}, },

View File

@ -1,8 +1,10 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; 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 { 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 { 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() @Injectable()
export class MicrosoftFetchByBatchService { export class MicrosoftFetchByBatchService {
@ -42,11 +44,23 @@ export class MicrosoftFetchByBatchService {
}, },
})); }));
const batchResponse = await client try {
.api('/$batch') const batchResponse = await client
.post({ requests: batchRequests }); .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 { return {
@ -54,4 +68,21 @@ export class MicrosoftFetchByBatchService {
batchResponses, 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, syncStep,
messageChannel, messageChannel,
workspaceId, workspaceId,
exception,
); );
break; break;
case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS: case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
@ -89,6 +90,7 @@ export class MessageImportExceptionHandlerService {
'id' | 'throttleFailureCount' 'id' | 'throttleFailureCount'
>, >,
workspaceId: string, workspaceId: string,
exception: MessageImportDriverException,
): Promise<void> { ): Promise<void> {
if ( if (
messageChannel.throttleFailureCount >= MESSAGING_THROTTLE_MAX_ATTEMPTS messageChannel.throttleFailureCount >= MESSAGING_THROTTLE_MAX_ATTEMPTS
@ -98,8 +100,21 @@ export class MessageImportExceptionHandlerService {
workspaceId, workspaceId,
MessageChannelSyncStatus.FAILED_UNKNOWN, 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( 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, MessageImportExceptionCode.UNKNOWN,
); );
} }
@ -161,17 +176,19 @@ export class MessageImportExceptionHandlerService {
MessageChannelSyncStatus.FAILED_UNKNOWN, MessageChannelSyncStatus.FAILED_UNKNOWN,
); );
this.exceptionHandlerService.captureExceptions([ const messageImportException = new MessageImportException(
new MessageImportException(
`Unknown error importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception.message}`,
MessageImportExceptionCode.UNKNOWN,
),
]);
throw new MessageImportException(
exception.message, exception.message,
MessageImportExceptionCode.UNKNOWN, MessageImportExceptionCode.UNKNOWN,
); );
this.exceptionHandlerService.captureExceptions([messageImportException], {
additionalData: {
messageChannelId: messageChannel.id,
},
workspace: { id: workspaceId },
});
throw messageImportException;
} }
private async handlePermanentException( private async handlePermanentException(