How To Do Logging In NodeJs?
I’m going to talk about logging in nodejs in this article. Logging is the most important part of your application but to make it work proper in scalable applications like microservice architectures becomes difficult, as you would want to know the logs you see are coming from where!
I will show you how I make logging in nodejs streamlined and avoid logging at multiple places unnecessarily.
Table of Contents
Implementing Logging in Nodejs:
A proper logging structure will make it easy for you and coming developers to put logs in your application. I’m using express framework in this article, but you can implement the same in any framework of your choice.
We need a logging library to print logs, Bunyan is a good option out there. Remember you can always replace your logger library easily inside our logger service and that’s the benefit of implementing this logger service.
You can follow along the article and go through the code on github
Let’s make a logger service
We will make a modular logger that can be imported anywhere in your project without worrying about changing the logger at anytime and not affecting your whole project. If you ever need to change level or you want to use a new logging library, you can do so in this file.
services/logger.js
var bunyan = require("bunyan"); // Logger library
/**
* You can make a wrapper here for the logging levels
* and export your wrapper as the logger, which will
* support a few logger libraries
*/
var logger = bunyan.createLogger({
name: "node-logger-demo", // Name can be service name and extra details you may wan to put.
level: "info" // Logger level should be read from environment variables.
});
// We will add remote stream here next
module.exports = logger;
Implement error handler in express
If you have scaffolded your app using express generator, you will have an error handler created for you, else you want to create one shown below inside your app.js file. This middleware accepts first parameter as error and is called whenever you make a next(error) call.
If you are a newbie in express, there is an example of next() coming in this article, keep going.
app.js
const logger = require('./services/logger'); // require the logger that we just created.
...
// Put this middleware at last after defining your routes.
// error handler
app.use(function (err, req, res, next) {
/**
* Log the error using our logger service
* We will control logging in dev and prod environment in
* our logger service itself.
*/
logger.error(err);
res.status(err.status || 500);
res.send({ "error": err.message });
// send only the error message that you would like to send to user
});
...
You can import the logger service in all your project modules and log easily the same way it’s used inside app.js error handler. But now you will do that rarely because of this error handler which logs every error you pass inside next as next(error) from your routes. Lets put this together and see how it works.
Using next(error) inside your routes
This is one route registered with app.js, and is attached at api endpoint /api/user. If you make a mongoose query or call a function which returns a promise or accepts a callback function, you can always do next(error) inside catch of promise or callback function if error is not null. This avoid you writing an additional logging statement and also makes it easy to send a response to user with proper error message, all because of error handler that we wrote just above.
routes/user.js
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function (req, res, next) {
try {
/**
* make a db query to get users
* I'm hard coding a dummy json for the purpose
*/
var result = {
users: [{ name: "user1" }, { name: "user2" }]
};
}
catch (error) {
/**
* If something goes wrong, error is sent to next
* and than its logged by error handler
*/
next(error);
return; // return to stop further execution
}
res.send(result);
});
module.exports = router;
Above code snippet passes the error to error handler, hence it does not require to import the logger service and the error automatically gets logged inside the error handler we wrote above. You can still import logger service to log info and other level, take a look at the below code snippet.
Using logger service to log info, debug etc logs
routes/user.js
var logger = require('../services/logger');
...
router.get('/:id', function (req, res, next) {
const user = {
name: "user1"
};
// use logger service to log info.
logger.info(`requested user id: ${req.params.id}`);
res.send(user)
})
module.exports = router;
Here we imported the logger service and used it to log an info about the requested users id.
You can use the logger this way anywhere in your project and log anything. The best part is again replace the logger library inside logger service, change the log level and any settings and you are good to go with new library without any extra changes. We are done with logging setup here but now lets see how we can see those logs on a remote website by just enhancing our logger service, after all who wants to see logs in a log file anymore!
Implement remote log stream
If you haven’t been using any remote logging service before, you should now. This allows viewing your logs at nice place by just login into your account and lets you analyze the logs, you can do filtering and all kind of stuff easily. We will integrate logentries in our logger service to see and analyze logs at logentries. There are other choices for logging service you can choose according to your needs. I have used logentries in a few of my projects and it does really a good job.
Integrate logentries
We need a bunyan transport to send logs to logentries, The below one is very light implementation over tcp, Install bunyan-transport
npm install --save bunyan-transport
Now lets require this module to make our logger service send logs to logentries. Make changes to the logger.js file as shown below, we only changed our logger service and everything just works as it is, but now you will see logs at logentries.com instead of the console.
Bunyan prints a lot more information than you might need sometimes, you can configure which key values to be sent to logentries inside bunyan transport. You can read more about it here at bunyan-transport
services/logger.js
const bunyanTransport = require('bunyan-transport');
logger = bunyan.createLogger({
name: 'node-logger-demo',
streams: [{
level: "info",
stream: new bunyanTransport.logentriesStream({
token: "token" // logentries token
}),
type: 'raw'
}]
});
module.exports = logger;
Till here its all fine, but you may be thinking to use this only in prod environment and not in development mode. You can sure do that, and I guess you already have your own way working with different environments, but still here’s a snippet to put all together.
Production and dev environment setup in logger service
I always keep a separate group of modules to read and validate environment variables and export as config to consume in my project. I will directly use the environment variables here to show you the working of logger. Based on type of environment we create a logger and export that from logger service, which includes log level as well.
const bunyan = require("bunyan");
const bunyanTransport = require('bunyan-transport');
let logger = null;
if (process.env.NODE_ENV == "production") {
logger = bunyan.createLogger({
name: 'node-logger-demo',
streams: [{
level: process.env.LOG_LEVEL,
stream: new bunyanTransport.logentriesStream({
token: process.env.LOG_TOKEN
}),
type: 'raw'
}]
});
}
else {
logger = bunyan.createLogger({
name: 'node-logger-demo',
level: process.env.LOG_LEVEL
});
}
module.exports = logger;
So, that’s how I make a streamlined logger in my nodejs projects, please share what tools you use in your projects and any suggestion on how this can be improved, and thanks for reading this article.