Basics
Execution log, Logger.log, and debugging
Most Apps Script debugging is printing a value with Logger.log or console.log and reading the error line. This page shows where the output goes and how to trace a failure.
1 of 8
Logger.log and console.log
Logger.log(value) prints one line. It is the old Apps Script way and it still works everywhere. console.log(value) prints the same kind of line and is the way you have probably seen in other JavaScript. On the current runtime, called V8, both are fine and both show up in the same place.
console.log has two extras. It accepts an object or an array directly and shows the contents, while Logger.log turns most things into text first. And it has companions that mark how serious a line is: console.info, console.warn, and console.error. Those severities show up as labels in the Executions history, which makes a real problem easy to pick out of a long run.
Pick one style and stay with it inside a project. Mixed logging is harder to scan than either style on its own.
2 of 8
The panel at the bottom and the Executions page
When you click Run, the Execution log panel opens under your code and fills in as the function goes. It covers this run only, and it is emptied when you run again. It is where you look while you are working.
The Executions page is the history. Click the icon that looks like a list with a clock in the left sidebar. Every run of this project is there, including runs started by triggers while you were asleep, with the function name, the start time, the duration, and a status of Completed, Failed, or Timed out.
Click any row to open the log lines from that run. This is the only way to see what a trigger did, because a trigger run never opens a panel in your editor.
- A run that reached the six minute limit, which is the cap at the time of writing, shows as Timed out, and the log stops mid way.
- Failed rows show the error message next to the row.
- Filter by function name or by status when the list is long.
3 of 8
Read the error line before changing anything
A failed run prints something like: TypeError: Cannot read properties of null (reading "getRange") at updateStatus(Code:12:5). Read it in two halves.
The first half is what went wrong. The second half is where. "at updateStatus" is the function that was running, "Code" is the file Code.gs, "12" is the line number, and "5" is the character on that line. Open the file, go to line 12, and look at that exact line.
When several lines are listed, the top one is where it stopped and the ones below show who called it. Start at the top. The word null in that message points to the usual cause: a sheet, a folder, or a label that was not found, so it came back as null and then you asked it to do something.
4 of 8
Print the value before and after
The fastest way to find a wrong result is to print the value on both sides of the line that changes it. One log before, one log after, then run and compare.
Label every log line with something you can search for, for example "row 5 status before" rather than a bare value. When twenty lines scroll past, unlabelled output is hard to match to the line that printed it.
Print the type as well when a value surprises you. Logger.log(typeof cellValue) identifies primitives such as numbers and strings, but every Date is an object. Check Logger.log(cellValue instanceof Date) for a Date and Array.isArray(cellValue) for an array when a date comparison does not come out true.
5 of 8
Use JSON.stringify to see arrays and objects
A range read with getValues() is a 2D array, meaning an array of rows where each row is an array of cells. Logging it plainly gives you a shape that is hard to read, and an object often prints as the unhelpful text [object Object].
JSON.stringify(value) turns either one into a readable string with the brackets and commas in place, so you can count the rows and see which cells are empty.
function logSheetValues() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const values = sheet.getDataRange().getValues();
Logger.log('Rows read: ' + values.length + '. Columns: ' + values[0].length + '.');
Logger.log('First row: ' + JSON.stringify(values[0]));
Logger.log('All rows: ' + JSON.stringify(values));
} 6 of 8
Catch an error, record it, and pass it on
try and catch let you say what to do when a line fails. Put the risky work in the try block, and the catch block runs with the error instead of the script stopping straight away.
Write a message you will understand later, then use throw error to pass the problem on. Rethrowing matters. A run that catches an error and does not rethrow it is marked Completed, so a broken trigger looks healthy in the Executions list and nobody is told.
Notice the check for a missing tab as well. Stopping with a clear message of your own is easier to read than a null error five lines later.
function updateStatusWithLogging() {
try {
writeStatusToFirstRow();
Logger.log('writeStatusToFirstRow finished.');
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
Logger.log('writeStatusToFirstRow failed: ' + message);
throw error;
}
}
function writeStatusToFirstRow() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Customers');
if (!sheet) {
throw new Error('This spreadsheet has no tab named Customers.');
}
sheet.getRange('B2').setValue('Checked');
} 7 of 8
Failure emails go to the trigger owner
When a trigger run fails, Google emails a summary to the account that created the trigger. It names the project, the function, and the error, and links to the Executions page.
So if a scheduled job stopped working and nobody noticed, check the inbox of the person who installed the trigger, not your own. If a colleague set it up, the mail is sitting with them.
Google collects failures into these emails, so one can arrive some time after the failure. Treat them as a signal that something has been failing, and go to the Executions page for the detail.
8 of 8
Test on one row before you loop
Before a loop touches two hundred rows, run the same code on row two only and read the log. A mistake then affects one row that you can check by eye.
The same idea applies to anything that leaves the script. Send the first email to your own address. Write the first result into a spare tab. Log what you are about to delete for one run before you let the delete line do anything.
Widen the range only when the small version is right.
Remember
The short version
- Logger.log and console.log both print to the same place, and console.log also handles objects and severity levels.
- The Execution log panel covers the current run, and the Executions page keeps the history of every run including triggers.
- An error line names the reason, the function, the file, and the line number, so read it before changing code.
- JSON.stringify makes a 2D array or an object readable in the log.
- Catch an error, log a clear message, then rethrow it so a failed run is not recorded as Completed.
Keep going
Put it to work
- Task guide Send an email for each row in a Google Sheet
Send one email to each person listed on a Customers tab. The script marks each row Sent as it goes, so a later run sends only to rows that are not marked yet.
- Task guide Run an Apps Script function every day
Make a function run on its own once a day. Run the setup function once, and from then on Apps Script calls the reminder function each day around the hour you chose.
- Task guide Copy unique rows to a clean tab
Remove duplicate people from a contact list. The script keeps the first row for each email address and writes those rows to a tab named Clean, leaving the original list as it is.
- Basics Run your first Apps Script function
Your first script, from opening the editor to reading its output. You replace the starter function, save, click Run, and read what Logger.log printed in the Execution log.
- Basics Common Apps Script errors and what they mean
Apps Script error messages say what failed, for example TypeError: Cannot read properties of null (reading 'getRange') or ReferenceError. Find the exact message below for its cause and fix.
- Basics Google Apps Script triggers explained
Google Apps Script triggers run functions automatically when a file opens, a cell changes, a form is submitted, or a scheduled time arrives. Use a simple onEdit or onOpen for work in the bound file. Choose an installable trigger for a schedule, form submission, or work that needs authorization.