API with NestJS #50. Introduction to logging with the built-in logger and TypeORM

JavaScript NestJS TypeScript

This entry is part 50 of 177 in the API with NestJS

As our application grows, more and more people start depending on it. At a time like this, it is crucial to ensure that our API works well. To do that, we could use a way to troubleshoot the application to detect anomalies and to be able to find their origin. This article serves as an introduction to how we can keep logs on what happens in our application.

Logger built into NestJS

Fortunately, NestJS comes with a logger built-in. Before using it, we should create its instance.

posts.service.ts

Although we could use the we import from directly, creating a brand new instance for every service is a good practice and allows us to supply the name of the service for the constructor.

Log levels

A crucial thing about the is that it comes with a few methods:

  • error
  • warn
  • log
  • verbose
  • debug

The above methods correspond with log levels that we can configure for our application.

main.ts

We don’t use the above to read the environment variables because it isn’t initialized yet.

getLogLevels.ts

Because of the above setup, the and methods won’t produce logs on production.

If we take a look at the isLogLevelEnabled function, we can notice that providing turns on all of the log levels, not only . This is because NestJS assumes that if we want to display the logs, we also want to display logs of all of the lower levels. Because of that, is the same as .

We can find the importance of each log level here.

After doing all of the above, let’s start using the logger.

posts.service.ts

Now we can see that passing caused to appear as a prefix to our log message.

Using the logger in a middleware

Even though the above approach might come in handy, it might be cumbersome to write log messages manually. Thankfully, we can produce logs from middleware.

logs.middleware.ts

Check out the MDN documentation to read more about HTTP response status codes.

Above, we gather information about the request and response and log it based on the status code. Of course, the request and response objects contain more helpful information, so feel free to make your logs even more verbose.

The last step is to apply our middleware for all of our routes.

app.module.ts

Using the logger with TypeORM

Another helpful thing we can do is to log all SQL queries that happen in our application. To do that with TypeORM, we need to implement the interface:

databaseLogger.ts

The last step is to use the above class in our TypeORM configuration:

database.module.ts

When we start looking into the logs from TypeORM, we notice that it often produces quite lengthy queries. For example, the below query happens when we retrieve the data of the user that attempts to log in:

Saving logs into a PostgreSQL database

So far, we’ve only been logging all of the messages to the console. While that might work fine when developing the application on our machine, it wouldn’t make a lot of sense in a deployed application. There are a lot of services that can help us gather and manage logs, such as DataDog and Loggly. They are not free of charge, though. Therefore, in this article, we save logs into a PostgreSQL database.

For starters, let’s create an entity for our log:

log.entity.ts

Above, we use the decorator. If you want to know more about dates in PostgreSQL, check out Managing date and time with PostgreSQL and TypeORM

Once we’ve got the above done, let’s create a service that allows us to create logs:

logs.service.ts

Above, you can notice that we pass when saving our logs to the database. The above is because we need to overcome the issue of an infinite loop. When we store logs in the database, it causes SQL queries to be logged. When we log SQL queries, they are saved to the database, causing an infinite loop. Because of that, we need to adjust our slightly:

databaseLogger.ts

Above, we don’t log SQL queries if they are involved in creating logs.

Now we need to extend the logger built into NestJS and use the :

customLogger.ts

We also need to create the so that we can add it into our :

logger.module.ts

The final step is to call the method to inject our custom logger into the application:

main.ts

We could store some data in separate columns. Fore example, we could do that with the HTTP methods to be able to query the logs looking only for POST request, for example.

Summary

In this article, we’ve gone through the basics of logging with NestJS and TypeORM. We’ve learned about various log levels and how we can log messages directly and through middleware. We’ve also learned how to save our logs into an SQL database. Doing that has some benefits. For example, we could store more data in separate columns and use them when querying the data.

Even if saving logs into an SQL database has some advantages, the performance might not be the best if we have a lot of logs. Also, it could fill up the available space of our database. Therefore, it might be a good idea to look into services such as DataDog and Loggly. However, it is a topic for another article, so stay tuned!

Series Navigation<< API with NestJS #49. Updating with PUT and PATCH with MongoDB and MongooseAPI with NestJS #51. Health checks with Terminus and Datadog >>
Subscribe
Notify of
guest
4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Mitch
Mitch
2 years ago

one little error. if is not indicated context the error is like so:
ERROR [AuthController] Data too long for column ‘password’ at row 1
ERROR [AuthController] undefined

two records is created and not one. why not use this.context

error (message: string, stack?: string) {
  super.error(message, stack, this.context)
  this.logsService.createLog({
   message,
   context: this.context,
   stack: stack,
   level: ‘error’
  })
 }

Diliru
Diliru
2 years ago

Hey can you please also demonstrate implementing logger to mongoose

Xdev
Xdev
2 years ago

How would you limit logging to only specific routes

Last edited 2 years ago by Xdev
Phong
Phong
1 year ago

HI !

Thank you for your article but I am so confusing that in case we need to create brand new instance of Logger in service class, so why we need to setup in main.ts like below

const app = await NestFactory.create(AppModule, {
logger: console // or LogLevel or LogService,
});

why we need to do that, I have read the code and do not know how it can related in Service and in app

If we create brand new instance of Logger in each service how we can make sure that both are in same config