import { LogLevel } from "./constants";
import { Logging } from "./logging";
import { now } from "./time";
import { LoggedClass } from "./LoggedClass";
import { Logger } from "./types";
export type ArgFormatFunction = (...args: any[]) => string;
export type ReturnFormatFunction = (e?: Error, result?: any) => string;
/**
* @description Method decorator for logging function calls.
* @summary Wraps class methods to automatically log entry, exit, timing, and optional custom messages at a configurable {@link LogLevel}.
* @param {LogLevel} level - Log level applied to the generated log statements (defaults to `LogLevel.info`).
* @param {number} [verbosity=0] - Verbosity threshold required for the entry log to appear.
* @param {ArgFormatFunction} [entryMessage] - Formatter invoked with the original method arguments to describe the invocation.
* @param {ReturnFormatFunction} [exitMessage] - Optional formatter that describes the outcome or failure of the call.
* @return {function(any, any, PropertyDescriptor): void} Method decorator proxy that injects logging behavior.
* @function log
* @mermaid
* sequenceDiagram
* participant Client
* participant Decorator as log decorator
* participant Method as Original Method
* participant Logger as Logging instance
*
* Client->>Decorator: call decorated method
* Decorator->>Logger: log method call
* Decorator->>Method: call original method
* alt result is Promise
* Method-->>Decorator: return Promise
* Decorator->>Decorator: attach then handler
* Note over Decorator: Promise resolves
* Decorator->>Logger: log benchmark (if enabled)
* Decorator-->>Client: return result
* else result is not Promise
* Method-->>Decorator: return result
* Decorator->>Logger: log benchmark (if enabled)
* Decorator-->>Client: return result
* end
* @category Method Decorators
*/
export function log(
level: LogLevel = LogLevel.info,
verbosity = 0,
entryMessage: ArgFormatFunction = (...args: any[]) => `called with ${args}`,
exitMessage?: ReturnFormatFunction
) {
return function log(target: any, propertyKey?: any, descriptor?: any) {
if (!descriptor || typeof descriptor === "number")
throw new Error(`Logging decoration only applies to methods`);
const logger: Logger =
target instanceof LoggedClass
? target["log"].for(target[propertyKey as keyof typeof target])
: Logging.for(target).for(target[propertyKey]);
const method = logger[level].bind(logger) as any;
const originalMethod = descriptor.value;
descriptor.value = new Proxy(originalMethod, {
apply(fn, thisArg, args: any[]) {
method(entryMessage(...args), verbosity);
try {
const result = Reflect.apply(fn, thisArg, args);
if (result instanceof Promise) {
return result
.then((r: any) => {
if (exitMessage) method(exitMessage(undefined, r));
return r;
})
.catch((e) => {
if (exitMessage) logger.error(exitMessage(e as Error));
throw e;
});
}
if (exitMessage) method(exitMessage(undefined, result));
return result;
} catch (err: unknown) {
if (exitMessage) logger.error(exitMessage(err as Error));
throw err;
}
},
});
return descriptor;
};
}
/**
* @description Method decorator that records execution time at the benchmark level.
* @summary Wraps the target method to emit {@link Logger.benchmark} entries capturing completion time or failure latency.
* @return {function(any, any, PropertyDescriptor): void} Method decorator proxy that benchmarks the original implementation.
* @function benchmark
* @mermaid
* sequenceDiagram
* participant Caller
* participant Decorator as benchmark
* participant Method as Original Method
* Caller->>Decorator: invoke()
* Decorator->>Method: Reflect.apply(...)
* alt Promise result
* Method-->>Decorator: Promise
* Decorator->>Decorator: attach then()
* Decorator->>Decorator: log completion duration
* else Synchronous result
* Method-->>Decorator: value
* Decorator->>Decorator: log completion duration
* end
* Decorator-->>Caller: return result
* @category Method Decorators
*/
export function benchmark() {
return function benchmark(target: any, propertyKey?: any, descriptor?: any) {
if (!descriptor || typeof descriptor === "number")
throw new Error(`benchmark decoration only applies to methods`);
const logger: Logger =
target instanceof LoggedClass
? target["log"].for(target[propertyKey as keyof typeof target])
: Logging.for(target).for(target[propertyKey]);
const originalMethod = descriptor.value;
descriptor.value = new Proxy(originalMethod, {
apply(fn, thisArg, args: any[]) {
const start = now();
try {
const result = Reflect.apply(fn, thisArg, args);
if (result instanceof Promise) {
return result
.then((r: any) => {
logger.benchmark(`completed in ${now() - start}ms`);
return r;
})
.catch((e) => {
logger.benchmark(`failed in ${now() - start}ms`);
throw e;
});
}
logger.benchmark(`completed in ${now() - start}ms`);
return result;
} catch (err: unknown) {
logger.benchmark(`failed in ${now() - start}ms`);
throw err;
}
},
});
return descriptor;
};
}
/**
* @description Method decorator for logging function calls with debug level.
* @summary Convenience wrapper around {@link log} that logs using `LogLevel.debug`.
* @return {function(any, any, PropertyDescriptor): void} Debug-level logging decorator.
* @function debug
* @category Method Decorators
*/
export function debug() {
return log(
LogLevel.debug,
0,
(...args: any[]) => `called with ${args}`,
(e?: Error, result?: any) =>
e
? `Failed with: ${e}`
: result
? `Completed with ${JSON.stringify(result)}`
: "completed"
);
}
/**
* @description Method decorator for logging function calls with info level.
* @summary Convenience wrapper around {@link log} that logs using `LogLevel.info`.
* @return {function(any, any, PropertyDescriptor): void} Info-level logging decorator.
* @function info
* @category Method Decorators
*/
export function info() {
return log(LogLevel.info);
}
/**
* @description Method decorator for logging function calls with silly level.
* @summary Convenience wrapper around {@link log} that logs using `LogLevel.silly`.
* @return {function(any, any, PropertyDescriptor): void} Silly-level logging decorator.
* @function silly
* @category Method Decorators
*/
export function silly() {
return log(LogLevel.silly);
}
/**
* @description Method decorator for logging function calls with trace level.
* @summary Convenience wrapper around {@link log} that logs using `LogLevel.trace`.
* @return {function(any, any, PropertyDescriptor): void} Trace-level logging decorator.
* @function trace
* @category Method Decorators
*/
export function trace() {
return log(LogLevel.trace);
}
/**
* @description Method decorator for logging function calls with verbose level.
* @summary Convenience wrapper around {@link log} that logs using `LogLevel.verbose` with configurable verbosity.
* @return {function(any, any, PropertyDescriptor): void} Verbose logging decorator.
* @function verbose
* @category Method Decorators
*/
export function verbose(): (
target: any,
propertyKey?: any,
descriptor?: any
) => void;
/**
* @description Method decorator for logging function calls with verbose level.
* @summary Convenience wrapper around {@link log} that logs using `LogLevel.verbose` while toggling benchmarking.
* @return {function(any, PropertyDescriptor): void} Verbose logging decorator.
* @function verbose
* @category Method Decorators
*/
export function verbose(): (
target: any,
propertyKey?: any,
descriptor?: any
) => void;
/**
* @description Method decorator for logging function calls with verbose level.
* @summary Convenience wrapper around {@link log} that logs using `LogLevel.verbose` with configurable verbosity and optional benchmarking.
* @param {number|boolean} verbosity - Verbosity level for log filtering or flag to enable benchmarking.
* @return {function(any, any,PropertyDescriptor): void} Verbose logging decorator.
* @function verbose
* @category Method Decorators
*/
export function verbose(verbosity: number | boolean = 0) {
if (!verbosity) {
verbosity = 0;
}
return log(LogLevel.verbose, verbosity as number);
}
/**
* @description Creates a decorator that makes a method non-configurable.
* @summary Prevents overriding by marking the method descriptor as non-configurable, throwing if applied to non-method targets.
* @return {function(object, any, PropertyDescriptor): PropertyDescriptor|undefined} Decorator that hardens the method descriptor.
* @function final
* @category Method Decorators
*/
export function final() {
return (target: object, propertyKey?: any, descriptor?: any) => {
if (!descriptor)
throw new Error("final decorator can only be used on methods");
if (descriptor?.configurable) {
descriptor.configurable = false;
}
return descriptor;
};
}
Source