I faced problem using logger in NestJS, even though the building of CUSTOM Modules and Services didn't take too much time. So let's se how it was built. First of all I installed Winston/Nest
library to make a Winston Logger Module
:
@Module({})
export class WinstonLoggerModule {
static forRoot(): DynamicModule {
return {
imports: [
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.ms(),
nestWinstonModuleUtilities.format.nestLike('MyApp', {
colors: true,
prettyPrint: true,
}),
),
}),
],
// other options
}),
],
module: WinstonLoggerModule,
providers: [
{ provide: WINSTON_LOGGER_SERVICE, useClass: WinstonLoggerService },
],
exports: [WinstonLoggerModule],
};
}
}
After that I created a Winston Logger Service
import { Inject, Injectable } from '@nestjs/common';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston'; // Logger by Winston!!
import { IWinstonLoggerService } from './linterface';
@Injectable()
export class WinstonLoggerService implements IWinstonLoggerService {
constructor(
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
) {}
warn(message): void {
this.logger.warn(message);
}
error(message): void {
this.logger.error(message);
}
debug(message): void {
this.logger.debug(message);
}
info(message): void {
this.logger.info(message);
}
}
I guess no need to show here the IWinstonService cause it's pretty obvious.
After that, I imported the WinstonLoggerModule
into the application module. And I can also inject the WinstonLoggerService
into other services to catch some debugs.
Like this:
@Injectable()
export class DefaultCacheService implements ICacheService {
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache,
@Inject(WINSTON_LOGGER_SERVICE) private logger: IWinstonLoggerService
) {}
async checkInCache(expression: string) {
this.logger.info('Checking Cache...') // logger!!!
const value = await this.cacheManager.get(expression);
return value;
}
async setToCache(cacheData: HistoryItemResponseDto) {
const { expression, result } = cacheData;
await this.cacheManager.set(expression, result, 0);
return cacheData;
}
}
Until this moment everything works fine. But I have to main issues.
** The first ** one is how to inject WinstonLoggerService
inside of custom provider so I could use it's methods for logging errors which may happen during some connections for example Mongo DB Provider
type DataBaseProvider = {
provide: string;
useFactory: (logger: IWinstonLoggerService) => Promise<typeof mongoose>;
}[];
export const databaseProviders: DataBaseProvider = [
{
provide: DATABASE_CONNECTION,
useFactory: async (HERE!!!): Promise<typeof mongoose> => { //How to pass logger here? Should i pass service or Interface or token?
try {
return await mongoose.connect(process.env.DATA_BASE_URI);
} catch (err) {
logger.error( err ) // And how to reach the logger here?
throw new Error(err);
}
},
},
];
And ** the second ** issue is how to log context with my custom logger. I mean, it's one thing to log inputs and outputs inside functions. I would like to log that the service is running in principle. Like the standard Nest.JS logger does it. I mean this:
@Injectable()
class MyService {
private readonly logger = new Logger(MyService.name); // How to use here My Custom Logger?
doSomething() {
this.logger.log('Doing something...');
}
}
I would be very grateful if you could help me with these questions. Thanks.
For your first question how to inject a provider into a custom provider, that's what the inject
array property is for. So you take your custom provider and update the inject
array and the useFactory
to be like so
type DataBaseProvider = {
provide: string;
useFactory: (logger: IWinstonLoggerService) => Promise<typeof mongoose>;
}[];
export const databaseProviders: DataBaseProvider = [
{
provide: DATABASE_CONNECTION,
inject: [WINSTON_LOGGER_SERVICE],
useFactory: async (logger: IWinstonLoggerService): Promise<typeof mongoose> => { //How to pass logger here? Should i pass service or Interface or token?
try {
return await mongoose.connect(process.env.DATA_BASE_URI);
} catch (err) {
logger.error( err ) // And how to reach the logger here?
throw new Error(err);
}
},
},
];
and then as long as the module has access to the WINSTON_LOGGER_SERVICE
it'll all just work. Docs on Custom Providers for more information
For your second question, so long as you use app.useLogger()
in the main.ts
then whenever you use Logger
from Nest, Nest will properly call to the custom logger you've said to use. This is described in the docs here