How to write Google Apps Script logs into Google Sheets

Introduction

In Google Apps Script, the ability to track and record actions, errors, and performance metrics is crucial for both developers and users. However, the built-in logging mechanisms often fall short regarding accessibility and ease of use. This is where Local Google Apps Script Logging comes into play, offering a streamlined and integrated approach to capturing script activities.

Screenshot showing local Google Apps Script Logs

Watch the Video Tutorial

For a comprehensive visual guide, watch our video tutorial. If you prefer, you can also watch it directly on YouTube.

Why Local Google Apps Script Logs Matters

Local logging ensures that logs are easily accessible within a Google Sheet, providing a persistent and organized record of script activity. This is especially beneficial for scripts that are frequently executed or shared across teams, as it allows for immediate access to logs without navigating away from the workspace.

Introducing LocalLogger for Enhanced Logging

Overview of the Localogger Class

As seen in the Settings class implementation covered in a previous blog post, I love creating components that can be reused across different projects. This is the exact approach we followed here as well. Localogger is a custom class that extends the basic logging functionality in Google Apps Script. It allows developers to log messages directly into a Google Sheet, categorizing them by severity levels for better clarity. It includes a notification system sending email alerts in case of errors or critical issues.

Key Features

// Mimic an Enum for Severity Levels
const Severity = {
  INFO: 'INFO',
  WARNING: 'WARNING',
  ERROR: 'ERROR',
  DEBUG: 'DEBUG'
};

/**
 * Class representing a logger with functionality to log messages to a Google Spreadsheet.
 */
class LocalLogger {
/**
   * Creates a logger instance.
   * @param {string} emailAddress - The email address to send notifications to. (Leave blank to disable notifications)
   * @param {boolean} logAtTop - Determines if logs should be placed at the top of the spreadsheet.
   * @param {GoogleAppsScript.Spreadsheet.Sheet} [logSheet] - Optional. The sheet to log messages to.
   */
constructor(emailAddress = null, logAtTop = false, notifyLevel = Severity.ERROR, logSheet = null) {
  this.spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  this.logSheet = logSheet || this.spreadsheet.getSheetByName('Logs');
  this.logAtTop = logAtTop;
  this.notifyLevel = notifyLevel; // Notify when an ERROR or more severe log is added
  this.emailAddress = emailAddress; // Email address for sending notifications
}

/**
 * Initializes the logger by creating a Logs sheet if it does not exist.
 */
init() {
  if (!this.logSheet) {
    this.logSheet = this.spreadsheet.insertSheet('Logs');
    this.logSheet.appendRow(['Timestamp', 'Severity', 'Message', 'User/Session']);
    this.logSheet.getRange('1:1').setFontWeight('bold');
  }
}


