From 417945d4bbb6c6710fe5bfcedc0ae88f01bbf5e7 Mon Sep 17 00:00:00 2001 From: Kelvin Schoofs Date: Sat, 31 Jul 2021 19:53:44 +0200 Subject: [PATCH] Improve logging of errors through promises --- src/logging.ts | 16 ++++++++++++++-- src/utils.ts | 36 +++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index bc3e6f8..416907f 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -44,6 +44,10 @@ export interface LoggingOptions { export const LOGGING_NO_STACKTRACE: Partial = { callStacktrace: 0 }; export const LOGGING_SINGLE_LINE_STACKTRACE: Partial = { callStacktrace: 1 }; +function hasPromiseCause(error: Error): error is Error & { promiseCause: string } { + return typeof (error as any).promiseCause === 'string'; +} + export type LoggerDefaultLevels = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR'; class Logger { protected parent?: Logger; @@ -95,16 +99,24 @@ class Logger { if (message instanceof Error && message.stack) { let msg = message.message; try { - msg += `\nJSON: ${JSON.stringify(message)}`; + const json = JSON.stringify(message); + if (json !== '{}') msg += `\nJSON: ${json}`; } finally { } const { maxErrorStack } = options; if (message.stack && maxErrorStack) { let { stack } = message; if (maxErrorStack > 0) { - stack = stack.split(/\n/g).slice(0, maxErrorStack).join('\n'); + stack = stack.split(/\n/g).slice(0, maxErrorStack + 1).join('\n'); } msg += '\n' + stack; } + if (hasPromiseCause(message) && maxErrorStack) { + let { promiseCause } = message; + if (maxErrorStack > 0) { + promiseCause = promiseCause.split(/\n/g).slice(1, maxErrorStack + 1).join('\n'); + } + msg += '\nCaused by promise:\n' + promiseCause; + } message = msg; } // Do we need to also output a stacktrace? diff --git a/src/utils.ts b/src/utils.ts index d4b8657..1f543ac 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,20 +1,9 @@ import type { EnvironmentVariable } from "./fileSystemConfig"; - -export type toPromiseCallback = (err?: Error | null | void, res?: T) => void; -/** Wrapper around async callback-based functions */ -export async function toPromise(func: (cb: toPromiseCallback) => void): Promise { - return new Promise((resolve, reject) => { - try { - func((err, res) => err ? reject(err) : resolve(res!)); - } catch (e) { - reject(e); - } - }); -} - /** Wrapper around async callback-based functions */ export async function catchingPromise(executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => any): Promise { + const promiseCause = new Error(); + Error.captureStackTrace(promiseCause, catchingPromise); return new Promise((resolve, reject) => { try { const p = executor(resolve, reject); @@ -24,6 +13,27 @@ export async function catchingPromise(executor: (resolve: (value?: T | Promis } catch (e) { reject(e); } + }).catch(e => { + if (e instanceof Error) { + let t = (e as any).promiseCause; + if (!(t instanceof Error)) t = e; + if (!('promiseCause' in t)) { + Object.defineProperty(e, 'promiseCause', { + value: promiseCause.stack, + configurable: true, + enumerable: false, + }); + } + } + throw e; + }); +} + +export type toPromiseCallback = (err?: Error | null | void, res?: T) => void; +/** Wrapper around async callback-based functions */ +export async function toPromise(func: (cb: toPromiseCallback) => void): Promise { + return catchingPromise((resolve, reject) => { + func((err, res) => err ? reject(err) : resolve(res!)); }); }