Where are JavaScript Errors Logged?

Unlike other web languages, JavaScript was originally a client-side language. As a result, error handling is designed with the client side in mind, rather than the server side. Rather than dealing with log files, rotation, permissions, and all the other fun things that come with server-side languages, JavaScript errors are dealt with inline. While the client-side-only perception is slowly giving way to server-side Node.js due to the increased security and performance benefits of server-side rendering, many of the logging conventions of Node.js are still influenced by its browser-based siblings.

Introduction to The Console

In both client-side JavaScript and Node.js, log data is handled by default via a global console instance. While client-side JavaScript writes console data to the individual browser's developer console, Node.js console data is written to stdout and stderr. What this means, at least in terms of client-side JavaScript, is that log data is readily available to both the end user and the developer. While this can present some privacy issues, it can make debugging significantly easier in any environment. For those concerned about privacy, logging can also be disabled in production through logging frameworks or minification tools.

Log Levels

When it comes to logging, console.log() is one of the more common debug methods used in JavaScript development. That said, the Console object offers a handful of other useful log levels, which can be used to provide more relevant information with minimal changes.

log()

The purpose of console.log() is the general output of logging information.

error()

console.error(), as the name implies, outputs an error message. While the browser console will flag this as an error message, in Node.js applications the log message will be written directly to stdout.

warn()

As with console.error(), console.warn() outputs a warning message to the console. In Node.js applications, console.warn() is an alias for console.error(), meaning that any message sent to it will be written directly to stdout.

info()

If console.info() appears to be the same as console.log(), that is because it is. While it flagged differently in the browser, similarly to how console.error() and console.warn() are separated out, console.info() is an alias of console.log() in Node.js.

Log Formatting

One of the more interesting facets of JavaScript logging is the flexibility of the log formats. While many programming languages only support plain text logging, JavaScript provides the ability to write anything—from integers to objects—to the console log. Beyond that, the logging mechanism supports a few additional features that make logging significantly more powerful.

String substitution

The first thing to be aware of here is the logging method's ability to support string substitution, which provides a great mechanism for writing incredibly well-formatted log messages.

console.log('String %s', 'substitution');

Counting

If you need to keep track of the total occurrences of a specific issue, the Console object provides a count() mechanism that counts how many times a given label has been called.

> console.count('abc')
abc: 1
> console.count('xyz')
xyz: 1
> console.count('abc')
abc: 2

Grouping

To better format related logs within the console, the console.group() command allows you to indent all of the following logs until the console.groupEnd() command is called. Grouping can be nested, increasing the indent level of each subsequent log message.

console.log("Top level");
console.group();
console.log("Level 1");
console.group();
console.log("Level 2");
console.groupEnd();
console.log("Level 1");
console.groupEnd();
console.log("Top level");

The above code, when run, looks like the following when run within both a console and browser window:

Console Log Grouping Example

Forced stack traces

While stack traces are generally only found in exceptions (more on stack traces in How to Debug JavaScript Errors in Production), it is possible to output the current stack trace without handling command execution. The console.trace() command will do just that, allowing for more in-depth debugging without interrupting the program execution.

Logging in Node.js

While Node.js supports many of the same logging conventions as browser-based JavaScript, its server-side nature opens up to significantly more powerful tools designed with server infrastructure in mind. A great example of this is Node.js logging framework Winston. Rather than simply piping data directly to stdout, Winston is a logging framework that is designed to give you total control over the who, what, when where, why, and how of your logs. To better understand how Winston works, let’s take a look at a basic logging configuration:

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    //
    // - Write to all logs with level `info` and below to `combined.log` 
    // - Write all logs error (and below) to `error.log`.
    //
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

//
// If we're not in production then log to the `console` with the format:
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
// 
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

logger.log(‘info’, ‘I am an INFO’);
logger.log(‘error’, ‘ERROR! UH OH!);

What the above code does is creates a basic logger with some interesting settings. The most interesting aspect of the configured logger is that it uses separate log files for different log levels. Any log labeled "info" and below are sent to the combined.log file, while errors are sent to their own error.log file. This allows you to much more effectively identify issues without having to sort through debug messages. Additionally, to better support development, Winston can be configured to send logs directly to the console outside of a production environment. This is incredibly useful, as it gives developers the ability to identify and fix issues in their code without having to keep one eye on the logs.

With the ability to log and format data directly to designated log files, Winston is a powerful tool that allows Node.js to take the same care of logging data as other server-side languages like PHP and Ruby.