  /**
   * Logs a message to the spreadsheet.
   * @param {string} message - The message to log.
   * @param {string} [severity=Severity.INFO] - The severity of the log message.
   */
  log(message, severity = Severity.INFO) {
    const userEmail = Session.getActiveUser().getEmail(); // May return empty in some contexts due to privacy
    const sessionID = Session.getTemporaryActiveUserKey(); // Alternative session identifier
    const userInfo = userEmail || sessionID;
    const timestamp = new Date();
    const formattedTimestamp = Utilities.formatDate(timestamp, Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss");
    const logEntry = [formattedTimestamp, severity, message, userInfo];

    try {
      this.writeLogEntry(logEntry);

      // Trigger notification if severity is high
      if (this.shouldNotify(severity)) {
        this.notify(message, severity);
      }
    } catch (e) {
      // Handle the error, e.g., log to a different place or send an email
      Logger.log('Failed to log message: ' + e.toString());
    }
  }

/**
 * Writes a single log entry to the spreadsheet.
 * @param {Array} entry - The log entry to write.
 */
writeLogEntry(entry) {
  let range;
  if (this.logAtTop) {
    // Insert a new row after the headers for the new log entry
    this.logSheet.insertRowAfter(1);
    range = this.logSheet.getRange(2, 1, 1, 4); // Now the new entry will be on the second row
  } else {
    // Append at the bottom of the log sheet
    const lastRow = this.logSheet.getLastRow();
    range = this.logSheet.getRange(lastRow + 1, 1, 1, 4);
  }
  range.setValues([entry]);
  range.setFontWeight('normal');
  this.applyLogColor(range, entry[1]); // Apply color based on severity
}

 /**
   * Sends a notification for a log entry.
   * @param {string} message - The log message.
   * @param {string} severity - The severity of the log message.
   */
  notify(message, severity) {
    
    if(!this.emailAddress) {
      Logger.log('No email address specified for notifications');
      return;
    }

    try {
      const subject = `New ${severity} log entry`;
      const body = `A new log entry with severity ${severity} was added: \n ${message}`;
      MailApp.sendEmail(this.emailAddress, subject, body); // Use the stored email address
    } catch (e) {
      Logger.log('Failed to send notification: ' + e.toString());
    }
  }

  /**
   * Determines if a log entry should trigger a notification based on its severity.
   * @param {string} severity - The severity of the log entry.
   * @return {boolean} True if notification should be sent, false otherwise.
   */
  shouldNotify(severity) {
    const severityOrder = [Severity.DEBUG, Severity.INFO, Severity.WARNING, Severity.ERROR];
    return severityOrder.indexOf(severity) >= severityOrder.indexOf(this.notifyLevel);
  }

  /**
 * Applies background color to a log entry based on its severity.
 * @param {GoogleAppsScript.Spreadsheet.Range} range - The range to apply the background color to.
 * @param {string} severity - The severity of the log entry.
 */
applyLogColor(range, severity) {
  let color = "#FFFFFF"; // Default white background
  switch (severity) {
    case Severity.INFO:
      color = "#D9EAD3"; // Light green
      break;
    case Severity.WARNING:
      color = "#FFE599"; // Light yellow
      break;
    case Severity.ERROR:
      color = "#F4CCCC"; // Light red
      break;
    case Severity.DEBUG:
      color = "#CFE2F3"; // Light blue
      break;
  }
  range.setBackground(color);
}

}

Setting Up LocalLogger

In order to test LocalLogger yourself:

  1. Create a new Google Sheet
  2. Go to the Apps Script Editor (Extensions –> Google Apps Script Editor.
  3. Create a new file (called LocalLogger for example) and paste the LocalLogger Implementation
  4. Now in your main Code file, paste the code snippet below.

As you can see below, we have LocalLogger’s instantiation and initiation.

Instantiation Upon instantiation, we define:

Initialization Within the onOpen() function of the Sheet, we initialize – init() function – our Logger. During initialization, the logger checks for an existing ‘Logs’ sheet and creates one if necessary, complete with headers for ‘Timestamp‘, ‘Severity‘, ‘Message‘, and ‘User/Session‘.

The rest of the snippet shows the different ways to create a log message entry with different severity levels.

const notificationEmailAddress = "apptiva.projects@gmail.com";
const showNewestMessageFirst = true;

const logger = new LocalLogger(notificationEmailAddress, showNewestMessageFirst);

function onOpen() {  
  logger.init();
  let ui = SpreadsheetApp.getUi();
  ui.createMenu('Logging')
  .addItem("Generate Debug Message", 'generateDebugMessage')
    .addItem("Generate Info Message", 'generateInfoMessage')
    .addItem("Generate Warning Message", 'generateWarningMessage')
    .addItem("Generate Error Message", 'generateErrorMessage')
    .addToUi();
}

function generateDebugMessage(){
  logger.log('This is a DEBUG Message', Severity.DEBUG);
}

function generateInfoMessage(){
  logger.log('This is an INFO Message', Severity.INFO);
}

function generateErrorMessage(){
  logger.log('This is an ERROR Message', Severity.ERROR);
}

function generateWarningMessage(){
  logger.log('This is a WARNING Message', Severity.WARNING);
}

function generateWarningMessage(){
  logger.log('This is a WARNING Message', Severity.WARNING);
}

Local Google Apps Script Logs – Conclusion

The enhanced LocalLogger class allows for sophisticated Google Apps Script logs within Google Sheets, complete with severity levels and notification capabilities. By leveraging these features, developers can maintain a high level of awareness and control over their scripts, leading to more reliable and efficient automation.

Talk to us

Take your business, project or idea to the next level!

Book a Free Discovery Call

Join the list of companies we are proud to have worked with

  • Wynter
  • Insurancemarket
  • MindWave
  • Kleemann