Google Sheets脚本超时:如何捕获并提前记录排查信息?
问题背景
我开发了一个基于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

