Debugging ServiceNow Scripts: Script Debugger, Logging and Troubleshooting Toolkit
ServiceNow Developer Series, Part 7
Series so far: Part 1: Script Include Basics · Part 2: Business Rules · Part 3: GlideAjax in Depth · Part 4: Returning Multiple Values (JSON) · Part 5: GlideRecord Deep Dive · Part 6: Client Scripts vs UI Policies vs Data Policies · Part 7: Debugging Toolkit (this post)
1. Why this topic matters
You have now learned how to write server scripts, client scripts, GlideAjax calls, and GlideRecord queries. Sooner or later, one of them will not behave. A field stays empty. A Business Rule seems to be ignored. A GlideAjax call returns null. A script works for you but fails for a colleague.
What separates a fast developer from a slow one is rarely typing speed. It is how quickly they find out what is actually happening. Many developers fall back on one habit: scatter gs.log() lines everywhere, run the script, and hope. ServiceNow gives you far better tools, and most of them are only a few clicks away.
This post gives you:
- a simple way to decide where a bug lives before you touch any tool
- a clean approach to logging that works in global and scoped apps
- a practical tour of the Script Debugger, Session Log, Session Debug, Script Tracer and Field Watcher
- a step-by-step method to debug GlideAjax calls from Parts 3 and 4
- troubleshooting tables for the problems you will meet most often
2. Start with the right question: where does the bug live?
The most expensive debugging mistake is using a server tool on a client problem, or the other way round. Before you open anything, decide which side is failing.
Quick ways to tell:
- A form behaves oddly (field hidden, wrong value, message missing): start client side, or use Field Watcher to see which script touched the field.
- Data is wrong after saving: start server side, with Business Rules and Script Includes.
- A GlideAjax result is wrong: check both sides. The browser’s Network tab shows what came back, and the System Log shows what the server did.
- Something works for admin but not for a user: suspect ACLs and roles, and use the security debugging options described in Section 5.
Then follow a simple loop every time:
- Reproduce the problem reliably, with the same user, record and steps.
- Isolate the side (client or server) and the specific script.
- Form one hypothesis, such as “
callerIdarrives empty." - Verify it with a tool, not a guess.
- Fix one thing, then test again.
- Clean up any temporary logging.
3. Logging done right
Logging is the simplest debugging tool and the one most often misused.
3.1 Choose the right method
The key facts:
gs.debug(),gs.info(),gs.warn()andgs.error()work in both global and scoped applications. They are the methods to use going forward.gs.print()andgs.log()are older. Community guidance and the debugging best-practices material both note that they are not available in scoped applications, which is why messages written with them can seem to "disappear" there.- A single
gs.log()has a handy second argument, the source, which makes messages easier to find. With the modern methods you get the same benefit by putting a clear prefix in the message yourself.
3.2 Understand log levels
For scoped applications, the scoped logging API has four levels of verbosity, and each setting includes the less verbose levels. Setting a level to debug produces info, warn, error and debug messages, while setting it to error produces error messages only.
Scoped applications also get two system properties for controlling logging, named after the application scope: scope_name.logging.verbosity (error, warn, info, debug or trace) and scope_name.logging.destination (file system or database).
3.3 Write messages you can actually use
A good log line answers three questions: where am I, what happened, and with which data.
var SRC = '[IncidentContextAjax.getCallerContext]'; // easy to filter in the logtry {
gs.info(SRC + ' start, caller=' + callerId); var gr = new GlideRecordSecure('sys_user');
if (!gr.get(callerId)) {
gs.warn(SRC + ' caller not found or no access, caller=' + callerId);
return JSON.stringify({ success: false, error: 'CALLER_NOT_FOUND_OR_NO_ACCESS' });
}
// ... build the response ...
} catch (e) {
gs.error(SRC + ' failed: ' + e.message); // always log the cause
return JSON.stringify({ success: false, error: 'SERVER_ERROR' });
}
Notice the pattern from Part 4: the detail goes to the log, and only a short, safe error code goes to the browser.
3.4 Keep detailed logging behind a switch
The debugging best-practices guidance recommends controlling gs.debug() statements with a system property. Its reasoning is practical: debug messages written with gs.log() or alert() tend to get left in the code, and a property prevents them from filling the log.
// Create a system property such as my_app.debug.enabled (true/false)
var DEBUG = (gs.getProperty('my_app.debug.enabled') === 'true');if (DEBUG) {
gs.debug('[MyApp] payload=' + JSON.stringify(payload));
}Now you can switch detailed output on in production for ten minutes while you investigate, then switch it off again, with no code change.
3.5 Where to read the logs
Messages are stored in the Log table. You can open it from the System Logs module, or by typing syslog.list in the navigator. Filter by your source prefix so you only see your own messages.
4. Script Debugger: pause, step and inspect
Logging tells you what happened. The Script Debugger lets you stop time and look around.
According to ServiceNow’s documentation, users with the script_debugger role can debug server-side JavaScript. It can pause server-side scripts that run in an interactive transaction, such as Business Rules, Script Includes, script actions, and UI actions that need a response to proceed.
4.1 What you can do
- set and remove breakpoints, including conditional breakpoints that only pause when an expression is true
- set logpoints, which write a message at a line without stopping the script
- step over a function call (run it as one step), step into it (go to its first line), or step out of it (return to the caller)
- see the call stack, the current line, and the values of variables
- debug in a dedicated debug transaction that applies only to your session, so several developers can debug at once without interfering with each other
4.2 A worked example
Imagine the Part 4 form fills in location incorrectly. Here is a clean way to investigate:
- Open the
IncidentContextAjaxScript Include and open the Script Debugger. - Set a breakpoint on the line after
var callerId = this.getParameter('sysparm_caller_id'); - On the Incident form, change the Caller. The call reaches the server, and the debugger pauses at your breakpoint.
- Look at the variables. Is
callerIdempty, or the wrong sys_id? That already answers the first hypothesis. - Step over line by line. Watch where
gr.get(callerId)returnsfalse, or where a field has an unexpected value. - Remove the breakpoint when you are done.
4.3 Limits worth knowing
- The debugger is designed for interactive transactions. For work that does not run while a user waits, such as scheduled jobs, rely on logs.
- The Scripts — Background module does not support setting breakpoints directly, although you can debug script objects that it calls.
- If you do not have read access to a method, step into behaves like step over.
- If the debugger is not pausing for you, restart it. The documentation notes that it does not pause on breakpoints for the current user until it is restarted.
4.4 Session Log
Users with the log_debugger role can use the Session Log to view and download logs for Business Rules, Script Includes and certain custom UIs. By default it shows up to 100 transactions and 10,000 messages. You can enable it as a separate tab with the Script Debugger using the glide.debugger.log.ui system property. Its Page option displays logs under forms and lists as well as in the Session Log tab, while Session shows them only in the tab.
5. Session Debug: ask the platform to explain itself
Sometimes you do not need to step through code. You just want to know what the platform did. Session Debug turns on debug messages for your own session. Open it from System Diagnostics, then Session Debug.
Get Shubh Prakash Shukla’s stories in your inbox
Join Medium for free to get updates from this writer.
Useful options include:
- Debug Business Rule: shows messages about Business Rules that ran.
- Debug Data Policies: shows data policy activity (Part 6).
- Debug Security: shows access-control decisions, which is the fastest way to explain “no access.”
- Debug SQL: shows the database queries a transaction produced (Part 5).
- Debug Log: displays all log entries.
- Debug Metric Statistics: an aggregate view of performance data such as slow transactions, scripts, queries, events and mutexes.
- Debug Scopes: messages when script objects enter or exit application scopes.
Session debugging stays active during your session or until you disable it, so turn it off when you finish. To view the output, choose Enable All, then open Debug Log under Session Debug.
6. Script Tracer: find out which scripts actually ran
A common question is: “Which of my 30 Business Rules touched this record?” The Script Tracer answers it.
- It is available to users with the admin role.
- You start the tracer, perform a UI transaction, and it lists the scripts that ran and the changes they made.
- You can filter by file type and table.
- From the results you can open the script in the debugger (Debug Script) or in the platform for editing (View File).
- By default it traces up to 1,000 lines of script.
- It is aimed at synchronous server-side scripts that run as part of an application’s logic.
Use Script Tracer to find the script, then use the Script Debugger to step through it.
7. Client-side debugging
ServiceNow’s own documentation points developers to browser developer tools for client-side scripts, and those tools are the first thing to learn.
7.1 Browser developer tools
- Console: shows JavaScript errors and
console.log()output. A red error here often explains a client script that silently stopped. - Sources: set breakpoints in client scripts, or add a temporary
debugger;line to pause there when developer tools are open. - Network: shows every request the page made, including GlideAjax calls (Section 8).
function onChange(control, oldValue, newValue, isLoading) {
console.log('onChange', { oldValue: oldValue, newValue: newValue, isLoading: isLoading });
if (isLoading) { return; }
// debugger; // uncomment temporarily, then remove
}Always remove console.log() and debugger; before moving an update set to production.
7.2 Field Watcher
When a field has the wrong value and you do not know why, the Field Watcher is the right tool. It tracks and displays the actions the system performs on a selected form field. It can show when:
- a default value is set
- an ACL or dictionary setting changes access
- a data policy blocks a value
- a UI Policy changes the field to or from read-only, visible, mandatory or editable
- a Business Rule, assignment rule, workflow engine action, client script or the user changes the value
Client-side changes appear in blue text, and each entry names the type of object that made the change, such as CLIENT SCRIPT, BUSINESS RULE or ACL. Only one field can be watched at a time. Administrators can use it, and non-admin users with the impersonator role can too, which helps you reproduce what a specific user sees.
7.3 JavaScript Log and jslog()
The JavaScript debug window offers a JavaScript Log, where client code can call jslog(), and the Field Watcher. It requires the admin role, and ServiceNow notes that it is not supported with Next Experience. If your instance uses Next Experience, use browser developer tools instead.
8. Debugging GlideAjax step by step
GlideAjax crosses the client and the server, so it needs a two-sided check. This is the routine to follow whenever the answer is null, empty or wrong (Parts 3 and 4).
- Open browser developer tools and the Network tab, then trigger the action.
- Find the request. Look for the call whose URL includes your Script Include name (
sysparm_processor) and method name (sysparm_name). If it is missing, your client code never reached the call. Check the Console for an error. - Check the parameters. Confirm each
sysparm_value you sent is present and correct. - Read the response. The answer is carried in an
answerattribute of the response. If it is empty, the problem is on the server. - Switch to the server. Check the System Log for your
gs.error()message. If there is none, confirm the Script Include is Client callable, its Name matches the class name, and the method name is spelled exactly right. - If the answer exists but the form still fails, the problem is parsing. Log the raw answer, and use the
safeParse()helper from Part 4 so invalid JSON cannot crash your script. - Still unclear? Put a breakpoint inside the Script Include and trigger the call from the form (Section 4.2).
9. Performance debugging
A script can be correct and still slow. Two options help:
- Debug SQL and Debug Metric Statistics (Session Debug) show the queries and the slow transactions, scripts, queries, events and mutexes behind a page load.
- Your instance’s transaction logs also record request-level details, which can help you spot slow pages. Check how they are exposed on your release.
Typical finding: a GlideRecord query inside a loop, or a query with no filter. Both are covered in Part 5.
10. Pick the right tool fast
11. Common problems and fixes
12. Logging best practices
- Use
gs.info(),gs.warn(),gs.error()andgs.debug(). Leavegs.log()andgs.print()behind. - Add a source prefix to every message so you can filter the log.
- Always log the cause in
catchblocks. An emptycatchhides the very problem you will be hunting later. - Gate verbose output behind a system property, so you can turn it on only when you need it.
- Never log secrets. Do not write passwords, tokens or personal data. Log identifiers and codes instead.
- Log decisions, not noise. One line per important step is useful. A line per loop iteration floods the log.
- Clean up. Remove temporary
console.log(),debugger;and noisy statements before you ship. - Turn off Session Debug when you are finished.
13. Before and after: a script that fails silently
Before:
var gr = new GlideRecord('sys_user');
gr.get(callerId);
try {
// ... build the result ...
} catch (e) {
// ignore
}
return JSON.stringify(result);Problems: get() is not checked, so a missing user goes unnoticed, the empty catch swallows every error, and nothing is logged. The form simply does nothing, and nobody knows why.
After:
var SRC = '[CallerContext]';
try {
var gr = new GlideRecordSecure('sys_user');
if (!gr.get(callerId)) {
gs.warn(SRC + ' user not found or no access: ' + callerId);
return JSON.stringify({ success: false, error: 'CALLER_NOT_FOUND_OR_NO_ACCESS' });
}
// ... build the result ...
} catch (e) {
gs.error(SRC + ' failed: ' + e.message);
return JSON.stringify({ success: false, error: 'SERVER_ERROR' });
}Now every failure leaves a clear trail in the log, and the client receives a short code it can handle.
14. Final checklist
Before you debug
- [ ] I can reproduce the problem with a specific user, record and steps
- [ ] I know whether the problem is client side or server side
- [ ] I have one clear hypothesis to test
Logging
- [ ] I use
gs.info(),gs.warn(),gs.error()andgs.debug(), notgs.log()orgs.print() - [ ] Messages carry a source prefix and the relevant ids
- [ ] Every
catchblock logs the cause - [ ] Verbose logging sits behind a system property
- [ ] Nothing sensitive is logged
Tools
- [ ] Script Debugger for pausing and inspecting server scripts in interactive transactions
- [ ] Session Debug for Business Rule, security and SQL messages
- [ ] Script Tracer to find which scripts ran
- [ ] Field Watcher for “who changed this field?”
- [ ] Browser developer tools (Console, Sources, Network) for client scripts and GlideAjax
Cleanup
- [ ] Temporary
console.log(),debugger;and breakpoints removed - [ ] Session Debug turned off
- [ ] Debug property switched back off
15. Key takeaways
- Decide the side first. Client and server problems need different tools.
- Use the modern log methods (
gs.info,gs.warn,gs.error,gs.debug), which work in global and scoped apps. - Log the cause and the context, and keep detailed output behind a system property.
- Script Debugger pauses and inspects, Session Debug explains what the platform did, Script Tracer finds which scripts ran, and Field Watcher shows who changed a field.
- For GlideAjax, check both sides: the Network tab for the request and answer, and the System Log for the server.
- Clean up after yourself. Remove debug code and turn debugging off.
Sources
- ServiceNow Docs, Script Debugger and Session Log: https://www.servicenow.com/docs/r/api-reference/scripts/script-debugger.html
- ServiceNow Docs, Script Debugger and Session Log (Xanadu; roles, interactive transactions, Session Log defaults,
glide.debugger.log.ui): https://www.servicenow.com/docs/r/zZBaGIzc7EPkA0ySnMn5eg/eFpTaSOE7boTyKwIcHQ5wQ - ServiceNow Docs, Script Debugger user interface (breakpoints, call stack, step over, step into, step out): https://www.servicenow.com/docs/r/zZBaGIzc7EPkA0ySnMn5eg/B_GffaVuPGcho9wKHFgBBA
- ServiceNow Docs, Session debug (session debug options): https://www.servicenow.com/docs/r/api-reference/scripts/c_SessionDebug.html
- ServiceNow Docs, Script Tracer and debugging scripts: https://www.servicenow.com/docs/r/api-reference/scripts/script-tracer.html
- ServiceNow Docs, JavaScript debug window (JavaScript Log, Field Watcher, Next Experience note): https://www.servicenow.com/docs/r/xanadu/api-reference/scripts/c_JavaScriptDebugWindow.html
- ServiceNow Docs, Field watcher: https://docs.servicenow.com/bundle/utah-application-development/page/script/debugging/concept/c_FieldWatcher.html
- ServiceNow Community, ServiceNow Development Best Practices (debugging best practices,
gs.printandgs.login scoped apps): https://www.servicenow.com/community/developer-articles/servicenow-development-best-practices/ta-p/2312003 - Quality Clouds documentation, Debugging best practices (summary of the ServiceNow guidance on controlling debug output with system properties): https://docs.qualityclouds.com/qcd/debugging-best-practices-3997745.html
- ServiceNow Community, Why log messages are not displaying in system logs (scoped logging levels and properties): https://www.servicenow.com/community/new-customers-policy-risk-forum/why-logs-messages-are-not-displaying-in-system-logs-for/m-p/2580959
- ServiceNow Community, What is the difference between gs.print and gs.debug: https://www.servicenow.com/community/developer-forum/what-is-the-difference-between-gs-print-and-gs-debug-and-when-we/td-p/1466387
- ServiceNow Community, gs.log() vs gs.print() difference: https://www.servicenow.com/community/service-management-forum/gs-log-vs-gs-print-difference-which-is-best-and-why/td-p/405154
- ServiceNow Community, gs.log vs gs.print: syslog table: https://www.servicenow.com/community/developer-forum/gs-log-vs-gs-print-the-docs-entry-for-gs-log-says-it-logs-to-two/td-p/1615741
- DEV Community, ServiceNow: Script Tracer: https://dev.to/23thom/servicenow-script-tracer-2ei7
Next in the series: continue with the roadmap topic after this one.










