238 lines
6.2 KiB
TypeScript

import {
Logger,
Injectable,
Controller,
ControllerOptions,
ScopeOptions,
} from "@nestjs/common";
import { ScopedLogger } from "./logger";
import { LoggedParamReflectData } from "./reflected";
import { loggedParam, scopedLogger } from "./reflected";
import objectContainedLogged from "./functions";
function loggerInit(_target: any) {
if (!Object.getOwnPropertyNames(_target).includes("logger")) {
const newTargetLogger = new Logger(_target.constructor.name);
newTargetLogger.log("Logger Initialized.");
Object.defineProperty(_target, "logger", {
writable: false,
enumerable: false,
configurable: false,
value: newTargetLogger,
});
}
}
export function LoggedInjectable(options?: ScopeOptions) {
return (target: any) => {
target = Injectable(options)(target);
loggerInit(target.prototype);
const logger = target.prototype.logger;
const methods = Object.getOwnPropertyNames(target.prototype);
methods.forEach((method) => {
if (
method !== "constructor" &&
typeof target.prototype[method] === "function"
) {
logger.log(`LoggedFunction applied to ${method}`);
LoggedFunction(target.prototype, method, {
value: target.prototype[method],
});
}
});
};
}
export function LoggedController(): (target: any) => void;
export function LoggedController(
prefix: string | string[]
): (target: any) => void;
export function LoggedController(
options: ControllerOptions
): (target: any) => void;
export function LoggedController(param?: any): (target: any) => void {
return (target: any) => {
target = Controller(param)(target);
loggerInit(target.prototype);
const logger = target.prototype.logger;
const methods = Object.getOwnPropertyNames(target.prototype);
methods.forEach((method) => {
if (
method !== "constructor" &&
typeof target.prototype[method] === "function"
) {
logger.log(`LoggedRoute applied to ${method}`);
LoggedRoute()(target.prototype, method, {
value: target.prototype[method],
});
}
});
};
}
export function LoggedFunction<F extends Array<any>, R>(
_target: any,
key: string,
descriptor: TypedPropertyDescriptor<(...args: F) => Promise<R>>
) {
loggerInit(_target);
const logger: Logger = _target.logger;
const fn = descriptor.value;
if (!fn || typeof fn !== "function") {
logger.warn(
`LoggedFunction decorator applied to non-function property: ${key}`
);
return;
}
_target[key] = async function (...args: F) {
const scopedLoggerInjectableParam: number = Reflect.getOwnMetadata(
scopedLogger,
_target,
key
);
if (
typeof scopedLoggerInjectableParam !== "undefined" &&
(args.length <= scopedLoggerInjectableParam ||
!(args[scopedLoggerInjectableParam] instanceof ScopedLogger))
) {
args[scopedLoggerInjectableParam] = new ScopedLogger(logger, key);
} else if (typeof scopedLoggerInjectableParam !== "undefined") {
args[scopedLoggerInjectableParam] = new ScopedLogger(
args[scopedLoggerInjectableParam],
key
);
}
const injectedLogger =
typeof scopedLoggerInjectableParam !== "undefined"
? args[scopedLoggerInjectableParam]
: logger;
const loggedParams: LoggedParamReflectData[] = Reflect.getOwnMetadata(
loggedParam,
_target,
key
);
injectedLogger.log(
`CALL ${key} ${
loggedParams && loggedParams.length > 0
? "WITH " +
(
await Promise.all(
loggedParams.map(
async ({ name, index, include, exclude }) =>
name +
"=" +
(await objectContainedLogged(args[index], {
include,
exclude,
}))
)
)
).join(", ")
: ""
}`
);
try {
const r: R = await fn.call(this, ...args);
injectedLogger.log(`RETURNED ${key}`);
return r;
} catch (e) {
injectedLogger.error(`WHILE ${key} ERROR ${e}`);
throw e;
}
};
}
export function LoggedRoute<F extends Array<any>, R>(route?: string) {
return (
_target: any,
key: string,
descriptor: TypedPropertyDescriptor<(...args: F) => Promise<R>>
) => {
loggerInit(_target);
const logger = _target.logger;
let fullRoute = `${_target.constructor.name}/`;
const fn = descriptor.value;
if (!fn) return;
descriptor.value = async function (...args: F) {
const scopedLoggerInjectableParam: number = Reflect.getOwnMetadata(
scopedLogger,
_target,
key
);
fullRoute += route || Reflect.getMetadata("path", fn);
if (
typeof scopedLoggerInjectableParam !== "undefined" &&
(args.length <= scopedLoggerInjectableParam ||
!(args[scopedLoggerInjectableParam] instanceof ScopedLogger))
) {
args[scopedLoggerInjectableParam] = new ScopedLogger(logger, fullRoute);
}
const injectedLogger =
typeof scopedLoggerInjectableParam !== "undefined"
? args[scopedLoggerInjectableParam]
: logger;
const loggedParams: LoggedParamReflectData[] = Reflect.getOwnMetadata(
loggedParam,
_target,
key
);
injectedLogger.log(
`HIT HTTP ${fullRoute} (${key}) ${
loggedParams && loggedParams.length > 0
? "WITH " +
(
await Promise.all(
loggedParams.map(
async ({ name, index, include, exclude }) =>
name +
"=" +
(await objectContainedLogged(args[index], {
include,
exclude,
}))
)
)
).join(", ")
: ""
}`
);
try {
const r: R = await fn.call(this, ...args);
injectedLogger.log(`RETURNED RESPONSE ${fullRoute} (${key})`);
return r;
} catch (e) {
injectedLogger.error(`WHILE HTTP ${fullRoute} (${key}) ERROR ${e}`);
throw e;
}
};
};
}