Skip to content

Commit

Permalink
fix: provide generic exception handling functionality
Browse files Browse the repository at this point in the history
Introduce a log helper with static functions to get exception message /
exception stack whatever is thrown or provided.

Closes: hyperledger#1702
Signed-off-by: Michael Courtin <michael.courtin@accenture.com>
  • Loading branch information
m-courtin authored and Leeyoungone committed Jan 13, 2022
1 parent 81570f5 commit 582e913
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
Bools,
Logger,
LoggerProvider,
LogHelper,
Servers,
} from "@hyperledger/cactus-common";

Expand Down Expand Up @@ -241,7 +242,8 @@ export class ApiServer {

return { addressInfoCockpit, addressInfoApi, addressInfoGrpc };
} catch (ex) {
const errorMessage = `Failed to start ApiServer: ${ex.stack}`;
const stack = LogHelper.getExceptionStack(ex);
const errorMessage = `Failed to start ApiServer: ${stack}`;
this.log.error(errorMessage);
this.log.error(`Attempting shutdown...`);
try {
Expand Down Expand Up @@ -296,9 +298,10 @@ export class ApiServer {
await this.getPluginImportsCount(),
);
return this.pluginRegistry;
} catch (e) {
} catch (ex) {
this.pluginRegistry = new PluginRegistry({ plugins: [] });
const errorMessage = `Failed init PluginRegistry: ${e.stack}`;
const stack = LogHelper.getExceptionStack(ex);
const errorMessage = `Failed init PluginRegistry: ${stack}`;
this.log.error(errorMessage);
throw new Error(errorMessage);
}
Expand Down
59 changes: 59 additions & 0 deletions packages/cactus-common/src/main/typescript/logging/log-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export class LogHelper {
public static getExceptionStack(exception: unknown): string {
// handle unknown exception input
const defaultStack = "NO_STACK_INFORMATION_INCLUDED_IN_EXCEPTION";
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<string, unknown>).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;
}

public static getExceptionMessage(exception: unknown): string {
// handle unknown exception input
const defaultMessage = "NO_MESSAGE_INCLUDED_IN_EXCEPTION";
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<string, unknown>).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;
}
}
1 change: 1 addition & 0 deletions packages/cactus-common/src/main/typescript/public-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { LoggerProvider } from "./logging/logger-provider";
export { LogHelper } from "./logging/log-helper";
export { Logger, ILoggerOptions } from "./logging/logger";
export { LogLevelDesc } from "loglevel";
export { Objects } from "./objects";
Expand Down
Original file line number Diff line number Diff line change
@@ -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_INCLUDED_IN_EXCEPTION";
const no_stack_available = "NO_STACK_INFORMATION_INCLUDED_IN_EXCEPTION";
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 = no_stack_available;
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 NO_STACK_INFORMATION_INCLUDED_IN_EXCEPTION string", () => {
const expectedResult = no_stack_available;
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 NO_STACK_INFORMATION_INCLUDED_IN_EXCEPTION string", () => {
const expectedResult = no_stack_available;
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 and therefore returning NO_MESSAGE_INCLUDED_IN_EXCEPTION", () => {
const expectedResult = no_message_available;
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 returning NO_MESSAGE_INCLUDED_IN_EXCEPTION string", () => {
const expectedResult = no_message_available;
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 returning NO_MESSAGE_INCLUDED_IN_EXCEPTION string", () => {
const expectedResult = no_message_available;
let message = no_message_available;

const fakeError = undefined;

try {
throw fakeError;
} catch (error) {
message = LogHelper.getExceptionMessage(error);
}

// check message
expect(message).toBe(expectedResult);
});
});
});

0 comments on commit 582e913

Please sign in to comment.