概要

項目内容
案件IDMAS-178
カテゴリ信頼性
PhaseP1
優先度★★
所要時間2-3時間
対象ファイル000_infra/004_utils.js(ログ基盤拡張)
600_report/602_datamart_main.js(タイムアウト対策)
500_import/502_receipt_reader.js(API制限対策)

目的

データ不整合、API制限、GAS実行時間超過(6分)に対するエラー通知とリカバリー機構を強化し、システムの安定稼働を確保する。

現状の課題

課題一覧

#課題重大度現状
1永続ログなしconsole.log のみ(7日で自動削除、検索不可)
2実行時間超過対策なし6分制限への監視・中断機構なし
3API制限対応不十分503リトライあり。429(Rate Limit)未対応、Exponential Backoff未実装
4ロールバック機構なしwriteDtosToSheet_ で clearContent → setValues 間に失敗するとデータ消失
5処理中の不整合検出が throw のみ科目未登録等で即例外。警告スキップ不可

既存のエラーハンドリングパターン

// 全17ファイルで共通パターン
try {
  // 処理
} catch (e) {
  Utils.logError(FUNC, e);                    // console.error のみ
  SpreadsheetApp.getUi().alert('🚨 ...', e.message, ...);  // ダイアログ
}

修正方針(3ステップ段階実装)

大規模なアーキテクチャ変更ではなく、既存パターンを維持しつつ段階的に強化する。

Step 1: 永続ログ基盤(Utils拡張)

004_utils.js に永続ログ記録機能を追加。専用シート 99_error_log にエラー・警告を記録する。

/**
 * エラーログをスプレッドシートに永続記録する
 * @param {string} level - 'INFO' | 'WARN' | 'ERROR'
 * @param {string} funcName
 * @param {string} message
 * @param {string} [detail] - スタックトレース等
 */
persistLog: function(level, funcName, message, detail) {
  try {
    var ss = getWebSpreadsheet_();
    var sheet = ss.getSheetByName('99_error_log');
    if (!sheet) {
      sheet = ss.insertSheet('99_error_log');
      sheet.getRange(1, 1, 1, 5).setValues([['日時', 'レベル', '関数名', 'メッセージ', '詳細']]);
      sheet.getRange(1, 1, 1, 5).setBackground('#434343').setFontColor('#FFFFFF').setFontWeight('bold');
      sheet.setFrozenRows(1);
    }
    sheet.appendRow([new Date(), level, funcName, message, detail || '']);
  } catch (e) {
    // ログ記録自体の失敗は握りつぶす(無限ループ防止)
    console.error('[PERSIST_LOG_FAIL] ' + e.message);
  }
}

既存の logError を拡張:

logError: function(funcName, error, context) {
  var msg = context ? context + ' - ' + error.message : error.message;
  console.error('[ERROR] ' + funcName + ': ' + msg);
  if (error.stack) console.error(error.stack);
  // S-02追加: 永続記録
  Utils.persistLog('ERROR', funcName, msg, error.stack || '');
}

Step 2: 実行時間監視(タイムアウトガード)

602_datamart_main.jsbuildBudgetTrendDataMart() に実行時間チェックを追加。

// 関数冒頭
var startTime = new Date().getTime();
var TIMEOUT_MS = 300000;  // 5分(6分制限の50秒前に警告)

// 各ステップ間にチェック
function checkTimeout_(stepName) {
  var elapsed = new Date().getTime() - startTime;
  if (elapsed > TIMEOUT_MS) {
    var msg = stepName + ' でタイムアウト警告(経過: ' + Math.round(elapsed/1000) + '秒)';
    Utils.persistLog('WARN', FUNC, msg);
    throw new Error('⏰ 実行時間が5分を超えました。処理を中断します。\n' + msg);
  }
  Utils.logInfo(FUNC, stepName + ' 完了 (' + Math.round(elapsed/1000) + '秒)');
}

// 使用例
dmIngestData_(ctx, sheetInv, sheetBank, sheetAcct);
checkTimeout_('ステップ1: データ取込');
dmProcessAllEvents_(ctx);
checkTimeout_('ステップ2: イベント処理');

Step 3: API制限対策(Exponential Backoff)

502_receipt_reader.jscallGeminiForReceipt_() にExponential Backoffと429対応を追加。

// 既存: 503のみ固定5秒待機
// 改善: 429/503に対応、待機時間を指数的に増加
var MAX_RETRIES = 4;
var baseWaitMs = 2000;  // 2秒
for (var retry = 0; retry < MAX_RETRIES; retry++) {
  response = UrlFetchApp.fetch(url, options);
  status = response.getResponseCode();
  if (status === 200) break;
  if ((status === 429 || status === 503) && retry < MAX_RETRIES - 1) {
    var waitMs = baseWaitMs * Math.pow(2, retry);  // 2s, 4s, 8s, 16s
    Utils.logInfo(FUNC, 'API ' + status + ' リトライ待機: ' + waitMs + 'ms');
    Utilities.sleep(waitMs);
    continue;
  }
  throw new Error('Gemini API エラー: HTTP ' + status);
}

ロールバック機構について

