All About Logging

What is an application log?

There are multiple different types of logging including system logs, server logs, garbage collector logs, and application logs.

An application log is a file that contains information about events that have occurred within a software application. Every application produces logs which hold information about what the application is doing.

What type of information is included in a log? It varies by the application. The developers of the software application control what goes into the log file, but there are some commonly included components:

  • context info: Background information that provides insight into the state of the application at the time of the message
  • timestamps: A specific piece of contextual information for tracking and correlating issues
  • Log levels: Labels that help you calculate the level of importance for the entries in your log file. Frequently used levels include INFO, WARN, and ERROR

Why are logs important?

The main use for logs is diagnostics. Logs hold information that can help with

  • debugging
  • analytics
  • compliance
  • security

Logging Agent vs Logging Library

A logging agent is a separate service that reads logs from one location and sends them to another location. Think of it like a funnel.

Some benefits of logging agents include:

  1. minimal setup & config
  2. multi-threading & failure safety
  3. no code
  4. scalable (single agent instance can log for almost any number of applications)
  5. modify/update/replace agents w/o having to take down application
  6. format-agnostic

Some drawbacks of logging agents include:

  1. require interaction w/ host (not good w/ Serverless)
  2. requires CPU compute usage to track log files

A logging library (or logging framework) is code that you embed into your application to create and manage log events.

Some benefits of logging libraries include:

Some drawbacks of logging libraries include:

  • usually synchronous (application must wait for the library to finish writing a log event before continuing)
  • no failure safety (libraries only run as long as your application is running)
  • not scalable (each application runs its own instance of the library)

Logging in Node with Winston

What is Winston?

Winston is a simple and universal logging library.

Why use a logging library?

When developing an application, sometimes we need to get information about how the application is running. We can use functions like console.log, console.error, console.warn to accomplish this. A logging library, such as Winston, allows us to output similar types of data except it can send this data (log data) to locations other than just the console. For example, an external file.

The basic idea of logging

Basically, you have a logger that writes to an arbitrary amount of transports. A transport is essentially a storage device for your logs. Some common transports include:

  • stdout (console)
  • file
  • HTTP
  • Datadog (log aggregator)

Log levels

Log levels allow us to categorize the entries in a log file by urgency. The higher up it is, the more severe it is. As you can see, fatal is the most severe.

These are npm log levels:

{
   fatal: 0,
   error: 0,
   info: 0,
   http: 0,
   verbose: 0,
   debug: 0,
}

Setting up a simple logger in Winston

The recommended way to use winston is to create your own logger. The simplest way to do this is using winston.createLogger.

Install Winston in project

npm install winston

In a new file, create a logger with winston.createLogger

const winston = require('winston');

const logger = winston.createLogger({

});

module.exports = logger;

Add custom properties to logger

const winston = require('winston');

const logger = winston.createLogger({
   format: winston.format.simple(),
   transports: [new winston.transports.Console()],
});

module.exports = logger;

The format property controls how the logs are displayed visually.

transports is an array of the places (transports) to send the log data to. In the above example, our only transport is the console.

Use the logger

const logger = require('./logger')   // access the file you created earlier

logger.info('text info');
logger.warn('text warn');
logger.error('text error');
logger.debug('text debug');

The output should look something like this:

Why is the debug log not showing up? The default log level for a Winston logger is info. This means that Winston will only log the info level and all levels ABOVE it (warn, error, etc.)

Creating custom log format in Winston

Destructure Winston import

This is just for our own ease of use. No change to the logic.

const { createLogger, format, transports } = require('winston');
const { timestamp, combine, printf } = format;

const logger = createLogger({
  format: format.simple(),
  transports: [new transports.Console()]
});

Create custom format

const myFormat = printf(({ level, message, timestamp }) => {
  return `${timestamp} ${level}: ${message}`;
});

Add the format to your logger

const logger = createLogger({
  format: combine(timestamp(), myFormat),
  transports: [new transports.Console()]
});

Final code

const { createLogger, format, transports } = require('winston');

const myFormat = printf(({ level, message, timestamp }) => {
  return `${timestamp} ${level}: ${message}`;
});

const logger = createLogger({
  format: combine(timestamp(), myFormat),
  transports: [new transports.Console()]
});

Here is another custom log format

const logger = createLogger({
  format: combine(
       format.colorize(),
       timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), 
       myFormat
  ),
  transports: [new transports.Console()]
});