Logging best practices for Node.JS application

Krunal Shah

Mar 23, 2020 | 5 min read

Logging best practices for Node.JS application

Introduction

Logging is helping developers reduce mistakes and cyber attacks. The application is dynamic in design. We can't always foresee how an application will react to changes in the data, errors or program. Logging in lets us comprehend our own programs better.

An absolute logging solution is very important for all of the applications. A successful logging system improves the reliability of the application and makes it easy to maintain on the production server. Application logging is the process of collecting knowledge about the run time of your program.

What are the logs?

Logs are the events that reflect the various aspects of your application, it is the easiest mode of troubleshooting and diagnosing your application, if written correctly by the team.

Importance of logging?

As we already said that a good logging solution builds the application robustly. Every node.js module or application needs logging to know real-time errors.

During development, it’s often useful to log debug messages to investigate specific issues. In those scenarios, it’s useful to log:

  • The value of a related variable
  • If a particular function was called
  • The return value for a function call
  • API responses
  • The sequence of particular events

Log Levels

Logging level exists for reason, they indicate us to put a log message into one of them, sorted by urgency. That allows us to filter the messages of the production log by the level of urgency.

Logging levels severity specified by RFC5424. These are described in the following table along with their numerical values.  Severity values MUST be in the range of 0 to 7 inclusive.

Numerical Code Severity
0 Emergency: system is unusable
1 Alert: action must be taken immediately
2 Critical: critical conditions
3 Error: error conditions
4 Warning: warning conditions
5 Notice: normal but significant condition
6 Informational: informational messages
7 Debug: debug-level messages

Best practices for logging

Important parts of logs.

Application logs are for both machines and humans. Humans use logs to debugging issues and machines use logs to generate reports and graphs for data analysis.

Every log should consist of three parts.

  • Source of log
  • Timestamp
  • Context and level

Source of log

When we are using microservices architecture this becomes really crucial to know the source of the log, the details like service name, method, zone, etc..

Detailed metadata about the source is mostly handled by the log shipper agent. Which pushes logs into a centralized logging system.

Timestamp

When an event occurred or log generated it’s very important to have a timestamp of that moment. So make sure every log has a timestamp so that we can perform actions on that like sort or filter by occurrence time.

Context and level

While referring to logs to find bugs, if logs do not have enough information and if you have to refer back to source code to understand, it’s always frustrating for any developer. So while logging we should always pass enough context.

E.g. A log without context would look like.‌

User transactions fail.

The proper example with meaningful context looks like.

User transaction fails due to invalid expiry date of the card.

What and how to log

Logs methods and inputs

While debugging if we get to know which method was called and what the parameters were passed, it's really helpful to developers.

So log methods entry along with parameters and exit points will help us a lot to find a place where we need to debug.

Let’s check using a perfect example.


exports.doTransaction = async (req, res, next) => {
 try {
   logger.info(`>>>>> Entering user controller (parameter: ${req.body})`);

   // process   

   logger.info('<<<<< Leaving user controller');
   res.json({ … });
 } catch (error) {
   logger.error(':::::error: catch block error:::::');
   next(error);
 }
};

The log should not produce a side effect

The logs should be stateless and should not produce any side effects. For example. If you see the cardModel.save  that will create a new resource in the database is called inside the logger, this is incorrect logger should only deal with information logging and not implementing business logic.

exports.doTransaction = async (req, res, next) => {
 try {
   logger.info(`>>>>> Entering user controller (parameter: ${req.body})`);

   // process   
   logger.debug('<<<<< saving card details    
                cardModel.save(req.body.cardInfo) // Don’t do this
              ');
`   res.json({ ... });
 } catch (error) {
   logger.error(':::::error: catch block error:::::');
   next(error);
 }
};

Log errors with details

If there is an error please be descriptive, mention what was attempted and why it failed.

Log what failed and what you are trying to do next.

exports.saveUser() {
    try {
          logger.info(`>>>>> Entering user controller (parameter: ${req.body})`);
        // Handle parameters
        checkUnique(username)
       const maskedUsername = maskUsername(username)
    } catch(error) {
        log.error(`Failed to do checkUnique with username ${maskedUsername}, ignoring it and trying to save it`, error);
        saveUserDetails(userDetails);
    }
}

Here we logged what failed and what we are doing in the next process.

Sensitive information

We have to make sure of some things while logging that we should not log sensitive information like username and password, financial information, card details, etc..

As developers, we should refer or consult with the product team to prepare a list of sensitive information mast it before logging.

Correct usage of log levels

If production applications have heavy traffic on a regular basis then the ideal log setup might generate GBs of logs. Hence we have to categorize our logs in groups, so we can get appropriate logs by filtering.

Don’t use console.log

Most developers have used console.log to log and debug code, as it is easy to use, easily accessible and no need to setup is required.

Using console.log,  debug and info print in stdout, Hence we will not be able on-off debug and info. Similarly, console.warn and console.error both print in stderr, switching between them is very hard in production applications.

To overcome all these issues we can use Winston logging framework, and there are few more options are Bunyan, Pino etc..

Performance impact

If the frequency of writing log is high it may impact the performance of the application directly.that's why we should only enable the error and warning level, and change the level to debug when we want to figure out the issue and switch it back to the error.‌


Third Rock Techkno is a leading IT services company. We are a top-ranked web, voice and mobile app development company with over 10 years of experience. Client success forms the core of our value system.

We have expertise in the latest technologies including angular, react native, iOs, Android and more. Third Rock Techkno has developed smart, scalable and innovative solutions for clients across a host of industries.

Our team of dedicated developers combine their knowledge and skills to develop and deliver web and mobile apps that boost business and increase output for our clients.