MENU

GASデバッグ完全ガイド|初心者がつまずくポイントと解決テクニック

GASデバッグ完全ガイド|初心者がつまずくポイントと解決テクニック

GAS開発では、エラーを自力で解決するデバッグスキルが必須です。この記事では、ログ出力からブレークポイント、エラーハンドリングまで、実践的なデバッグテクニックを体系的に解説します。


目次

デバッグの基本: ログ出力

デバッグの第一歩は「変数の中身を確認すること」です。GASには2つのログ出力方法があります。

Logger.log() vs console.log() の違い

項目 Logger.log() console.log()
出力先 Apps Script ログ(旧エディタ) 実行ログ(新エディタ)
リアルタイム表示 不可(実行後に確認) 可能
トリガー実行時 確認しにくい Stackdriver Loggingで確認可
推奨度 旧式 推奨

結論: console.log() を使いましょう。

console.log() の基本的な使い方


function debugBasic() {
  // 文字列を出力
  console.log('処理を開始します');

  // 変数の値を確認
  const userName = 'テスト太郎';
  console.log('ユーザー名: ' + userName);

  // 複数の値を出力(カンマ区切り)
  const age = 30;
  console.log('名前:', userName, '年齢:', age);

  // テンプレートリテラル(バッククォート)で見やすく
  console.log(`名前: ${userName}, 年齢: ${age}`);
}

実行結果:


処理を開始します
ユーザー名: テスト太郎
名前: テスト太郎 年齢: 30
名前: テスト太郎, 年齢: 30

オブジェクトと配列のデバッグ

配列やオブジェクトの中身を確認するときは、JSON.stringify() を使います。


function debugObjects() {
  // 配列の中身を確認
  const fruits = ['りんご', 'バナナ', 'オレンジ'];
  console.log('配列そのまま:', fruits);  // りんご,バナナ,オレンジ
  console.log('JSON形式:', JSON.stringify(fruits));  // ["りんご","バナナ","オレンジ"]

  // オブジェクトの中身を確認
  const user = {
    name: '山田太郎',
    email: 'yamada@example.com',
    age: 25
  };
  console.log('オブジェクトそのまま:', user);  // [object Object]
  console.log('JSON形式:', JSON.stringify(user));  // {"name":"山田太郎",...}

  // 見やすく整形(インデント付き)
  console.log('整形JSON:', JSON.stringify(user, null, 2));
  /*
  {
    "name": "山田太郎",
    "email": "yamada@example.com",
    "age": 25
  }
  */
}

スプレッドシートデータのデバッグ


function debugSpreadsheetData() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const data = sheet.getDataRange().getValues();

  // データ全体の構造を確認
  console.log('行数:', data.length);
  console.log('列数:', data[0].length);

  // 先頭5行だけ確認(大量データの場合)
  console.log('先頭5行:');
  for (let i = 0; i < Math.min(5, data.length); i++) {
    console.log(`  行${i + 1}: ${JSON.stringify(data[i])}`);
  }

  // 特定のセルを確認
  console.log('A1セル:', data[0][0]);
  console.log('B2セル:', data[1][1]);
}

実行ログの見方

ログの確認方法

  • スクリプトエディタで関数を実行
  • 画面下部の「実行ログ」パネルを確認
  • パネルが見えない場合は「表示」→「実行ログ」

ログレベルの使い分け


function logLevels() {
  // 通常のログ(情報)
  console.log('通常のログメッセージ');

  // 情報(console.logと同等)
  console.info('情報レベルのログ');

  // 警告(黄色で表示)
  console.warn('警告: 処理は続行しますが注意が必要です');

  // エラー(赤色で表示)
  console.error('エラー: 処理を中断しました');
}

使い分けの目安:

レベル 用途
console.log() 通常の情報 処理開始、変数の値確認
console.info() 重要な情報 処理完了、成功メッセージ
console.warn() 警告 非推奨機能の使用、想定外の値
console.error() エラー 処理失敗、例外発生

トリガー実行時のログ確認

トリガーで自動実行された処理のログは、通常のエディタからは見えません。

確認方法:

  • GASエディタで「実行」→実行数をクリック
  • または Google Cloud Console で確認
  • 「Stackdriver Logging」→ログで検索

ブレークポイントの使い方

ブレークポイントを使うと、コードの実行を一時停止して、その時点の変数の値を詳しく確認できます。

ブレークポイントの設定

  • 停止したい行の行番号の左側をクリック
  • 赤い丸(●)が表示される
  • 「デバッグ」ボタン(虫のアイコン)をクリック
  • ブレークポイントで実行が停止

