Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14x 14x 14x 14x 14x 14x 14x 14x 14x 14x 14x 1x 1x 1x 1x 14x 14x 14x 14x 14x 14x 1x 1x 1x 14x 14x 1x 1x 1x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 1x 1x 65x 65x 65x 65x 65x 65x 140x 4x 4x 140x 61x 61x 1x 1x 1x 1x 14x 14x 1x 1x 13x 13x 4x 4x 9x 9x 9x | import { logger } from '@practica/logger';
import * as Http from 'http';
import { AppError } from './app-error';
let httpServerRef: Http.Server;
export const errorHandler = {
// Listen to the global process-level error events
listenToErrorEvents: (httpServer: Http.Server) => {
httpServerRef = httpServer;
process.on('uncaughtException', async (error) => {
await errorHandler.handleError(error);
});
process.on('unhandledRejection', async (reason) => {
await errorHandler.handleError(reason);
});
process.on('SIGTERM', async () => {
logger.error(
'App received SIGTERM event, try to gracefully close the server'
);
await terminateHttpServerAndExit();
});
process.on('SIGINT', async () => {
logger.error(
'App received SIGINT event, try to gracefully close the server'
);
await terminateHttpServerAndExit();
});
},
handleError: (errorToHandle: unknown): number => {
try {
logger.info('Handling error1');
const appError: AppError = covertUnknownToAppError(errorToHandle);
logger.error(appError.message, appError);
metricsExporter.fireMetric('error', { errorName: appError.name }); // fire any custom metric when handling error
// A common best practice is to crash when an unknown error (catastrophic) is being thrown
if (appError.isCatastrophic) {
terminateHttpServerAndExit();
}
return appError.HTTPStatus;
} catch (handlingError: unknown) {
// Not using the logger here because it might have failed
process.stdout.write(
'The error handler failed, here are the handler failure and then the origin error that it tried to handle'
);
process.stdout.write(JSON.stringify(handlingError));
process.stdout.write(JSON.stringify(errorToHandle));
return 500;
}
},
};
const terminateHttpServerAndExit = async () => {
// TODO: implement more complex logic here (like using 'http-terminator' library)
if (httpServerRef) {
await httpServerRef.close();
}
process.exit();
};
// Responsible to get all sort of crazy error objects including none error objects and
// return the best standard AppError object
export function covertUnknownToAppError(errorToHandle: unknown): AppError {
if (errorToHandle instanceof AppError) {
// This means the error was thrown by our code and contains all the necessary information
return errorToHandle;
}
const errorToEnrich: object = getObjectIfNotAlreadyObject(errorToHandle);
const message = getOneOfTheseProperties(
errorToEnrich,
['message', 'reason', 'description'],
'Unknown error'
);
const name = getOneOfTheseProperties(
errorToEnrich,
['name', 'code'],
'unknown-error'
);
const httpStatus = getOneOfTheseProperties(
errorToEnrich,
['HTTPStatus', 'statusCode', 'status'],
500
);
const isCatastrophic = getOneOfTheseProperties<boolean>(
errorToEnrich,
['isCatastrophic', 'catastrophic'],
true
);
const stackTrace = getOneOfTheseProperties<string | undefined>(
errorToEnrich,
['stack'],
undefined
);
const standardError = new AppError(name, message, httpStatus, isCatastrophic);
standardError.stack = stackTrace;
const standardErrorWithOriginProperties = Object.assign(
standardError,
errorToEnrich
);
return standardErrorWithOriginProperties;
}
const getOneOfTheseProperties = <ReturnType>(
object: object,
possibleExistingProperties: string[],
defaultValue: ReturnType
): ReturnType => {
// eslint-disable-next-line no-restricted-syntax
for (const property of possibleExistingProperties) {
if (property in object) {
return object[property];
}
}
return defaultValue;
};
// This simulates a typical monitoring solution that allow firing custom metrics when
// like Prometheus, DataDog, CloudWatch, etc
const metricsExporter = {
fireMetric: async (name: string, labels: object) => {
// 'In real production code I will really fire metrics'
logger.info(`Firing metric ${name} with labels ${JSON.stringify(labels)}`);
},
};
function getObjectIfNotAlreadyObject(target: unknown): object {
if (typeof target === 'object' && target !== null) {
return target;
}
return {};
}
|