Error Logging in Javascript: 4 Things to Know

The literal meaning of a log is “an official record of events during the voyage of a ship or aircraft”. As programmers, we will usually not be dealing with that type of logs, but instead with logs that tell us what happened at a particular time in our software system.

If you have used any programming language before, you would have come across a way to print something on the display or on the console for debugging purposes (i.e System.out.println for Java, cout for C++, etc…). This is called logging. Error logging is basically adding logs that are of the level ERROR, which is mainly used when there is an exception. ERROR level logs are also usually the only logs that are printed and maintained when the application is deployed into production.

Javascript is a language that is very popular in the Hybrid app development community due to the variety of well-supported frameworks that is based on it. Recently my team had to work on the task of adding descriptive error logs to an entire Node.js application by replacing the ones which were already in place.

As part of this task, we were to make sure that these error logs are descriptive enough so that it is easy to spot an error or bug and identify the root cause easily. While working on the task there were some things that I noticed to be true, which I will be expounding in this blog post. So without further ado let’s begin.

Less descriptive logs are better than no logs (Existence)

For most people, this would be a no-brainer. It is always better to have something than to have nothing. When it comes to error logging, it is better for the console to only say “Error” when there is an exception, than it to say nothing and make the programmer scratch his head when the app in production wouldn’t load.

For example, if the loader of an app keeps spinning, the programmer wouldn’t be able to say whether it is related to a slow internet connection, a CSS mistake, a variable not being reset, or something way more serious. In this case, if there is an error log that reads “Failure during getUsers request”, it would save a lot of the programmer’s time.

Some things in life are useful by just merely existing. Error logs are one of them.

The more specific the log is, the easier is it to pin down the problem (Specificity)

Yes, I just told you that error logs merely existing is useful. But if “Error occurred” is all that you print in every single place every single time, you wouldn’t get much information apart from the fact that an “error occurred”.

Whenever an error log is going to be added, it is always good to keep these 3 W’s in mind while deciding on what to print:

  1. What happened?
  2. Where it happened?
  3. Why it happened? (if the information is available)

What happened: This is the description which says what the exception actually is. (Example: Failure during getUsers request)

Where it happened: Print the method name, as well as the file name containing that method as a part of the error log. (Example: login-controller.ts, getUsers)

Why it happened: This is optional information that is not always available while printing the error log. But having this as a part of the log, if available, will make the job of the programmer trying to debug the issue easier (an example for this would be “Due to request timeout“). Sometimes when there is a Javascript error that is thrown that is related to the language’s syntax, then the catch block would have the error object which at many times would be enough to know why the exception occurred.

JSON.stringify(err) vs ${err} (Preference)

At the end of the previous point, I mentioned that the error object that is provided in the catch block would be usually enough to know why an exception occurred. When it comes to using this error object in the error log however, there is a snag that has to be kept in mind.

Consider the following piece of code:

try {
    let a: any = {};
    console.debug(a.b.a);
} catch (err) {
    console.error(`Failure during getUsersRequest - Error:`, JSON.stringify(err));
    console.error(`Failure during getUsersRequest - Error: ${err}`);
}

What might be the difference in the approaches in printing the error objects on line 5 and on line 6? We can see from the code inside the try block that a TypeError would be thrown by the compiler. But what would be the difference in what is being printed?

If you look carefully at the console in the screenshot below you would find that for JSON.stringify(err) the error object is printed as {}. This is definitely not something which would help us. But the same error object prints a more informative message when printed using ${err}. The reason this happens is because JSON.stringify serializes only enumerable properties in an object. Since the TypeError object does not have enumerable properties, JSON.stringify returns an empty object string. But on the other hand however, if we use ${err} instead of JSON.stringify at all places, then whenever there is a custom error object being returned from an inner catch block, that would always be printed as [object Object] at all places. This is a catch-22 situation.

What we need now is a solution that works in both of these situations. Here it comes:

class CustomError {
    public message: string;
    public status: number;
    public errorObject: any;
    constructor(message: string, status: number, errorObject?: any) {
        this.message = message;
        this.status = status;
        this.errorObject = errorObject;
    }
    public toString = (): string => {
        let errorObjectString = (this.errorObject && this.errorObject.toString) ? this.errorObject : JSON.stringify(this.errorObject);
        return `${this.message} - Error: ${errorObjectString}`;
    }
}
try {
    let a: any = {};
    console.debug(a.b.a);
} catch (err) {
    let customError: CustomError = new CustomError("Failure during getUsers request", 500, err);
    console.error(`Failure during getUsersRequest - Error:`, JSON.stringify(err));
    console.error(`Failure during getUsersRequest - Error: ${err}`);
    console.error(customError.toString());
}

If you see what I am doing in the above piece of code, I am basically creating a custom error class that handles the decision on whether to use JSON.stringify(err) or ${err}. By doing this we do not have to worry whether the error object would actually be printed. The overridden toString() method in the CustomError class takes care of that logic for us. All we have to do is to call the toString() method using the custom error object, and then pass the return value to the logging method. Mission accomplished.

Always have guards in your catch block (Defence)

Nothing hurts more than when a friend betrays you. In exception handling, the catch block is our friend. But when the catch block itself throws an error (unintentionally), then that would end up in an unhandled exception that could crash the application. We don’t want that. That is why we always need to have guards whenever we perform any major operation apart from just logging the error. Although this is important in the rest of the code as well, this is mainly important in the operations done in the catch block.

An example:

try {
  // Some code here
} catch(error) {
  if (error.message === "Generic Error") {
    // Do something
  } else if (error.message === "Some error") {
    // Do something
  } else {
    // Do something
  }
}

In the above piece of code, we just assume that the error object will always have a value. In an ideal world that is possible. But if the error object is undefined for some reason, then the code would break saying cannot read property ‘message’ of undefined. In order to avoid this, it is always good to add guards.

try {
  // Some code here
} catch(error) {
  if (error && error.message === "Generic Error") {
    // Do something
  } else if (error && error.message === "Some error") {
    // Do something
  } else {
    // Do something
  }
}

Bonus: Centralize your Logging System (Universalism)

Last but not least, it is always good to have a logging utility that centralizes how logging is actually taking place. In some cases, we may want to create a log stash where we store logs as soon as the log method is invoked. In this case, the log method in the logging utility would first print the log using console.error and then follow up with a write to a file or something else.

Also, it is possible to use a different implementation for printing logs in the future by just swapping out console.error with a different method call (Eg. winston). At the end of the day, it can also enforce strict formats for printing logs which will help make the logs look consistent.

That’s all for this time. Adios!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.