Are you struggling with debugging your code? Are you searching for some logging solutions that might make debugging easier? Read on to learn more. 

Software development goes through several phases: requirements gathering, analysis, coding, testing, and maintenance. Out of all these phases, the coding/ development phase requires a lot of time and effort. Software engineers deal with syntax errors, logical errors, and runtime errors. Syntactical errors are identified at compile time and occur due to the code not abiding by the rules of a programming language.

On the other hand, logical and run-time errors can’t be identified by the Integrated Development Environment (IDE) and are often difficult to debug and fix. Resolving bugs is a time-consuming process and requires a lot of debugging.

Debugging is a process in which one tries to understand why the code written isn’t working as expected. It is easy to solve the issue when we know the mistake and the exact lines in the code where it is occurring. Hence, logging is very useful for debugging code.  

What is Logging?

Logging is a technique in which messages are captured during the execution of a program. One has to log only those messages, which could help them in debugging. So, knowing when to add log statements to the code is extremely important. Also, differentiating between log statements is equally essential. There are various levels in logging,, like info, warn, error, debug, and verbose. Error and warning statements are used for exception handling.

Data that is returned from functions, results after array manipulation, data retrieved from APIs, etc., are some examples of data that can be logged using info statements. The debug and verbose logs are used to give a detailed description of the errors.

The debug log gives information about the stack trace, input-output parameters, etc. “Verbose” is not as detailed as the “debug” log but gives a list of all the events that have occurred. Logs are written out to the console, files, and output stream. Log management tools can be used for structured and formatted logging.

Node.js Logging

Nodejs is a javascript runtime environment. Node.js applications are asynchronous and non-blocking, and are used in data-intensive and real-time systems. The best way to learn more about Node.js is to go through Node.js tutorials and its documentation. Logging is required for improved performance, troubleshooting, and error tracking. Logging in Node.js can be done using the inbuilt function console.log. Also, debug function is interlinked with multiple packages and can be used effectively.

Middleware is used for managing requests and responses. Middleware could be an application or any other Javascript framework. Logging in middleware can be done through applications and routers. Any Node.js logger has to use the npm or yarn install command to install the loggers.

Npm stands for “Node Package Manager,” and YARN stands for “Yet Another Resource Negotiator”. However, Yarn is preferred over npm as it is faster and installs packages parallelly.

Some of the best Node.js loggers are listed below:

Pino

Pino is a library that is one of the best loggers for Node.js applications. It is open source, extremely fast, and logs the statements in an easy-to-read JSON format. Some of the Pino log levels are – debug, warn, error, and info messages. A Pino logger instance can be imported into the project, and the console.log statements must be replaced with logger.info statements.

YouTube video

Use the following command to install Pino:

$ npm install pino   

The logs generated are elaborate and in JSON format, highlighting the line number of the log, the type of log, the time when it was logged, etc. Pino causes minimal overhead logging in an application and is extremely flexible while processing logs.

Pino can be integrated with web frameworks like Hapi, Restify, Express, etc. Logs generated by Pino can also be stored in files. It uses Worker threads for operation and is compatible with TypeScript. 

Winston

Winston supports logging for various web frameworks with its primary focus on flexibility and extensibility. It supports multiple kinds of transport and can store logs in various file locations. Transports are places where the log messages are stored.

Along with some built-in transports like Http, Console, File, and Stream, it supports other transports such as Cloud watch and MongoDB. It does logging under various levels and formats. Logging levels indicate the severity of the issue. 

YouTube video

The various logging levels are as shown below:

{
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  verbose: 4,
  debug: 5,
  silly: 6
}

The log output format can be customized, filtered, and combined too. Logs include information about the timestamp, labels associated with a log, milliseconds elapsed from the previous log, etc.

Winston also handles exceptions and uncaught promises. It provides additional features such as query runtime filing, streaming logs, etc. Firstly, one has to install Winston. Then, a Winston configuration object, along with transport, is created for storing the log. A logger object is created using the createLogger() function, and the log message is passed to it.     

Node-Bunyan

Bunyan is used for fast logging in node.js in JSON format. It also provides a CLI (Command Line Interface) tool for viewing the logs. It is lightweight and supports various runtime environments like Node.js, Browserify, WebPack, and NW.js. The JSON format of the logs is further beautified using the pretty printing function. Logs have various levels like fatal, error, warn, info, debug and trace; each is associated with a numeric value.

All levels above the level set for the instance are logged. Bunyan stream is a place where the outputs are logged. Subcomponents of an application can be logged using the log.child() function. All the child loggers are bounded to a specific parent application. The stream type could be a file, rotating file, raw data, etc. The code sample for defining a stream is shown below:

var bunyan = require('bunyan');
var log = bunyan.createLogger({
    name: "foo",
    streams: [
        {
            stream: process.stderr,
            level: "debug"
        },
        ...
    ]
});

Bunyan also supports DTrace logging. The probes involved in DTrace logging include log-trace, log-warn, log-error, log-info, log-debug, and log-fatal. Bunyan uses serializers to produce the logs in JSON format. Serializer functions don’t throw exceptions and are defensive.  

Loglevel

Loglevel is used for logging in Javascript applications. It is also one of Node.js best logger as it is lightweight and simple. It logs the given level and uses a single file with no dependencies for logging. The default log level is “warn.” The log outputs are well formatted along with line numbers. Some methods used for logging are trace, debug, warn, error and info.

They are resilient to failure in any environment. getLogger() is the method used to retrieve the logger object. It can be combined with other plugins as well to extend its features. Some of the plugins include loglevel-plugin-prefix, loglevel-plugin-remote, ServerSend, and DEBUG. The plugin for adding prefix messages to logging is shown below:

