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; } }; }; }