Sign up to receive SuiteScript examples and advice directly in your email inbox.

5 Logging in SuiteScript 2.0

Created: May 29, 2017

Logging is an incredibly important tool in software development, and that's no different in SuiteScript. We use logging to write messages to our Script's Execution Log. These messages can help us track down errors, audit the execution path of the script, and trace through data values as the script progresses.

In SuiteScript 1.0, we added log messages using the nlapiLogExecution function; in SuiteScript 2.0, we use the methods found in the N/log module.

N/log

On the log module, there is one method per Log Level, and each accepts a single Object parameter.

Logging API Equivalencies between SuiteScript 1.0 and 2.0

The severity and importance of the message we want to log dictates which level we should use. Later, when we are viewing our messages, we can use the Log Level to filter down to a very specific view of what our script is doing. If we always log everything at DEBUG, then we are only making it harder on ourselves and other developers to trace through our script to analyze it appropriately.

All four log methods accept exactly the same input options, so we really only need to see an example of one to get familiar with all of them. However, I want to make sure we're using logging effectively, so I'll show the logging strategy that I employ using an example we've seen previously in the series.

Logging Strategy

To explain the strategy briefly, let's go through how I use each log level.

DEBUG is the lowest, or most detailed, logging level. For that reason, I use DEBUG for the very minute details of my script. I primarily use it for writing the values of specific variables throughout my script.

AUDIT is the next level, and I use this level as an indicator of where the script currently is in its execution. Primarily, I put an AUDIT log at the very beginning of every function that simply tells me which function we're entering.

ERROR comes next, and as you might imagine, I use this for problems that are encountered during my script, but there is a little more to it than that. I specifically use ERROR when I am able to recover from the problem.

For example, let's say I have a Map/Reduce script responsible for finding all of my un-invoiced Sales Orders and creating an Invoice for them. If the script fails to transform a specific Sales Order into an Invoice for whatever reason, I would consider that an ERROR because there's no reason the script can't just continue on to more Sales Orders. When I know the problem isn't severe enough to cause more problems later in my script, I use the ERROR level.

But what about more serious errors? What if in that same Map/Reduce script, the search I am running to get the data to process in the first place fails? Well, my script certainly can't continue without those results, so that is where I use EMERGENCY because at that point, my script can no longer continue on and do anything meaningful.

Example

To start, here's a module we used in the sublists video along with its function to find and remove a line from a sublist. Notice I've added the N/log module to our dependency list. This actually isn't necessary, as the log module is actually available globally. You can reference it from any 2.0 module at any time without including it in your dependencies, but I prefer to be explicit.

/**
 * @NApiVersion 2.0
 * @NScriptType Suitelet
 * @NModuleScope SameAccount
 */
define(["N/record", "N/log"], function (r, log) {
    // ...
    function removeLine(rec) {
        var index = rec.findSublistLineWithValue({
            sublistId: "item",
            fieldId: "item",
            value: 77
        });

        if (index === -1) {
            // Not found
            return;
        }

        try {
            rec.removeLine({
                sublistId: "item",
                line: index
            });
        } catch (e) {
            // Fails silently
        }
    }
    // ...
});

Now let's update it with some logging.

First I want to add my AUDIT message immediately upon entering the function; all it needs to do is log the name of the function being called:

function removeLine(rec) {
    log.audit({title:"removeLine"});
    // ...

Next I want a DEBUG message that tells me the value of index after it's initialized:

var index = rec.findSublistLineWithValue({
    sublistId: "item",
    fieldId: "item",
    value: 77
});
log.debug({details:"index = " + index});

Now how about another AUDIT message that lets me know we didn't find the line:

if (index === -1) {
    log.audit({details:"Could not find item"}); 
    return;
}

Lastly, instead of my catch block failing silently on any exception from removeLine, I at least want some more details on the exception. Since failing to remove the line is probably still a problem I can recover from, I'll just use the ERROR level:

try {
    rec.removeLine({
        sublistId: "item",
        line: index
    });
} catch (e) {
    log.error({
        title: "Error removing line item",
        details: e.message
    });
}

By following this pattern, I can establish a very consistent routine for tracking down a problem in a script. When a script encounters an error, I look at its AUDIT-level messages. This shows me a trail of the functions that were executed, leading right up to the error. When I find the error, I also see the last function that was executed in the logs; this gives me a great starting point for investigation.

Once I've identified the function, then I switch to viewing DEBUG-level messages between the function start and the error, which can help me identify the cause. Even if there aren't any messages in between, I know right where I can start adding new messages to help me trace the source of the error.