Logging
Log through Nest's Logger with a class context, then make the application use a logger of your own that extends ConsoleLogger and keeps what it prints, with the boot log buffered until it exists.
What you'll learn
- Log from a class through new Logger(ClassName.name), at the right level, in Nest's own format
- Extend ConsoleLogger, keep its output and add behaviour, and install it with app.useLogger() from the container
- Explain what bufferLogs does and why the boot log is lost without it
Every lesson so far has shown Nest's boot log in the console: [NestFactory] Starting Nest application..., [RouterExplorer] Mapped {/cats, GET} route. That is the built-in logger, and the same class is meant for your own messages, so that an application's log reads as one stream with one format, whether a line came from Nest or from CatsService. This lesson uses it properly, then replaces it with a logger of the shelter's own, and keeps Nest's own lines flowing through the replacement.
Logger, with a context
Logger from @nestjs/common is the class Nest's internals log through. A class makes its own instance, named after itself:
@Injectable()
export class CatsService {
private readonly logger = new Logger(CatsService.name);
findAll() {
this.logger.log('Listing cats');
return [];
}
}
The output carries the context in brackets, [CatsService] Listing cats, the level, a timestamp and the process id, exactly like Nest's lines. The levels are log, error, warn, debug, verbose and fatal; error takes a stack as its second argument. Since v12, an object after the message is structured data: logger.log('User created', { userId: 1 }).
NestFactory.create(AppModule, { logger: ['error', 'warn'] }) limits what is printed; the levels cascade, so 'log' includes warn and error. logger: false silences everything, and new ConsoleLogger({ json: true }) prints one JSON object per line, {"level":"log","pid":19096,"timestamp":1607370779834,"message":"Starting Nest application...","context":"NestFactory"}, for log collectors that parse it.
Your own logger
app.useLogger(logger) makes any object with the LoggerService methods (log, error, warn, and optionally debug, verbose, fatal) the application's logger. Nest's internal lines go through it, and so does every new Logger() instance, because Logger is a thin static front that forwards to whatever useLogger set. Two ways to build one. From scratch, implementing the interface, when the destination is a logging service with its own client. Or by extending ConsoleLogger, keeping the format and adding behaviour:
export class MyLogger extends ConsoleLogger {
error(message: any, stack?: string, context?: string) {
// add your tailored logic here
super.error(...arguments);
}
}
One detail the docs' snippet skips: ConsoleLogger's own constructor takes an optional context and options. A subclass with no constructor inherits those parameter types, but not the @Optional() markers on them, which Nest reads from the class itself; the container then tries to inject a String and an Object, and fails. A subclass that Nest constructs declares its own constructor, even an empty constructor() { super(); }.
The docs recommend making the logger a provider of its own module, so it can inject what it needs and be injected in turn, and installing it from the container at boot:
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(MyLogger));
bufferLogs: true is the detail that matters. Nest logs while it boots, before useLogger can run; without buffering those lines go to the default logger and are lost to yours. With it, they wait and are flushed through the logger you install. A provider with scope: Scope.TRANSIENT gives each class that injects it a fresh instance, so each can setContext() its own name; a singleton reads the context from each call instead, which is what Nest passes as the last argument.
Your task
The shelter's staff read the log from the API, so the logger keeps what it prints.
ShelterLoggerextendsConsoleLogger:log,warnanderrorstill print, and record{ level, context, message }inentries, a hundred at most. Nest passes the context as the last optional parameter.CatsServicelogs through aLoggernamed after the class: an entry when a cat is created, a warning when one is not found.main.tsbuffers the boot log and installsShelterLoggerfrom the container as the application's logger.
Skip the buffering first and read GET /logs?context=NestApplication: the boot happened before your logger existed, and its lines are not there. Then add bufferLogs: true.
When it fails
GET /logsis empty although the console shows the lines:useLoggerwas never called, soLoggerforwards to the defaultConsoleLogger, not to yours.- Nest's boot lines are missing from the entries, yours are present: no
bufferLogs; the boot was logged beforeuseLoggerran. - The context is empty on every entry: it is the last optional parameter, not a property; read
optionalParams[optionalParams.length - 1]. Nest could not find ShelterLogger element:app.get()looks the provider up in the container; the logger must be a provider of an imported module.Nest can't resolve dependencies of the ShelterLogger (?, Object): the subclass has no constructor, so it inheritedConsoleLogger's parameter types without their@Optional()markers. Declareconstructor() { super(); }.
Remember
new Logger(ClassName.name)in each class; the output matches Nest's own lines.NestFactory.create(App, { logger: [...] })filters levels;ConsoleLogger({ json: true })prints JSON.app.useLogger(app.get(MyLogger))routes everything through your logger, Nest's internals included.bufferLogs: trueholds the boot log until your logger is installed.
Stuck? Show a hint
shelter.logger.ts: log(message, ...optionalParams) { super.log(message, ...optionalParams); this.record('log', message, optionalParams); } and the same for warn and error; the context is optionalParams[optionalParams.length - 1] when it is a string, else this.context. cats.service.ts: private readonly logger = new Logger(CatsService.name); this.logger.log(`Created cat #${cat.id} ${cat.name}`); this.logger.warn(`Cat #${id} not found`). main.ts: NestFactory.create(AppModule, { bufferLogs: true }); app.useLogger(app.get(ShelterLogger)).