You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Google Sheets脚本超时:如何捕获并提前记录排查信息?

解决Google Apps Script超时终止前保存性能日志的方案

问题背景

我开发了一个基于TypeScript、用Visual Studio Community编写、通过CLASP发布的大型Google Sheets应用,部分代码常因运行过慢触发脚本终止,正在排查超时原因。试过三种方案但都有局限:

  • 写入日志并在Executions中查看:仅能知道超时位置,无法定位具体慢代码
  • 自定义Timer类记录函数执行时间并实时写入TIMING_LOG工作表:小测试有效,但实时写入的开销太高
  • 将日志存入数组,操作完成后批量写入工作表:效果不错,但脚本超时终止时,未写入的日志会丢失

想要有类似GoogleAppScript.addEventListener("terminating", handleTermination)的机制,能在脚本终止前触发逻辑保存日志。

可行替代方案

1. 用PropertiesService临时缓存日志

把性能日志同时存在数组和PropertiesService.getScriptProperties()里,每次记录日志就更新缓存。脚本正常结束时批量写工作表并清空缓存;如果超时终止,下次运行脚本先检查缓存里的未保存日志,优先写入再执行新逻辑。

TypeScript示例代码:

const TIMING_LOG_SHEET_NAME = "TIMING_LOG";
const PROPERTY_LOG_KEY = "UNSAVED_TIMING_LOGS";

class Timer {
  private startTime: number;
  private functionName: string;

  constructor(funcName: string) {
    this.functionName = funcName;
    this.startTime = Date.now();
  }

  end() {
    const duration = Date.now() - this.startTime;
    const logEntry = `${new Date().toISOString()},${this.functionName},${duration}ms`;
    
    // 同步更新数组和Properties缓存
    const logs = JSON.parse(PropertiesService.getScriptProperties().getProperty(PROPERTY_LOG_KEY) || "[]");
    logs.push(logEntry);
    PropertiesService.getScriptProperties().setProperty(PROPERTY_LOG_KEY, JSON.stringify(logs));
  }
}

// 脚本启动先检查并保存上次未完成的日志
function init() {
  const unsavedLogs = JSON.parse(PropertiesService.getScriptProperties().getProperty(PROPERTY_LOG_KEY) || "[]");
  if (unsavedLogs.length === 0) return;

  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TIMING_LOG_SHEET_NAME);
  if (!sheet) return;

  const logRows = unsavedLogs.map(log => log.split(","));
  sheet.getRange(sheet.getLastRow() + 1, 1, logRows.length, 3).setValues(logRows);
  PropertiesService.getScriptProperties().deleteProperty(PROPERTY_LOG_KEY);
}

// 主函数示例
function main() {
  init();
  
  const timer1 = new Timer("fetchData");
  fetchData(); // 你的耗时操作
  timer1.end();

  const timer2 = new Timer("processData");
  processData(); // 你的耗时操作
  timer2.end();

  // 正常结束时主动保存日志
  saveLogsToSheet();
}

function saveLogsToSheet() {
  const unsavedLogs = JSON.parse(PropertiesService.getScriptProperties().getProperty(PROPERTY_LOG_KEY) || "[]");
  if (unsavedLogs.length === 0) return;
  
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(TIMING_LOG_SHEET_NAME);
  if (!sheet) return;
  
  const logRows = unsavedLogs.map(log => log.split(","));
  sheet.getRange(sheet.getLastRow() + 1, 1, logRows.length, 3).setValues(logRows);
  PropertiesService.getScriptProperties().deleteProperty(PROPERTY_LOG_KEY);
}

2. 拆分长任务,分段存日志

把大型任务拆成多个小函数,每个小任务完成后就批量写入该段的日志。比如把数据处理拆成fetchBatch1、processBatch1、fetchBatch2、processBatch2等,每完成一个批次就写对应日志,哪怕后续批次超时,前面的日志也已经保存下来了。

3. 结合console.time()和Cloud Logging

虽然Executions面板只能看到超时位置,但Cloud Logging(原Stackdriver)能看更详细的日志时间线。在每个关键函数前后加:

console.time("fetchData");
fetchData();
console.timeEnd("fetchData");

然后去Cloud Logging里筛选日志,看每个console.timeEnd的输出,就能定位耗时最长的函数。这种方式不用写工作表,开销极低,而且就算脚本超时,已经输出的日志会保留在Cloud Logging里。

说明

Google Apps Script目前没有官方的terminating事件监听器——脚本超时是强制终止,没法触发自定义回调。上面的方案都是用现有服务的替代办法,其中PropertiesService缓存+Cloud Logging的组合能较好平衡日志完整性和性能开销。

内容的提问来源于stack exchange,提问作者Todd Powers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 21:22:22