Refactor requestErrorHandler, adjust request errors to appropriate pino log level

This commit is contained in:
Tuan Dang
2023-11-01 10:10:39 +02:00
parent ad4513f926
commit 3b0bd362c9
8 changed files with 175 additions and 118 deletions
+95 -23
View File
@@ -15,7 +15,7 @@
"@godaddy/terminus": "^4.12.0", "@godaddy/terminus": "^4.12.0",
"@node-saml/passport-saml": "^4.0.4", "@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
"@sentry/node": "^7.49.0", "@sentry/node": "^7.77.0",
"@sentry/tracing": "^7.48.0", "@sentry/tracing": "^7.48.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10", "@types/libsodium-wrappers": "^0.7.10",
@@ -5028,18 +5028,59 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/node": { "node_modules/@sentry/node": {
"version": "7.59.3", "version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.59.3.tgz", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.77.0.tgz",
"integrity": "sha512-5dG90YzmKjuy5TK04qDuc9LBxnGfqsZw8Oh9giwOBfQVCaFp7R14AXyQ1F8k3iUF+4sGeTvoqi9I/GKAItVmlA==", "integrity": "sha512-Ob5tgaJOj0OYMwnocc6G/CDLWC7hXfVvKX/ofkF98+BbN/tQa5poL+OwgFn9BA8ud8xKzyGPxGU6LdZ8Oh3z/g==",
"dependencies": { "dependencies": {
"@sentry-internal/tracing": "7.59.3", "@sentry-internal/tracing": "7.77.0",
"@sentry/core": "7.59.3", "@sentry/core": "7.77.0",
"@sentry/types": "7.59.3", "@sentry/types": "7.77.0",
"@sentry/utils": "7.59.3", "@sentry/utils": "7.77.0",
"cookie": "^0.4.1", "https-proxy-agent": "^5.0.0"
"https-proxy-agent": "^5.0.0", },
"lru_map": "^0.3.3", "engines": {
"tslib": "^2.4.1 || ^1.9.3" "node": ">=8"
}
},
"node_modules/@sentry/node/node_modules/@sentry-internal/tracing": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.77.0.tgz",
"integrity": "sha512-8HRF1rdqWwtINqGEdx8Iqs9UOP/n8E0vXUu3Nmbqj4p5sQPA7vvCfq+4Y4rTqZFc7sNdFpDsRION5iQEh8zfZw==",
"dependencies": {
"@sentry/core": "7.77.0",
"@sentry/types": "7.77.0",
"@sentry/utils": "7.77.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/node/node_modules/@sentry/core": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.77.0.tgz",
"integrity": "sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==",
"dependencies": {
"@sentry/types": "7.77.0",
"@sentry/utils": "7.77.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/node/node_modules/@sentry/types": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.77.0.tgz",
"integrity": "sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/node/node_modules/@sentry/utils": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.77.0.tgz",
"integrity": "sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==",
"dependencies": {
"@sentry/types": "7.77.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -20523,18 +20564,49 @@
} }
}, },
"@sentry/node": { "@sentry/node": {
"version": "7.59.3", "version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.59.3.tgz", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.77.0.tgz",
"integrity": "sha512-5dG90YzmKjuy5TK04qDuc9LBxnGfqsZw8Oh9giwOBfQVCaFp7R14AXyQ1F8k3iUF+4sGeTvoqi9I/GKAItVmlA==", "integrity": "sha512-Ob5tgaJOj0OYMwnocc6G/CDLWC7hXfVvKX/ofkF98+BbN/tQa5poL+OwgFn9BA8ud8xKzyGPxGU6LdZ8Oh3z/g==",
"requires": { "requires": {
"@sentry-internal/tracing": "7.59.3", "@sentry-internal/tracing": "7.77.0",
"@sentry/core": "7.59.3", "@sentry/core": "7.77.0",
"@sentry/types": "7.59.3", "@sentry/types": "7.77.0",
"@sentry/utils": "7.59.3", "@sentry/utils": "7.77.0",
"cookie": "^0.4.1", "https-proxy-agent": "^5.0.0"
"https-proxy-agent": "^5.0.0", },
"lru_map": "^0.3.3", "dependencies": {
"tslib": "^2.4.1 || ^1.9.3" "@sentry-internal/tracing": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.77.0.tgz",
"integrity": "sha512-8HRF1rdqWwtINqGEdx8Iqs9UOP/n8E0vXUu3Nmbqj4p5sQPA7vvCfq+4Y4rTqZFc7sNdFpDsRION5iQEh8zfZw==",
"requires": {
"@sentry/core": "7.77.0",
"@sentry/types": "7.77.0",
"@sentry/utils": "7.77.0"
}
},
"@sentry/core": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.77.0.tgz",
"integrity": "sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==",
"requires": {
"@sentry/types": "7.77.0",
"@sentry/utils": "7.77.0"
}
},
"@sentry/types": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.77.0.tgz",
"integrity": "sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA=="
},
"@sentry/utils": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.77.0.tgz",
"integrity": "sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==",
"requires": {
"@sentry/types": "7.77.0"
}
}
} }
}, },
"@sentry/tracing": { "@sentry/tracing": {
+1 -1
View File
@@ -6,7 +6,7 @@
"@godaddy/terminus": "^4.12.0", "@godaddy/terminus": "^4.12.0",
"@node-saml/passport-saml": "^4.0.4", "@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
"@sentry/node": "^7.49.0", "@sentry/node": "^7.77.0",
"@sentry/tracing": "^7.48.0", "@sentry/tracing": "^7.48.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10", "@types/libsodium-wrappers": "^0.7.10",
+25 -33
View File
@@ -3,52 +3,44 @@ import { ErrorRequestHandler } from "express";
import { TokenExpiredError } from "jsonwebtoken"; import { TokenExpiredError } from "jsonwebtoken";
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors"; import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
import { logger } from "../utils/logging"; import { logger } from "../utils/logging";
import RequestError from "../utils/requestError"; import RequestError, { mapToPinoLogLevel } from "../utils/requestError";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
export const requestErrorHandler: ErrorRequestHandler = async ( export const requestErrorHandler: ErrorRequestHandler = async (
error: RequestError | Error, err: RequestError | Error,
req, req,
res, res,
next next
) => { ) => {
if (res.headersSent) return next(); if (res.headersSent) return next();
const logAndCaptureException = async (error: RequestError) => { let error: RequestError;
// TODO: tie pino error-handling to error types/levels
// (await getLogger("backend-main")).log( switch (true) {
// (<RequestError>error).levelName.toLowerCase(), case err instanceof TokenExpiredError:
// `${error.stack}\n${error.message}` error = UnauthorizedRequestError({ stack: err.stack, message: "Token expired" });
// ); break;
case err instanceof ForbiddenError:
logger.error(error.stack, error.message); error = UnauthorizedRequestError({ context: { exception: err.message }, stack: err.stack })
break;
//* Set Sentry user identification if req.user is populated case err instanceof RequestError:
if (req.user !== undefined && req.user !== null) { error = err as RequestError;
Sentry.setUser({ email: (req.user as any).email }); break;
} default:
error = InternalServerError({ context: { exception: err.message }, stack: err.stack });
Sentry.captureException(error); break;
};
if (error instanceof RequestError) {
if (error instanceof TokenExpiredError) {
error = UnauthorizedRequestError({ stack: error.stack, message: "Token expired" });
}
await logAndCaptureException((<RequestError>error));
} else {
if (error instanceof ForbiddenError) {
error = UnauthorizedRequestError({ context: { exception: error.message }, stack: error.stack })
} else {
error = InternalServerError({ context: { exception: error.message }, stack: error.stack });
}
await logAndCaptureException((<RequestError>error));
} }
logger[mapToPinoLogLevel(error.level)](error);
if (req.user) {
Sentry.setUser({ email: (req.user as any).email });
}
Sentry.captureException(error);
delete (<any>error).stacktrace // remove stack trace from being sent to client delete (<any>error).stacktrace // remove stack trace from being sent to client
res.status((<RequestError>error).statusCode).json(error); res.status((<RequestError>error).statusCode).json(error); // revise json part here
next(); next();
}; };
+1 -1
View File
@@ -29,7 +29,7 @@ export const UnauthorizedRequestError = (error?: Partial<RequestErrorContext>) =
}); });
export const ForbiddenRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({ export const ForbiddenRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO, logLevel: error?.logLevel ?? LogLevel.WARN,
statusCode: error?.statusCode ?? 403, statusCode: error?.statusCode ?? 403,
type: error?.type ?? "forbidden", type: error?.type ?? "forbidden",
message: error?.message ?? "You are not allowed to access this resource", message: error?.message ?? "You are not allowed to access this resource",
+7 -4
View File
@@ -13,9 +13,12 @@ export const logger = pino({
}, },
}, },
transport: { transport: {
target: "pino-pretty", targets: [
options: { {
colorize: true target: "pino-pretty",
} level: process.env.PINO_LOG_LEVEL || "trace",
options: { colorize: true }
}
]
} }
}); });
+24 -23
View File
@@ -2,34 +2,30 @@ import { Request } from "express"
import { getVerboseErrorOutput } from "../config"; import { getVerboseErrorOutput } from "../config";
export enum LogLevel { export enum LogLevel {
DEBUG = 100, TRACE = 10,
INFO = 200, DEBUG = 20,
NOTICE = 250, INFO = 30,
WARNING = 300, WARN = 40,
ERROR = 400, ERROR = 50,
CRITICAL = 500, FATAL = 60
ALERT = 550,
EMERGENCY = 600,
} }
export const mapToWinstonLogLevel = (customLogLevel: LogLevel): string => { type PinoLogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
export const mapToPinoLogLevel = (customLogLevel: LogLevel): PinoLogLevel => {
switch (customLogLevel) { switch (customLogLevel) {
case LogLevel.TRACE:
return "trace";
case LogLevel.DEBUG: case LogLevel.DEBUG:
return "debug"; return "debug";
case LogLevel.INFO: case LogLevel.INFO:
return "info"; return "info";
case LogLevel.NOTICE: case LogLevel.WARN:
return "notice";
case LogLevel.WARNING:
return "warn"; return "warn";
case LogLevel.ERROR: case LogLevel.ERROR:
return "error"; return "error";
case LogLevel.CRITICAL: case LogLevel.FATAL:
return "crit"; return "fatal";
case LogLevel.ALERT:
return "alert";
case LogLevel.EMERGENCY:
return "emerg";
} }
} }
@@ -42,10 +38,10 @@ export type RequestErrorContext = {
stack?: string|undefined stack?: string|undefined
} }
export default class RequestError extends Error{ export default class RequestError extends Error {
private _logLevel: LogLevel private _logLevel: LogLevel
private _logName: string private _logName: string;
statusCode: number statusCode: number
type: string type: string
context: Record<string, unknown> context: Record<string, unknown>
@@ -55,9 +51,10 @@ export default class RequestError extends Error{
constructor( constructor(
{logLevel, statusCode, type, message, context, stack} : RequestErrorContext {logLevel, statusCode, type, message, context, stack} : RequestErrorContext
){ ){
super(message) super(message)
this._logLevel = logLevel || LogLevel.INFO this._logLevel = logLevel || LogLevel.INFO
this._logName = LogLevel[this._logLevel] this._logName = LogLevel[this._logLevel];
this.statusCode = statusCode this.statusCode = statusCode
this.type = type this.type = type
this.context = context || {} this.context = context || {}
@@ -83,8 +80,12 @@ export default class RequestError extends Error{
}) })
} }
get level(){ return this._logLevel } get level(){
get levelName(){ return this._logName } return this._logLevel
}
get levelName(){
return this._logName
}
withTags(...tags: string[]|number[]){ withTags(...tags: string[]|number[]){
this.context["tags"] = Object.assign(tags, this.context["tags"]) this.context["tags"] = Object.assign(tags, this.context["tags"])
+13 -13
View File
@@ -1,4 +1,3 @@
/* eslint-disable no-console */
import crypto from "crypto"; import crypto from "crypto";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto"; import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
@@ -47,6 +46,7 @@ import {
ProjectPermissionSub, ProjectPermissionSub,
memberProjectPermissions memberProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { logger } from "../logging";
/** /**
* Backfill secrets to ensure that they're all versioned and have * Backfill secrets to ensure that they're all versioned and have
@@ -88,7 +88,7 @@ export const backfillSecretVersions = async () => {
) )
}); });
} }
console.log("Migration: Secret version migration v1 complete"); logger.info("Migration: Secret version migration v1 complete");
}; };
/** /**
@@ -518,7 +518,7 @@ export const backfillSecretFolders = async () => {
.limit(50); .limit(50);
} }
console.log("Migration: Folder migration v1 complete"); logger.info("Migration: Folder migration v1 complete");
}; };
export const backfillServiceToken = async () => { export const backfillServiceToken = async () => {
@@ -534,7 +534,7 @@ export const backfillServiceToken = async () => {
} }
} }
); );
console.log("Migration: Service token migration v1 complete"); logger.info("Migration: Service token migration v1 complete");
}; };
export const backfillIntegration = async () => { export const backfillIntegration = async () => {
@@ -550,7 +550,7 @@ export const backfillIntegration = async () => {
} }
} }
); );
console.log("Migration: Integration migration v1 complete"); logger.info("Migration: Integration migration v1 complete");
}; };
export const backfillServiceTokenMultiScope = async () => { export const backfillServiceTokenMultiScope = async () => {
@@ -575,7 +575,7 @@ export const backfillServiceTokenMultiScope = async () => {
} }
} }
console.log("Migration: Service token migration v2 complete"); logger.info("Migration: Service token migration v2 complete");
}; };
/** /**
@@ -650,7 +650,7 @@ export const backfillTrustedIps = async () => {
}); });
await TrustedIP.bulkWrite(operations); await TrustedIP.bulkWrite(operations);
console.log("Backfill: Trusted IPs complete"); logger.info("Backfill: Trusted IPs complete");
} }
}; };
@@ -698,7 +698,7 @@ export const backfillPermission = async () => {
if (lock) { if (lock) {
try { try {
console.info("Lock acquired for script [backfillPermission]"); logger.info("Lock acquired for script [backfillPermission]");
const memberships = await Membership.find({ const memberships = await Membership.find({
deniedPermissions: { deniedPermissions: {
@@ -801,7 +801,7 @@ export const backfillPermission = async () => {
} }
} }
console.info("Backfill: Finished converting old denied permission in workspace to viewers"); logger.info("Backfill: Finished converting old denied permission in workspace to viewers");
await MembershipOrg.updateMany( await MembershipOrg.updateMany(
{ {
@@ -814,14 +814,14 @@ export const backfillPermission = async () => {
} }
); );
console.info("Backfill: Finished converting owner role to member"); logger.info("Backfill: Finished converting owner role to member");
} catch (error) { } catch (error) {
console.error("An error occurred when running script [backfillPermission]:", error); logger.error(error, "An error occurred when running script [backfillPermission]");
} }
} else { } else {
console.info("Could not acquire lock for script [backfillPermission], skipping"); logger.info("Could not acquire lock for script [backfillPermission], skipping");
} }
}; };
@@ -837,5 +837,5 @@ export const migrateRoleFromOwnerToAdmin = async () => {
} }
); );
console.info("Backfill: Finished converting owner role to member"); logger.info("Backfill: Finished converting owner role to member");
} }
+9 -20
View File
@@ -153,29 +153,18 @@ Other environment variables are listed below to increase the functionality of yo
JWT token lifetime expressed in seconds or a string describing a time span JWT token lifetime expressed in seconds or a string describing a time span
</ParamField> </ParamField>
{" "} #### Logging
<ParamField
query="MONGO_USERNAME"
type="string"
default="none"
optional
></ParamField>
{" "}
<ParamField
query="MONGO_PASSWORD"
type="string"
default="none"
optional
></ParamField>
#### Error logging
Infisical uses Sentry to report error logs Infisical uses Sentry to report error logs
{" "} <ParamField
query="PINO_LOG_LEVEL"
type="string"
default="info"
optional
>
The minimum log level for application logging; can be one of `trace`, `debug`, `info`, `warn`, `error`, or `fatal`.
</ParamField>
<ParamField <ParamField
query="SENTRY_DSN" query="SENTRY_DSN"