Add call recorder oom diagnosis logs (#22315)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22315?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+114
-1
@@ -106,6 +106,19 @@ const handleRecallStatusEvent = async ({
|
||||
};
|
||||
}
|
||||
|
||||
const shouldLogTerminalDiagnostics = isRecallRecordingDoneSignal({
|
||||
event,
|
||||
statusCode,
|
||||
});
|
||||
|
||||
if (shouldLogTerminalDiagnostics) {
|
||||
logRecallWebhookPhase({
|
||||
phase: 'match-start',
|
||||
webhookEvent,
|
||||
callRecordingStatus,
|
||||
});
|
||||
}
|
||||
|
||||
const callRecording = await findMatchingCallRecording({
|
||||
client,
|
||||
webhookEvent,
|
||||
@@ -144,12 +157,28 @@ const handleRecallStatusEvent = async ({
|
||||
...buildRecordingTimestampsUpdate({ webhookEvent, callRecording }),
|
||||
};
|
||||
|
||||
if (isRecallRecordingDoneSignal({ event, statusCode })) {
|
||||
if (shouldLogTerminalDiagnostics) {
|
||||
logRecallWebhookPhase({
|
||||
phase: 'terminal-start',
|
||||
webhookEvent,
|
||||
callRecording,
|
||||
callRecordingStatus,
|
||||
});
|
||||
|
||||
const externalRecordingIdResolution = await resolveExternalRecordingId({
|
||||
callRecording,
|
||||
webhookEvent,
|
||||
});
|
||||
|
||||
logRecallWebhookPhase({
|
||||
phase: 'recording-id-resolved',
|
||||
webhookEvent,
|
||||
callRecording,
|
||||
externalRecordingId: externalRecordingIdResolution.externalRecordingId,
|
||||
providerLookupFailed: externalRecordingIdResolution.providerLookupFailed,
|
||||
callRecordingStatus,
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
updateData,
|
||||
await buildTranscriptArtifactUpdate({
|
||||
@@ -158,6 +187,15 @@ const handleRecallStatusEvent = async ({
|
||||
}),
|
||||
);
|
||||
|
||||
logRecallWebhookPhase({
|
||||
phase: 'transcript-complete',
|
||||
webhookEvent,
|
||||
callRecording,
|
||||
externalRecordingId: externalRecordingIdResolution.externalRecordingId,
|
||||
updateData,
|
||||
callRecordingStatus,
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
updateData,
|
||||
await buildMediaIngestionUpdate({
|
||||
@@ -186,6 +224,18 @@ const handleRecallStatusEvent = async ({
|
||||
updateData,
|
||||
});
|
||||
|
||||
if (shouldLogTerminalDiagnostics) {
|
||||
logRecallWebhookPhase({
|
||||
phase: 'terminal-complete',
|
||||
webhookEvent,
|
||||
callRecording,
|
||||
updateData,
|
||||
callRecordingStatus: completesIngestion
|
||||
? CallRecordingStatus.COMPLETED
|
||||
: (updateData.status ?? callRecordingStatus),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'updated',
|
||||
event,
|
||||
@@ -196,6 +246,69 @@ const handleRecallStatusEvent = async ({
|
||||
};
|
||||
};
|
||||
|
||||
const logRecallWebhookPhase = ({
|
||||
phase,
|
||||
webhookEvent,
|
||||
callRecording,
|
||||
callRecordingStatus,
|
||||
externalRecordingId,
|
||||
providerLookupFailed,
|
||||
updateData,
|
||||
}: {
|
||||
phase: string;
|
||||
webhookEvent: RecallWebhookEvent;
|
||||
callRecording?: MatchedCallRecording;
|
||||
callRecordingStatus?: string;
|
||||
externalRecordingId?: string;
|
||||
providerLookupFailed?: boolean;
|
||||
updateData?: CallRecordingUpdateFields;
|
||||
}) => {
|
||||
console.log(
|
||||
[
|
||||
`[call-recorder] recall-webhook phase=${phase}`,
|
||||
`event=${webhookEvent.event}`,
|
||||
`statusCode=${webhookEvent.statusCode ?? 'n/a'}`,
|
||||
`callRecordingId=${callRecording?.id ?? webhookEvent.callRecordingIdFromMetadata ?? 'n/a'}`,
|
||||
`externalBotId=${webhookEvent.externalBotId ?? 'n/a'}`,
|
||||
`externalRecordingId=${externalRecordingId ?? webhookEvent.externalRecordingId ?? callRecording?.externalRecordingId ?? 'n/a'}`,
|
||||
`callRecordingStatus=${callRecordingStatus ?? 'n/a'}`,
|
||||
`currentStatus=${callRecording?.status ?? 'n/a'}`,
|
||||
`hasTranscript=${hasReachableTranscript(callRecording?.transcript)}`,
|
||||
`hasAudio=${isNonEmptyArray(callRecording?.audio)}`,
|
||||
`hasVideo=${isNonEmptyArray(callRecording?.video)}`,
|
||||
`updates=${formatUpdateDataKeys(updateData)}`,
|
||||
`providerLookupFailed=${providerLookupFailed ?? false}`,
|
||||
formatMemoryUsageForLog(),
|
||||
].join(' '),
|
||||
);
|
||||
};
|
||||
|
||||
const formatUpdateDataKeys = (
|
||||
updateData: CallRecordingUpdateFields | undefined,
|
||||
): string => {
|
||||
if (isUndefined(updateData)) {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
const updateDataKeys = Object.keys(updateData);
|
||||
|
||||
return updateDataKeys.length === 0 ? 'none' : updateDataKeys.join(',');
|
||||
};
|
||||
|
||||
const formatMemoryUsageForLog = (): string => {
|
||||
const memoryUsage = process.memoryUsage();
|
||||
|
||||
return [
|
||||
`rssMegaBytes=${formatBytesAsMegaBytes(memoryUsage.rss)}`,
|
||||
`heapUsedMegaBytes=${formatBytesAsMegaBytes(memoryUsage.heapUsed)}`,
|
||||
`externalMegaBytes=${formatBytesAsMegaBytes(memoryUsage.external)}`,
|
||||
`arrayBuffersMegaBytes=${formatBytesAsMegaBytes(memoryUsage.arrayBuffers)}`,
|
||||
].join(' ');
|
||||
};
|
||||
|
||||
const formatBytesAsMegaBytes = (bytes: number): string =>
|
||||
(bytes / 1024 / 1024).toFixed(1);
|
||||
|
||||
const findMatchingCallRecording = async ({
|
||||
client,
|
||||
webhookEvent,
|
||||
|
||||
+45
-7
@@ -91,7 +91,16 @@ const ingestMediaArtifact = async ({
|
||||
fieldMetadataUniversalIdentifier: string;
|
||||
}): Promise<CallRecordingMediaFile[] | undefined> => {
|
||||
try {
|
||||
const { buffer, contentType } = await downloadMediaFile(url);
|
||||
const { buffer, contentType } = await downloadMediaFile({
|
||||
callRecordingId,
|
||||
fileName,
|
||||
url,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[call-recorder] media-ingestion phase=artifact-upload-start callRecordingId=${callRecordingId} fileName=${fileName} downloadedBytes=${buffer.byteLength} contentType=${contentType} ${formatMemoryUsageForLog()}`,
|
||||
);
|
||||
|
||||
const uploadedFile = await metadataClient.uploadFile(
|
||||
buffer,
|
||||
fileName,
|
||||
@@ -109,20 +118,49 @@ const ingestMediaArtifact = async ({
|
||||
}
|
||||
};
|
||||
|
||||
const downloadMediaFile = async (
|
||||
url: string,
|
||||
): Promise<{ buffer: Buffer; contentType: string }> => {
|
||||
const downloadMediaFile = async ({
|
||||
callRecordingId,
|
||||
fileName,
|
||||
url,
|
||||
}: {
|
||||
callRecordingId: string;
|
||||
fileName: string;
|
||||
url: string;
|
||||
}): Promise<{ buffer: Buffer; contentType: string }> => {
|
||||
const response = await fetch(url, {
|
||||
signal: AbortSignal.timeout(MEDIA_DOWNLOAD_TIMEOUT_MS),
|
||||
});
|
||||
const contentType =
|
||||
response.headers.get('content-type') ?? 'application/octet-stream';
|
||||
const contentLength = response.headers.get('content-length') ?? 'unknown';
|
||||
|
||||
console.log(
|
||||
`[call-recorder] media-ingestion phase=artifact-download-response callRecordingId=${callRecordingId} fileName=${fileName} responseStatus=${response.status} contentLengthBytes=${contentLength} contentType=${contentType} ${formatMemoryUsageForLog()}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`download failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
return {
|
||||
buffer: Buffer.from(await response.arrayBuffer()),
|
||||
contentType:
|
||||
response.headers.get('content-type') ?? 'application/octet-stream',
|
||||
buffer,
|
||||
contentType,
|
||||
};
|
||||
};
|
||||
|
||||
const formatMemoryUsageForLog = (): string => {
|
||||
const memoryUsage = process.memoryUsage();
|
||||
|
||||
return [
|
||||
`rssMegaBytes=${formatBytesAsMegaBytes(memoryUsage.rss)}`,
|
||||
`heapUsedMegaBytes=${formatBytesAsMegaBytes(memoryUsage.heapUsed)}`,
|
||||
`externalMegaBytes=${formatBytesAsMegaBytes(memoryUsage.external)}`,
|
||||
`arrayBuffersMegaBytes=${formatBytesAsMegaBytes(memoryUsage.arrayBuffers)}`,
|
||||
].join(' ');
|
||||
};
|
||||
|
||||
const formatBytesAsMegaBytes = (bytes: number): string =>
|
||||
(bytes / 1024 / 1024).toFixed(1);
|
||||
|
||||
Reference in New Issue
Block a user