デバッガーの操作方法

ボタン 操作 説明
▶️ 再開 次のブレークポイントまで実行
⏭️ ステップオーバー 1行実行(関数内には入らない)
⬇️ ステップイン 1行実行(関数内に入る)
⬆️ ステップアウト 現在の関数を抜ける
⏹️ 停止 デバッグを終了

デバッグ中に確認できること


function debugWithBreakpoint() {
  const data = [
    { name: '商品A', price: 1000 },
    { name: '商品B', price: 2000 },
    { name: '商品C', price: 3000 }
  ];

  let total = 0;

  for (let i = 0; i < data.length; i++) {
    const item = data[i];  // ← ここにブレークポイント
    total += item.price;
  }

  console.log('合計:', total);
}

ブレークポイントで確認できる情報:

  • i の現在値(0, 1, 2…)
  • item の中身({ name: '商品A', price: 1000 }
  • total の途中経過(0, 1000, 3000…)
  • data 配列の全内容

ブレークポイントの条件設定

特定の条件でのみ停止させることもできます。

  • ブレークポイントを右クリック
  • 「条件付きブレークポイント」を選択
  • 条件式を入力(例: i === 5

try-catch によるエラーハンドリング

基本構文


function tryCatchBasic() {
  try {
    // エラーが起きる可能性のある処理
    const result = riskyOperation();
    console.log('成功:', result);
  } catch (error) {
    // エラー発生時の処理
    console.error('エラー発生:', error.message);
  } finally {
    // 成功・失敗に関わらず必ず実行
    console.log('処理終了');
  }
}

エラーオブジェクトの中身


function errorDetails() {
  try {
    // 意図的にエラーを発生させる
    const obj = null;
    obj.method();  // TypeError
  } catch (error) {
    // エラーの詳細情報
    console.log('エラー名:', error.name);        // TypeError
    console.log('メッセージ:', error.message);   // Cannot read properties of null
    console.log('スタック:', error.stack);       // エラー発生箇所の詳細
  }
}

try-catch パターン集

パターン1: API呼び出しのエラーハンドリング


/**
 * 外部API呼び出し(リトライ付き)
 */
function callAPIWithRetry(url, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      console.log(`API呼び出し(${attempt}回目)`);

      const response = UrlFetchApp.fetch(url, {
        muteHttpExceptions: true
      });

      const code = response.getResponseCode();

      if (code === 200) {
        return JSON.parse(response.getContentText());
      } else if (code === 429) {
        // レート制限: 待機してリトライ
        console.warn('レート制限。30秒待機します...');
        Utilities.sleep(30000);
        continue;
      } else {
        throw new Error(`HTTPエラー: ${code}`);
      }

    } catch (error) {
      console.error(`${attempt}回目失敗: ${error.message}`);

      if (attempt === maxRetries) {
        throw new Error(`${maxRetries}回リトライしましたが失敗しました: ${error.message}`);
      }

      // 次のリトライまで待機
      Utilities.sleep(5000);
    }
  }
}

パターン2: スプレッドシート操作のエラーハンドリング


/**
 * スプレッドシートを安全に開く
 */
function safeOpenSpreadsheet(spreadsheetId) {
  try {
    const ss = SpreadsheetApp.openById(spreadsheetId);
    console.log('スプレッドシートを開きました:', ss.getName());
    return ss;

  } catch (error) {
    if (error.message.includes('not found') || error.message.includes('missing')) {
      console.error('スプレッドシートが見つかりません。削除された可能性があります。');
      console.error('ID:', spreadsheetId);
    } else if (error.message.includes('permission')) {
      console.error('アクセス権限がありません。共有設定を確認してください。');
    } else {
      console.error('予期しないエラー:', error.message);
    }
    return null;
  }
}

/**
 * シートを安全に取得
 */
function safeGetSheet(spreadsheet, sheetName) {
  if (!spreadsheet) {
    console.error('スプレッドシートがnullです');
    return null;
  }

  const sheet = spreadsheet.getSheetByName(sheetName);

  if (!sheet) {
    console.error(`シート「${sheetName}」が見つかりません`);
    console.log('存在するシート:', spreadsheet.getSheets().map(s => s.getName()));
    return null;
  }

  return sheet;
}

パターン3: 一括処理のエラーハンドリング(スキップ方式)


/**
 * 複数行を処理(エラーが起きてもスキップして続行)
 */