var originalFactory = log.methodFactory;
log.methodFactory = function (methodName, logLevel, loggerName) {
    var rawMethod = originalFactory(methodName, logLevel, loggerName);

    return function (message) {
        rawMethod("Newsflash: " + message);
    };
};
log.setLevel(log.getLevel()); // Be sure to call setLevel method in order to apply plugin

The builds are run using the npm run dist command, and the tests can be run using the npm test command. Log level supports Webjar, Bower, and Atmosphere packages. A new version of Loglevel is released whenever new features are added.

Signale

Signale consists of 19 loggers for Javascript applications. It supports TypeScript and scoped logging. It consists of timers that help log the timestamp, data, and filename. Apart from the 19 loggers like await, complete, fatal, fav, info, etc., one can create custom logs.

Custom logs are created by defining a JSON object and fields with the logger data. Interactive loggers can also be created. When an interactive logger is set to true, new values from interactive loggers override the old ones.

The best part of Signale is the ability to filter out secretive or sensitive information. Multiple secrets are stored in an array. addSecrets() and clearSecrets() are the functions used for adding and clearing the secrets from the array.  Boostnote, Docz, Shower, Taskbook, and Vant use Signale for logging. The syntax for calling APIs from Signale is as follows:

signale.<logger>(message[,message]|messageObj|errorObj)

The number of Signale downloads is over 1 million at the time of writing this article. 

Tracer

Tracer is used for producing detailed logging messages. Logging messages consist of timestamps, file names, line numbers, and method names. Helper packages can be installed to customize the output logging format. The helper packages can be installed using the following command.

 npm install -dev tracer

Tracer supports file, stream, and MongoDB transport. It supports color console and filter conditions in logging. Initially, the tracer has to be installed using npm install. Secondly, a logger object has to be created, and the kind of console has to be selected. Then, the various log levels or types can be specified over the object for further logging.

Customized filters can be created by defining synchronous functions with the business logic present in the function body. Micro-templates like tinytim also can be used for system logging.

Cabin.js

Cabin is used for server and client-side logging of node.js applications. It is used where the masking of sensitive and critical information is required. This includes credit card numbers, BasicAuth Headers, salts, passwords, CSRF tokens, and bank account numbers. The code snippet below shows logging using Cabin.js.

const Cabin = require('cabin');
const cabin = new Cabin();
cabin.info('hello world');
cabin.error(new Error('oops!'));

It consists of more than 1600 field names. It also follows the principle of Bring Your Own Logger (BYOL). This makes it compatible with various other loggers like Axe, Pino, Bunyan, Winston, etc. It reduces storage costs in disks due to automatic stream and Cabin buffers. It is cross-platform compatible and easy to debug.

Server-side logging requires using middleware for routing and automatic output logging. Browser-side logging requires XHR requests and scripts. It uses Axe that displays metadata, i.e., data about data, stack traces, and other errors. SHOW_STACK and SHOW_META are boolean variables set to true or false to show or hide stack traces and metadata. 

Npmlog

Npmlog is a basic type of logger that npm uses. Some of the logging methods used are level, record, maxRecordSize, prefixStyle, heading, and stream. It also supports colored logging. The various logging levels are silly, verbose, info, warn, http and error. A sample code snippet for using the npm log is shown below.

var log = require('npmlog')

// additional stuff ---------------------------+
// message ----------+                         |
// prefix ----+      |                         |
// level -+   |      |                         |
//        v   v      v                         v
    log.info('fyi', 'I have a kitty cat: %j', myKittyCat)

All messages are suppressed if “Infinity” is specified as the log level. If “-Infinity” is specified as the log level, the option to see logging messages has to be enabled to see the logs.

Events and message objects are used for logging. Prefix messages are emitted when prefix events are used. Style objects are used for formatting the logs, like adding color to text and background, font style like bold, italics, underline, etc. Some npm log packages are brolog, npmlogger, npmdate log, etc.

Roarr

Roarr is a logger for Node.js that doesn’t require initialization and produces structured data. It has CLI and environmental variables. It is browser compatible. It can be integrated with Fastify, Fastify, Elastic Search, etc. It can distinguish between application code and dependency code. Every log message consists of a context, message, sequence, time, and version. Various log levels include trace, debug, info, warn, error, and fatal. A sample code snippet as to how logging is done is Roarr is as follows:

import {
  ROARR,
} from 'roarr';

ROARR.write = (message) => {
  console.log(JSON.parse(message));
};

Also, serialization of errors can be done, meaning the instance with the error can be logged along with the context of the object. Some of the environment variables which is specific to Node.js and Roarr are ROARR_LOG and ROARR_STREAM. “adopt” is a function that is used with node.js to pass down the context properties to various levels. Child functions can be used with middleware, too, while logging.

Final Words

Logging is a method of keeping track of various activities and events during the execution of a program. Logging plays a vital role in code debugging. It also helps in increasing code readability. Node.js is an open-source, javascript run time environment. Some of the best Node.js loggers are Pino, Winston, Bunyan, Signale, Tracer, Npmlog, etc. Each type of logger has its own features like profiling, filtering, streaming, and transportation.

Some loggers support colored consoles, and some are suitable for handling sensitive information. Detailed and formatted logs help developers the most while they try to fix bugs in their code. JSON format is generally preferred for logging because it logs data in the form of key-value pairs making it user-friendly.

Loggers can also be integrated with other applications and are multi-browser compatible. It is always advisable to look into the needs and applications you are building before choosing the type of logger you want to use.

You may also look at how to install Node.js and NPM on Windows and macOS.