From 982ead295905ba272c2154ed6ac9117e12bf298c Mon Sep 17 00:00:00 2001 From: Michael Courtin Date: Thu, 30 Dec 2021 16:35:58 +0100 Subject: [PATCH] fix: provide generic exception handling functionality Introduce a log helper with static functions to get exception message / exception stack whatever is thrown or provided. Closes: #1702 Signed-off-by: Michael Courtin --- .../src/main/typescript/logging/log-helper.ts | 59 +++++ .../unit/logging/log-helper.test.ts | 220 ++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 packages/cactus-common/src/main/typescript/logging/log-helper.ts create mode 100644 packages/cactus-common/src/test/typescript/unit/logging/log-helper.test.ts diff --git a/packages/cactus-common/src/main/typescript/logging/log-helper.ts b/packages/cactus-common/src/main/typescript/logging/log-helper.ts new file mode 100644 index 00000000000..b64c018c2a6 --- /dev/null +++ b/packages/cactus-common/src/main/typescript/logging/log-helper.ts @@ -0,0 +1,59 @@ +export class LogHelper { + static getExceptionStack(exception: unknown): string { + // handle unknown exception input + const defaultStack = ""; + const invalidStack = "INVALID_STACK_INFORMATION"; + let stack = defaultStack; + + // 1st need to check that exception is not null or undefined before trying to access the wanted stack information + if (exception && typeof exception === "object") { + // 2nd need to check if a stack property is available + if (Object.hasOwnProperty.call(exception, "stack")) { + // 3rd check if the stack property is already of type string + if (typeof (exception as Record).stack === "string") { + stack = (exception as { stack: string }).stack; + } else { + // need to stringify stack information first + try { + stack = JSON.stringify((exception as { stack: unknown }).stack); + } catch (error) { + // stringify failed -> maybe due to cyclic dependency stack etc. + stack = invalidStack; + } + } + } + } + return stack; + } + + static getExceptionMessage(exception: unknown): string { + // handle unknown exception input + const defaultMessage = ""; + const invalidMessage = "INVALID_EXCEPTION_MESSAGE"; + let message = defaultMessage; + + // 1st need to check that exception is not null or undefined before trying to access the wanted message information + if (exception && typeof exception === "object") { + // 2nd need to check if a message property is available + if (Object.hasOwnProperty.call(exception, "message")) { + // 3rd check if the message property is already of type string + if ( + typeof (exception as Record).message === "string" + ) { + message = (exception as { message: string }).message; + } else { + // need to stringify message information first + try { + message = JSON.stringify( + (exception as { message: unknown }).message, + ); + } catch (error) { + // stringify failed -> maybe due to invalid message content etc. + message = invalidMessage; + } + } + } + } + return message; + } +} diff --git a/packages/cactus-common/src/test/typescript/unit/logging/log-helper.test.ts b/packages/cactus-common/src/test/typescript/unit/logging/log-helper.test.ts new file mode 100644 index 00000000000..a28e4be7115 --- /dev/null +++ b/packages/cactus-common/src/test/typescript/unit/logging/log-helper.test.ts @@ -0,0 +1,220 @@ +import "jest-extended"; +import { LogHelper } from "../../../../main/typescript/logging/log-helper"; + +describe("log-helper tests", () => { + const no_message_available = "NO_MESSAGE_AVAILABLE"; + const no_stack_available = "NO_STACK_AVAILABLE"; + const errorMessage = "Oops"; + + describe("exception stack-tests", () => { + it("gets the stack information from a regular Error object", () => { + let expectedResult: string | undefined = ""; + let stack = no_stack_available; + + try { + const testError = new Error(errorMessage); + expectedResult = testError.stack; + throw testError; + } catch (error) { + stack = LogHelper.getExceptionStack(error); + } + + // check stack + expect(stack).toBe(expectedResult); + }); + + it("gets stack information from a faked Error object which is containing stack information as string type", () => { + const expectedResult = "Faked stack"; + let stack = no_stack_available; + + const fakeErrorWithStack = { + message: + "This is a fake error object with string-type stack information", + stack: expectedResult, + }; + + try { + throw fakeErrorWithStack; + } catch (error) { + stack = LogHelper.getExceptionStack(error); + } + + // check stack + expect(stack).toBe(expectedResult); + }); + + it("gets stack information from a faked Error object which is containing stack information as number type and therefore need to be stringified", () => { + const expectedResult = "123456"; + let stack = no_stack_available; + + const fakeErrorWithStack = { + message: + "This is a fake error object with number-type stack information", + stack: 123456, + }; + + try { + throw fakeErrorWithStack; + } catch (error) { + stack = LogHelper.getExceptionStack(error); + } + + // check stack + expect(stack).toBe(expectedResult); + }); + + it("gets no stack information as the faked Error object is not containing any stack information", () => { + const expectedResult = ""; + let stack = no_stack_available; + + const fakeErrorWithoutStack = { + message: "This is a fake error object without stack information", + }; + + try { + throw fakeErrorWithoutStack; + } catch (error) { + stack = LogHelper.getExceptionStack(error); + } + + // check stack + expect(stack).toBe(expectedResult); + }); + + it("handles throwing null successfully and returns empty string stack", () => { + const expectedResult = ""; + let stack = no_stack_available; + + const fakeError = null; + + try { + throw fakeError; + } catch (error) { + stack = LogHelper.getExceptionStack(error); + } + + // check stack + expect(stack).toBe(expectedResult); + }); + + it("handles throwing undefined successfully and returns empty string stack", () => { + const expectedResult = ""; + let stack = no_stack_available; + + const fakeError = undefined; + + try { + throw fakeError; + } catch (error) { + stack = LogHelper.getExceptionStack(error); + } + + // check stack + expect(stack).toBe(expectedResult); + }); + }); + + describe("exception message-tests", () => { + it("gets the exception message from a regular Error object", () => { + const expectedResult = errorMessage; + let message = no_message_available; + + try { + const testError = new Error(errorMessage); + throw testError; + } catch (error) { + message = LogHelper.getExceptionMessage(error); + } + + // check message + expect(message).toBe(expectedResult); + }); + + it("gets the exception message from a faked Error object which is containing message as string type", () => { + const expectedResult = errorMessage; + let message = no_message_available; + + const fakeErrorWithMessage = { + message: errorMessage, + stack: expectedResult, + }; + + try { + throw fakeErrorWithMessage; + } catch (error) { + message = LogHelper.getExceptionMessage(error); + } + + // check message + expect(message).toBe(expectedResult); + }); + + it("gets exception message from a faked Error object which is containing message information as number type and therefore need to be stringified", () => { + const expectedResult = "123456"; + let message = no_message_available; + + const fakeErrorWithNumberMessage = { + message: 123456, + }; + + try { + throw fakeErrorWithNumberMessage; + } catch (error) { + message = LogHelper.getExceptionMessage(error); + } + + // check message + expect(message).toBe(expectedResult); + }); + + it("gets no exception message information as the faked Error object is not containing any message information", () => { + const expectedResult = ""; + let message = no_message_available; + + const fakeErrorWithoutMessage = { + stack: "This is a fake error object without message information", + }; + + try { + throw fakeErrorWithoutMessage; + } catch (error) { + message = LogHelper.getExceptionMessage(error); + } + + // check message + expect(message).toBe(expectedResult); + }); + + it("handles throwing null successfully and returns empty string message", () => { + const expectedResult = ""; + let message = no_message_available; + + const fakeError = null; + + try { + throw fakeError; + } catch (error) { + message = LogHelper.getExceptionMessage(error); + } + + // check message + expect(message).toBe(expectedResult); + }); + + it("handles throwing undefined successfully and returns empty string message", () => { + const expectedResult = ""; + let message = no_message_available; + + const fakeError = undefined; + + try { + throw fakeError; + } catch (error) { + message = LogHelper.getExceptionMessage(error); + } + + // check message + expect(message).toBe(expectedResult); + }); + }); +});