Just another Log Helper
I am sure that in the depths of this community one or the other article / blog post about logging can be found, and nevertheless I wrote a new article about it. You may wonder why. The reason is that I was not satisfied with the OOTB logging means offered to me. And so I developed my own logging helper, which I improved more and more over several projects.
Hint:
ServiceNow offers a number of different logs, but for this article only the facilities of server-side scripts are of interest. For client-side logging, please refer to the relevant documentation.
Why do we need logging?
A good log allows insights into the process and the health of a system or an application, which would otherwise not be possible via an interface. Having a good log and monitoring infrastructure is a key feature allowing system administrators, support teams, and even developers to be more prepared to face possible problems. And because this is not trivial, from the very beginning this must be a topic that every project leader, architect and developer has to keep in mind. Architects and technical leads must define what, how, and when to log and even how to extract meaningful data from logs. Moreover, they must keep a sharp eye during reviews in order to ensure that the code satisfies the definitions. Developers and everyone involved in the project must not forget to send log messages at least in every layer of the architecture.
What should be logged?
It is not enough to log errors in order to use them for troubleshooting purposes. It is also useful to log successful requests and the interactions of users, so we can have a clear understanding of how users work with our application.
Tables syslog & syslog_app_scope
These two tables contain all outputs generated by one of the log commands. In addition, the stack traces of all uncaught exceptions are also written here. While the syslog table contains output from both global and scoped applications, the syslog_app_scope table only holds output written within scoped applications. Furthermore, you can define the log level for scoped applications, from which on outputs should be written into the table syslog_app_scope (see Application Logging and the logging.destination and logging.verbosity System Properties for more information). In addition, both tables differ in the level of detail.
Example output at table syslog:
Example output at table syslog_app_scope
Session Log
The Session Log is not stored in any table but can be viewed by navigating to System Diagnostics > Session Debug > Debug Log
As a result, a separate and well-know window with the Script Tracer, the Script Debugger and the Session Log will open. In addition to the log outputs, a lot of other information is continuously printed here, and it is therefore useful to narrow down the list with the help of the filters and the additional free-text search.
Example output at the Session Log:
Log Commands
The following table summarizes which logging methods exists for writing server-side log outputs:
| | Table syslog | Table syslog_app_scope | Session Log | |
| ------------------- | ---------------------------- | --------------- | - |
| gs.log() | x | | x |
| gs.debug() | | x1 | x |
| gs.info() | x | x1 | x |
| gs.warn() | x | x1 | x |
| gs.error() | x | x1 | x |
1only if logging verbosity is set at the respective application and log level
As you can see in the above table for script-based log outputs only the commands gs.info(), gs.warn() & gs.error() are suitable as they work in global scope as well as in scoped applications.
Requirements
Simple switch to debug mode
The OOTB method gs.debug() is not helpful as in the global scope no outputs are written to the syslog table and for scoped application a respective system property has to be set. However, this system property only applies to the specified application and not for all applications globally. More helpful would be a single method call from a global Script Include to enable debug outputs immediately, which are also written to the syslog table.
Debugging fatal errors
There are situations where I need another debugging level to log thrown and caught exceptions. In this scenario also an additional and formatted output of the caught exception is necessary.
Unique source logging
This is the most important requirement. While for scoped applications in the syslog_app_scope table the origin is rendered in a clickable version (column "Source Script"), such a feature is completely missing in the syslog table.
Context of code execution
The most worthless logging outputs are those where a single value is printed without any indication of where exactly the execution was performed.
For this reason, the outputs of the LogHelper are preceded by a prefix in square brackets, which is composed of two values:
- automatically determined node name
- user-specific log scope
Usage of numbered Placeholders
From the method gs.getMessage() we are used to specifying numbered placeholders in the message key, which are replaced with the also passed values automatically. Such a feature would also be helpful for the logging methods to keep the source code as simple as possible.
Support of Functional Programming
And additionally, it would be nice if the logging methods would return the final logging message to support the functional programming paradigm.
Solution
The solution is a comparatively simple Script Include with purely static methods, which eliminates the need for prior object instantiation.
Note:Don't forget to set "Accessible from" with the value "All application scopes" if you want to save that Script Include at "Global" scope:
All methods require passing of a scope (= origin from which the call was made) which is prepended in square brackets to the corresponding message.
Note:
When storing the Script Include in the "Global" scope, log messages will be only written to the syslog table but not to the syslog_app_scope table!
Examples
All method invocations follow the same structure:
global.LogHelper.[debug|info|warn|error|fatal]('<LOG SCOPE>', 'LOG MESSAGE'[, e][, values...]);
Hints:
- In case you want to call LogHelper methods from within your scoped application, you have to prepend the class name with global.
- All method calls return the message as a string value to support the functional programming paradigm. This way the resulting message will be also displayed in your script console without the need to reload the syslog table.
Simple info logging with placeholders
Use placeholders and any list of additional parameters to let the LogHelper methods generate a converted message.
Code:
var strSource = "EUR";
var strTarget = "USD"
global.LogHelper.info('CurrenyUtils.convert', 'Convert from {0} to {1}', strSource, strTarget);
Output at syslog:
Enabling Debug Outputs
In order to be able to understand the circumstances that led to the error, detailed log outputs are essential. On the other hand, in a production environment you want to have as little "trivial" log output as possible, so that the really important error output can be found more quickly. My LogHelper supports that by providing a system property loghelper.enable.debug which can be set (for example on DEV). If enabled, all outputs created by LogHelper.debug() will be written to the log table, otherwise they will be suppressed.
Code:
global.LogHelper.debug('Test', 'Hello World');
Output at syslog:
Fatal logging of caught exceptions
As the GlideSystem object does not offer a gs.fatal() method, internally gs.error() is called instead. The added value of the fatal() method is handling and printing the exception.
Code:
try {
a = b + 1;
}
catch (e) {
global.LogHelper.fatal(
'CurrenyUtils.convert', 'Internal error!', e
);
}
Output at syslog:
Enhancements
The given source code only represents a base variant which can be further expanded. For example at method fatal() you could create an event and a notification email which consumes that event could inform some persons about the caught runtime error.
Another improvement would be to enrich the log outputs with information available in the session, such as the name of the logged-in user or the URL that was just accessed.
Source Code
The following source code is originally hosted on my public GitHub repo at https://github.com/mskoddow/sn-scripts/blob/master/LogHelper.js
https://www.servicenow.com/community/developer-articles/just-another-log-helper/ta-p/2315453