function processBatchWithSkip() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const data = sheet.getDataRange().getValues();

  let successCount = 0;
  let errorCount = 0;
  const errors = [];

  for (let i = 1; i < data.length; i++) {
    try {
      const row = data[i];

      // 各行の処理
      processRow(row, i + 1);

      successCount++;

    } catch (error) {
      errorCount++;
      errors.push({
        row: i + 1,
        message: error.message
      });
      console.warn(`行${i + 1}でエラー: ${error.message}(スキップして続行)`);
      // continueで次の行へ
    }
  }

  // 処理結果サマリー
  console.log('=== 処理完了 ===');
  console.log(`成功: ${successCount}件`);
  console.log(`失敗: ${errorCount}件`);

  if (errors.length > 0) {
    console.log('エラー詳細:');
    errors.forEach(e => console.log(`  行${e.row}: ${e.message}`));
  }

  return { successCount, errorCount, errors };
}

パターン4: エラー通知付きテンプレート


/**
 * 本番運用向け: エラー通知機能付きメイン処理
 */
function mainWithNotification() {
  const startTime = new Date();
  const functionName = 'mainWithNotification';

  console.log(`[${functionName}] 処理開始: ${startTime.toLocaleString()}`);

  try {
    // ===== メイン処理 =====
    const result = doMainProcess();

    // 成功時のログ
    console.log(`[${functionName}] 処理成功:`, result);

  } catch (error) {
    // エラー情報を構造化
    const errorInfo = {
      functionName: functionName,
      message: error.message,
      stack: error.stack,
      timestamp: new Date().toISOString()
    };

    // ログに詳細出力
    console.error(`[${functionName}] エラー発生:`, JSON.stringify(errorInfo, null, 2));

    // メールで通知
    sendErrorNotificationEmail(errorInfo);

    // 必要に応じてエラーを再スロー
    // throw error;

  } finally {
    const endTime = new Date();
    const duration = (endTime - startTime) / 1000;
    console.log(`[${functionName}] 実行時間: ${duration}秒`);
  }
}

/**
 * エラー通知メール送信
 */
function sendErrorNotificationEmail(errorInfo) {
  const recipient = Session.getActiveUser().getEmail();
  const subject = `【GASエラー】${errorInfo.functionName} でエラーが発生しました`;

  const body = `
GASスクリプトでエラーが発生しました。

■ 関数名
${errorInfo.functionName}

■ 発生日時
${errorInfo.timestamp}

■ エラーメッセージ
${errorInfo.message}

■ スタックトレース
${errorInfo.stack}

---
このメールは自動送信されています。
  `.trim();

  try {
    GmailApp.sendEmail(recipient, subject, body);
    console.log('エラー通知メールを送信しました');
  } catch (e) {
    console.error('エラー通知メールの送信に失敗:', e.message);
  }
}

権限エラーの対処法

よくある権限エラー


Exception: You do not have permission to call SpreadsheetApp.openById

対処法1: 権限の再承認

  • スクリプトエディタで関数を手動実行
  • 「承認が必要です」ダイアログで「許可を確認」
  • Googleアカウントを選択
  • 「詳細」→「(プロジェクト名)に移動」
  • 許可をクリック

対処法2: マニフェストで明示的に指定

appsscript.json で必要な権限を明示します。


{
  "timeZone": "Asia/Tokyo",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/gmail.send",
    "https://www.googleapis.com/auth/calendar",
    "https://www.googleapis.com/auth/drive"
  ]
}

主要なスコープ一覧:

サービス スコープ
スプレッドシート https://www.googleapis.com/auth/spreadsheets
Gmail送信 https://www.googleapis.com/auth/gmail.send
カレンダー https://www.googleapis.com/auth/calendar
ドライブ https://www.googleapis.com/auth/drive
外部API https://www.googleapis.com/auth/script.external_request

対処法3: トリガーの権限確認

トリガー実行時の権限エラーは、トリガーを削除して再作成すると解決することがあります。

  • 「トリガー」ページを開く
  • 該当トリガーの「︙」→「削除」
  • 新しくトリガーを作成

API制限エラーの対処法

よくあるエラー


Exception: Rate Limit Exceeded
Exception: Quota exceeded: Email Recipients per day

GASの主な制限値

サービス 無料アカウント Google Workspace
スクリプト実行時間 6分/回 30分/回
トリガー合計実行時間 90分/日 6時間/日
メール送信 100件/日 1,500件/日
UrlFetchApp 20,000回/日 100,000回/日

対処法1: 待機時間を入れる