GASにはネイティブなトランザクションAPIがなく、完全なロールバックは困難。以下の軽量な保護策を推奨:

対策適用先内容
書き込み順序の最適化writeDtosToSheet_clearContent と setValues を可能な限り近接させ、間に重い処理を入れない(現状で対応済み)
冪等性の維持RPA関数isDuplicate_ による重複防止(MAS-076で修正済み)
エラーログによる手動復旧Step 199_error_log でどこまで処理が進んだか追跡可能にする

本格的なロールバック(処理前スナップショットの保存+復元)は GCP 移行(Phase 3-4)で対応。

影響範囲

Stepファイル変更量既存動作への影響
1004_utils.js約25行追加logError に永続記録を追加。既存の console 出力はそのまま
2602_datamart_main.js約15行追加5分超過時に例外で中断。通常は影響なし
3502_receipt_reader.js約10行変更リトライ回数増(3→4)、待機時間変更(固定5s→指数2-16s)

注意事項

  1. 99_error_log シートの肥大化: appendRow は高速だが、数千行を超えると遅延の可能性。月次で古いログを手動削除、またはMAS-212(物理削除アーカイブ)と連携
  2. persistLog の失敗: ログ記録自体が例外を投げないよう try-catch で保護。console.error にフォールバック
  3. checkTimeout_ の精度: new Date().getTime() ベースのため、GASのサーバー時計に依存。±数秒の誤差あり。50秒のマージンで対応
  4. Exponential Backoff の最大待機: 4回目のリトライで16秒待機。合計30秒のAPI待機が加わる可能性

関連ドキュメント

仕様書関連箇所
CLAUDE.mdclasp認証切れ時のエラー対応
MAS-179 監査証跡の強化99_error_log はMAS-179の簡易版として機能

人間が検討すべき事項

  • エラー通知先(メール or Slack)の選定: 本案件ではスプレッドシート内ログのみ。外部通知はMAS-184(通知連携)で対応
  • 99_error_log の保持期間とクリーンアップ方針

実装プロンプト(Claude Code 用)

Step 1: 永続ログ基盤

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 N-02 Step 1「永続ログ基盤」を実装してください。

## 実行前タスク

以下のファイルを読み込んでください:
1. `000_infra/004_utils.js` — Utils.logInfo, Utils.logError, Utils.toastResult の現在の実装(L232-257)
2. `CLAUDE.md`
3. `docs/dev/dev_mas-178_error_handling.md`

## 実装内容

### A: Utils.persistLog() の追加(004_utils.js)

Utils オブジェクトに `persistLog` メソッドを追加。99_error_log シートに [日時, レベル, 関数名, メッセージ, 詳細] を appendRow。シートが存在しなければ自動作成。

### B: Utils.logError() の拡張

既存の console.error 出力の後に `Utils.persistLog('ERROR', ...)` を呼び出し追加。

## 制約
- Utils.logInfo は変更しない(INFO レベルはログ量が多すぎるため永続記録しない)
- persistLog 内の例外は握りつぶす(無限ループ防止)

Step 2: 実行時間監視

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 N-02 Step 2「実行時間監視」を実装してください。

## 実行前タスク

以下のファイルを読み込んでください:
1. `600_report/602_datamart_main.js` — buildBudgetTrendDataMart() の全体構造(L157-348)。各ステップのログ出力箇所(L209-227)
2. `docs/dev/dev_mas-178_error_handling.md`

## 実装内容

buildBudgetTrendDataMart() の冒頭に startTime を記録。各ステップ間に checkTimeout_ を挿入。5分超過時は Utils.persistLog で記録後に throw。

## 制約
- 既存のステップログ(Utils.logInfo)はそのまま維持(checkTimeout_ 内に統合してもよい)
- タイムアウト閾値は 300000ms (5分) をハードコード。03_sys_params 化は後日

Step 3: API制限対策

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 N-02 Step 3「API制限対策」を実装してください。

## 実行前タスク

以下のファイルを読み込んでください:
1. `500_import/502_receipt_reader.js` — callGeminiForReceipt_() のリトライ処理(L213-226付近)
2. `docs/dev/dev_mas-178_error_handling.md`

## 実装内容

既存のリトライループを Exponential Backoff に置換:
- 最大リトライ回数: 4回
- 対応ステータス: 429, 503
- 待機時間: 2s → 4s → 8s → 16s
- Utils.logInfo でリトライ状況をログ出力

## 制約
- callGeminiForReceipt_ の関数シグネチャは変更しない
- 200以外のレスポンスの最終的な throw は維持

### 拡張思考の使用状況

| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| Step 1: 永続ログ | なし | appendRow + 自動シート作成のシンプルなパターン |
| Step 2: タイムアウト | なし | startTime + checkTimeout_ の単純な時間比較 |
| Step 3: Exponential Backoff | なし | Math.pow(2, retry) の定型パターン |

推奨実行モデル

工程推奨モデル理由
仕様書作成(本ドキュメント)Claude Opus 4.65つの課題の重大度評価、ロールバック可否の技術判断、段階的実装設計
Step 1-3 実装Claude Haiku 4.5各ステップとも定型パターンの実装。判断要素が少ない

変更履歴

日付変更内容
2026-04-14初版作成