I have a global logger module in nest, that logs to a cloud logging service. I am trying to create a class method decorator that adds logging functionality. But I am struggling
Okay, found a solution. In case anyone else stumbles upon this. First please keep in mind how decorators work – they are class constructor based, not instance based.
In my case I wanted to have my logger service injected in the class instance. So the solution is to tell Nest in the decorator to inject the LoggerService into the instance of the class that contains the decorated method.
import { Inject } from '@nestjs/common';
import { LoggerService } from '../../logger/logger.service';
export function logErrorDecorator(bubble = true) {
const injectLogger = Inject(LoggerService);
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
injectLogger(target, 'logger'); // this is the same as using constructor(private readonly logger: LoggerService) in a class
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = async function(...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
const logger: LoggerService = this.logger;
logger.setContext(target.constructor.name);
logger.error(error.message, error.stack);
// rethrow error, so it can bubble up
if (bubble) {
throw error;
}
}
};
};
}
This gives the possibility to catch errors in a method, log them within the service context, and either re-throw them (so your controllers can handle user resp) or not. In my case I also had to implement some transaction-related logic here.
export class FoobarService implements OnModuleInit {
onModuleInit() {
this.test();
}
@logErrorDecorator()
test() {
throw new Error('Oh my');
}
}