function processWithDelay() {
  const items = getItems();

  for (let i = 0; i < items.length; i++) {
    processItem(items[i]);

    // 100件ごとに5秒待機
    if ((i + 1) % 100 === 0) {
      console.log(`${i + 1}件処理完了。5秒待機...`);
      Utilities.sleep(5000);
    }
  }
}

対処法2: クォータ確認


function checkQuotas() {
  // メール送信可能数
  const emailQuota = MailApp.getRemainingDailyQuota();
  console.log(`メール送信可能数: ${emailQuota}件`);

  if (emailQuota < 10) {
    console.warn('クォータ残りわずか!');
  }
}

対処法3: 処理を分割(バッチ処理)


/**
 * 大量データを分割して処理
 */
function batchProcess() {
  const props = PropertiesService.getScriptProperties();
  const startIndex = parseInt(props.getProperty('batchIndex')) || 0;

  const allData = getAllData();
  const batchSize = 100;
  const endIndex = Math.min(startIndex + batchSize, allData.length);

  console.log(`処理中: ${startIndex + 1} 〜 ${endIndex} / ${allData.length}`);

  for (let i = startIndex; i < endIndex; i++) {
    processItem(allData[i]);
  }

  if (endIndex < allData.length) {
    // 次のバッチを設定
    props.setProperty('batchIndex', endIndex.toString());
    console.log('次のバッチを1分後に実行します');

    ScriptApp.newTrigger('batchProcess')
      .timeBased()
      .after(60 * 1000)
      .create();
  } else {
    // 完了
    props.deleteProperty('batchIndex');
    console.log('全件処理完了!');

    // 一時トリガーを削除
    deleteOldTriggers('batchProcess');
  }
}

function deleteOldTriggers(functionName) {
  ScriptApp.getProjectTriggers().forEach(trigger => {
    if (trigger.getHandlerFunction() === functionName) {
      ScriptApp.deleteTrigger(trigger);
    }
  });
}

デバッグ用コードテンプレート集

テンプレート1: 処理時間計測


function measureExecutionTime() {
  const startTime = Date.now();

  // === 計測したい処理 ===
  doSomething();
  // ======================

  const endTime = Date.now();
  const duration = (endTime - startTime) / 1000;
  console.log(`実行時間: ${duration}秒`);
}

テンプレート2: 進捗表示


function showProgress() {
  const total = 1000;

  for (let i = 0; i < total; i++) {
    // 処理

    // 10%ごとに進捗表示
    if ((i + 1) % (total / 10) === 0) {
      const progress = Math.round(((i + 1) / total) * 100);
      console.log(`進捗: ${progress}% (${i + 1}/${total})`);
    }
  }
}

テンプレート3: デバッグモード切り替え


const DEBUG_MODE = true;  // 本番時は false に変更

function debugLog(message, data = null) {
  if (!DEBUG_MODE) return;

  if (data !== null) {
    console.log(`[DEBUG] ${message}:`, JSON.stringify(data, null, 2));
  } else {
    console.log(`[DEBUG] ${message}`);
  }
}

function myFunction() {
  debugLog('処理開始');

  const data = fetchData();
  debugLog('取得データ', data);

  const result = processData(data);
  debugLog('処理結果', result);
}

テンプレート4: 変数ダンプ


/**
 * 変数の中身を詳しく出力
 */
function dump(label, value) {
  console.log(`===== ${label} =====`);
  console.log('型:', typeof value);
  console.log('値:', JSON.stringify(value, null, 2));

  if (Array.isArray(value)) {
    console.log('配列の長さ:', value.length);
  } else if (typeof value === 'object' && value !== null) {
    console.log('キー:', Object.keys(value));
  }

  console.log('='.repeat(label.length + 12));
}

// 使用例
function testDump() {
  const user = { name: '山田', age: 30 };
  dump('ユーザー情報', user);

  const items = ['A', 'B', 'C'];
  dump('アイテム配列', items);
}

まとめ

GASデバッグの基本から応用まで解説しました。

デバッグの3原則

  • ログを出力する – console.log() で変数の中身を確認
  • エラーをキャッチする – try-catch で例外を処理
  • 小さく分割する – 大きな処理は段階的にテスト

覚えておくべきテクニック

状況 テクニック
変数の確認 console.log(), JSON.stringify()
ステップ実行 ブレークポイント + デバッガー
例外処理 try-catch-finally
大量処理 バッチ分割 + Utilities.sleep()
本番運用 エラー通知メール

次のステップ

エラーは成長のチャンス。この記事で学んだテクニックで、自力解決スキルを身につけましょう!


最終更新: 2026-02-02

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次