From 0eaafcb6b60013f3c56428cadce10c895dd711d6 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 3 Dec 2024 22:51:29 -0300 Subject: [PATCH] make Deno capture unhandled exceptions and rejections and report them to the server https://github.com/RocketChat/Rocket.Chat/pull/33997 --- deno-runtime/error-handlers.ts | 33 +++++++++++++++++++ deno-runtime/main.ts | 3 ++ src/definition/metadata/AppMethod.ts | 2 ++ .../runtime/deno/AppsEngineDenoRuntime.ts | 18 ++++++++++ 4 files changed, 56 insertions(+) create mode 100644 deno-runtime/error-handlers.ts diff --git a/deno-runtime/error-handlers.ts b/deno-runtime/error-handlers.ts new file mode 100644 index 000000000..1e042e0f2 --- /dev/null +++ b/deno-runtime/error-handlers.ts @@ -0,0 +1,33 @@ +import * as Messenger from './lib/messenger.ts'; + +export function unhandledRejectionListener(event: PromiseRejectionEvent) { + event.preventDefault(); + + const { type, reason } = event; + + Messenger.sendNotification({ + method: 'unhandledRejection', + params: [ + { + type, + reason: reason instanceof Error ? reason.message : reason, + timestamp: new Date(), + }, + ], + }); +} + +export function unhandledExceptionListener(event: ErrorEvent) { + event.preventDefault(); + + const { type, message, filename, lineno, colno } = event; + Messenger.sendNotification({ + method: 'uncaughtException', + params: [{ type, message, filename, lineno, colno }], + }); +} + +export default function registerErrorListeners() { + addEventListener('unhandledrejection', unhandledRejectionListener); + addEventListener('error', unhandledExceptionListener); +} diff --git a/deno-runtime/main.ts b/deno-runtime/main.ts index 09be5258e..fa2822908 100644 --- a/deno-runtime/main.ts +++ b/deno-runtime/main.ts @@ -21,6 +21,7 @@ import videoConferenceHandler from './handlers/videoconference-handler.ts'; import apiHandler from './handlers/api-handler.ts'; import handleApp from './handlers/app/handler.ts'; import handleScheduler from './handlers/scheduler-handler.ts'; +import registerErrorListeners from './error-handlers.ts'; type Handlers = { app: typeof handleApp; @@ -126,4 +127,6 @@ async function main() { } } +registerErrorListeners(); + main(); diff --git a/src/definition/metadata/AppMethod.ts b/src/definition/metadata/AppMethod.ts index 8ec07f53e..b7fcf306f 100644 --- a/src/definition/metadata/AppMethod.ts +++ b/src/definition/metadata/AppMethod.ts @@ -101,4 +101,6 @@ export enum AppMethod { EXECUTE_POST_USER_STATUS_CHANGED = 'executePostUserStatusChanged', // Runtime specific methods RUNTIME_RESTART = 'runtime:restart', + RUNTIME_UNCAUGHT_EXCEPTION = 'runtime:uncaughtException', + RUNTIME_UNHANDLED_REJECTION = 'runtime:unhandledRejection', } diff --git a/src/server/runtime/deno/AppsEngineDenoRuntime.ts b/src/server/runtime/deno/AppsEngineDenoRuntime.ts index 00c07aa8a..15a74d29e 100644 --- a/src/server/runtime/deno/AppsEngineDenoRuntime.ts +++ b/src/server/runtime/deno/AppsEngineDenoRuntime.ts @@ -13,6 +13,7 @@ import type { IParseAppPackageResult } from '../../compiler'; import { AppConsole, type ILoggerStorageEntry } from '../../logging'; import type { AppAccessorManager, AppApiManager } from '../../managers'; import { AppStatus } from '../../../definition/AppStatus'; +import type { AppMethod } from '../../../definition/metadata'; import { bundleLegacyApp } from './bundler'; import { ProcessMessenger } from './ProcessMessenger'; import { LivenessManager } from './LivenessManager'; @@ -535,12 +536,29 @@ export class DenoRuntimeSubprocessController extends EventEmitter { case 'log': console.log('SUBPROCESS LOG', message); break; + case 'unhandledRejection': + case 'uncaughtException': + await this.logUnhandledError(`runtime:${method}`, message); + break; + default: console.warn('Unrecognized method from sub process'); break; } } + private async logUnhandledError( + method: `${AppMethod.RUNTIME_UNCAUGHT_EXCEPTION | AppMethod.RUNTIME_UNHANDLED_REJECTION}`, + message: jsonrpc.IParsedObjectRequest | jsonrpc.IParsedObjectNotification, + ) { + this.debug('Unhandled error of type "%s" caught in subprocess', method); + + const logger = new AppConsole(method); + logger.error(message.payload); + + await this.logStorage.storeEntries(AppConsole.toStorageEntry(this.getAppId(), logger)); + } + private async handleResultMessage(message: jsonrpc.IParsedObjectError | jsonrpc.IParsedObjectSuccess): Promise { const { id } = message.payload;