「バッチが失敗しているのに原因がわからない」「エラーメッセージが画面に流れて消えてしまう」——タスクスケジューラで定期実行するバッチほど、エラーログが不可欠です。
しかし単純に >> log.txt でリダイレクトするだけでは、正常な出力とエラーが混在して後から読み解けません。本記事では stderr(エラー出力)だけを分離する方法から始め、ERRORLEVEL判定で失敗コマンドを特定し、重大度ラベル(WARN/ERROR/FATAL)付きの構造化ログを作成する実践パターンを解説します。
- stdout(標準出力)と stderr(エラー出力)を分離してログに保存する方法
- ERRORLEVEL を判定してエラーが発生したコマンドの情報を記録する方法
- 重大度(INFO / WARN / ERROR / FATAL)ラベル付きの構造化エラーログを作る方法
- 複数コマンドのエラーをコマンド名付きで1ファイルに集約する方法
- findstr・for /f でエラーログを解析してエラー件数を集計する方法
- エラー発生時に PowerShell でアラート通知を送信する方法
- 実践パターン:バックアップ処理のエラーログ完全構成
一般的なログ出力(リダイレクト・タイムスタンプ・ローテーション)はログを出力する方法完全ガイド・ログを日付別ファイルに自動保存する完全ガイド・実行時刻・経過時間をログに記録する完全ガイドを参照してください。この記事はエラーの検出・分類・記録・解析・通知に特化しています。
エラーログ収集手法の比較
| 手法 | 検出方式 | 正常出力との分離 | エラー詳細 | 後解析 |
|---|---|---|---|---|
| 2>> error.log | stderr自動 | ◎ | コマンド依存 | ○ |
| ERRORLEVEL判定 + echo | 終了コード | ◎ | ◎(自由記述) | ◎ |
| 重大度ラベル付き構造化ログ | ERRORLEVEL | ◎ | ◎◎ | ◎◎ |
| 2>&1 で混合保存 | stderr自動 | △(混在) | コマンド依存 | △ |
2>&1 は stderr を stdout にマージするため、正常メッセージとエラーメッセージが混在したログになります。後から findstr でエラーを検索できますが、正常出力が多い場合は埋もれがちです。エラーだけを確実に収集するには 2> または 2>> でファイルに直接書き込むか、ERRORLEVEL を判定して明示的に記録する方法を組み合わせましょう。stderr(エラー出力)を分離してログに保存する
Windowsのコマンドは実行結果をstdout(標準出力:fd=1)とstderr(標準エラー出力:fd=2)の2チャンネルに分けて出力します。2> または 2>> でstderrだけをファイルに書き込めます。
@echo off REM -- stderr だけをファイルに上書き保存(毎回クリアされる) dir Z:\ 2> error.log REM -- stderr だけをファイルに追記(前回分が残る) copy C:\test\file.txt D:\backup\ 2>> error.log REM -- stdout は画面、stderr だけをファイルに保存 xcopy C:\src D:\dst /s 2>> error.log REM -- stdout と stderr を両方ファイルに保存(混在) robocopy C:\src D:\dst /E > all.log 2>&1 REM -- stdout と stderr を別々のファイルに保存 robocopy C:\src D:\dst /E > success.log 2> error.log
robocopy は終了コードで成否を表し、エラーメッセージも stdout に出力します。2> で何も捕捉できない場合は、コマンドが stderr を使っていない可能性があります。その場合は後述の ERRORLEVEL 判定でエラーを検出してください。@echo off
setlocal
set "ERRLOG=C:\logs\error.log"
REM エラーログを空ファイルで初期化
type nul > "%ERRLOG%"
xcopy C:\src D:\dst /s 2>> "%ERRLOG%"
REM エラーログのサイズが 0 より大きければエラーあり
for %%F in ("%ERRLOG%") do (
if %%~zF gtr 0 (
echo [ERROR] エラーが発生しました。ログ: %ERRLOG%
exit /b 1
) else (
echo [OK] エラーなし
)
)
endlocal
ERRORLEVEL を判定してエラー情報を明示的に記録する
ERRORLEVEL(終了コード)はコマンドが正常終了したかどうかを示す数値です。0 が成功、1以上が失敗を意味します(コマンドによって異なる)。コマンド実行直後に判定して、失敗時の詳細情報を自分でログに書き込む方法が最も確実です。
@echo off
setlocal
set "LOGFILE=C:\logs\batch_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log"
if not exist "C:\logs\" mkdir "C:\logs"
REM コマンドを実行
xcopy C:\src\*.csv D:\backup\ /y >nul 2>&1
REM ERRORLEVEL を即座に変数に保存(次のコマンドで上書きされる前に)
set RC=%ERRORLEVEL%
if %RC% neq 0 (
echo [%DATE% %TIME%] [ERROR] xcopy 失敗 ERRORLEVEL=%RC% >> "%LOGFILE%"
) else (
echo [%DATE% %TIME%] [INFO] xcopy 成功 >> "%LOGFILE%"
)
endlocal
if %ERRORLEVEL% や set RC=%ERRORLEVEL% はコマンド実行直後の次の行に書く必要があります。echo や set など別のコマンドを挟むと上書きされます。また if errorlevel 1(変数展開なし)形式は「1以上」を意味するため、正確な値比較には if %RC% equ 1 を使ってください。@echo off
setlocal
set "LOGFILE=C:\logs\robocopy_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log"
robocopy C:\src D:\dst /E /NFL /NDL /NJH /NJS > nul
set RC=%ERRORLEVEL%
REM robocopy 終了コード: 0=変更なし 1=コピー成功 2=追加ファイルあり
REM 4=不一致 8=エラー 16=致命的エラー
if %RC% lss 8 (
echo [%DATE% %TIME%] [INFO] robocopy 正常終了 RC=%RC% >> "%LOGFILE%"
) else (
echo [%DATE% %TIME%] [ERROR] robocopy エラー RC=%RC% >> "%LOGFILE%"
exit /b %RC%
)
endlocal
重大度ラベル付きの構造化エラーログを作成する
エラーログに INFO / WARN / ERROR / FATAL のレベルを付けておくと、後から findstr で重大なエラーだけを抽出できます。日時・処理ステップ名・終了コードを含む構造化フォーマットにしておくと運用保守が格段に楽になります。
@echo off
setlocal enabledelayedexpansion
set "LOGFILE=C:\logs\app_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log"
if not exist "C:\logs\" mkdir "C:\logs"
REM ログ書き込み用のサブルーチンを呼ぶ
call :LOG INFO "処理開始"
REM ステップ1: バックアップ
robocopy C:\data D:\backup /E /NFL /NDL /NJH /NJS > nul
set RC=%ERRORLEVEL%
if %RC% geq 8 (
call :LOG ERROR "バックアップ失敗 RC=%RC%"
call :LOG FATAL "処理中止"
exit /b 1
) else (
call :LOG INFO "バックアップ完了 RC=%RC%"
)
REM ステップ2: 古いファイル削除
forfiles /p C:\data /d -30 /c "cmd /c del @path" 2>nul
set RC=%ERRORLEVEL%
if %RC% neq 0 (
call :LOG WARN "forfiles 終了コード=%RC%(削除対象なし、または軽微なエラー)"
)
call :LOG INFO "処理終了"
exit /b 0
:LOG
REM 使い方: call :LOG LEVEL "メッセージ"
set "LVL=%~1"
set "MSG=%~2"
echo [%DATE% %TIME%] [%LVL%] %MSG% >> "%LOGFILE%"
if /i "%LVL%"=="ERROR" echo [%DATE% %TIME%] [ERROR] %MSG%
if /i "%LVL%"=="FATAL" echo [%DATE% %TIME%] [FATAL] %MSG%
exit /b 0
このパターンでは call :LOG サブルーチンに統一することでログフォーマットが一定になります。ERROR/FATAL レベルは画面にも表示し、INFO/WARN はファイルのみに記録します。
[2026-03-22 09:15:03.42] [ERROR] バックアップ失敗 RC=8日時・レベル・メッセージの3要素が揃っていると、
findstr "[ERROR]" app_20260322.log で即座に問題を絞り込めます。複数コマンドのエラーをコマンド名付きで1ファイルに集約する
複数の処理ステップを持つバッチでは、どのコマンドでエラーが発生したかをログに明記しておくことが重要です。エラーが発生してもすべての処理を最後まで実行し、まとめて報告するパターンを紹介します。
@echo off
setlocal enabledelayedexpansion
set "LOGFILE=C:\logs\batch_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log"
if not exist "C:\logs\" mkdir "C:\logs"
set "ERRORS=0"
set "ERROR_CMDS="
echo [%DATE% %TIME%] === バッチ処理開始 === >> "%LOGFILE%"
REM --- ステップ1: ファイルコピー ---
xcopy "C:\src\data" "D:\backup\data" /s /y /q >nul 2>> "%LOGFILE%"
set RC=%ERRORLEVEL%
if %RC% neq 0 (
set /a "ERRORS+=1"
set "ERROR_CMDS=!ERROR_CMDS! [xcopy RC=%RC%]"
echo [%DATE% %TIME%] [ERROR] xcopy 失敗 RC=%RC% >> "%LOGFILE%"
) else (
echo [%DATE% %TIME%] [INFO] xcopy 完了 >> "%LOGFILE%"
)
REM --- ステップ2: DB エクスポート ---
mysqldump -u root mydb > "D:\backup\mydb.sql" 2>> "%LOGFILE%"
set RC=%ERRORLEVEL%
if %RC% neq 0 (
set /a "ERRORS+=1"
set "ERROR_CMDS=!ERROR_CMDS! [mysqldump RC=%RC%]"
echo [%DATE% %TIME%] [ERROR] mysqldump 失敗 RC=%RC% >> "%LOGFILE%"
) else (
echo [%DATE% %TIME%] [INFO] mysqldump 完了 >> "%LOGFILE%"
)
REM --- ステップ3: 古いバックアップ削除 ---
forfiles /p "D:\backup" /d -7 /c "cmd /c del @path" 2>> "%LOGFILE%"
REM forfiles は対象なしのときも 1 を返すので ERRORLEVEL は無視する
REM --- サマリー ---
echo [%DATE% %TIME%] === 処理終了 ERRORS=%ERRORS% === >> "%LOGFILE%"
if %ERRORS% gtr 0 (
echo [%DATE% %TIME%] [SUMMARY] エラーコマンド:%ERROR_CMDS% >> "%LOGFILE%"
echo エラーが %ERRORS% 件発生しました:%ERROR_CMDS%
exit /b 1
) else (
echo [%DATE% %TIME%] [SUMMARY] すべて正常終了 >> "%LOGFILE%"
echo 全ステップ正常終了
)
endlocal
エラーログを日付別ファイルに保存してローテーションする
一般ログとエラーログを別ファイルに分け、エラーログは長期保存するパターンが運用に向いています。エラーが発生した日だけファイルが作成されるため、ディスク節約にもなります。
@echo off
setlocal
set "LOGDIR=C:\logs"
set "TODAY=%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%"
set "ERRLOG=%LOGDIR%\error_%TODAY%.log"
set "INFOLOG=%LOGDIR%\info_%TODAY%.log"
if not exist "%LOGDIR%\" mkdir "%LOGDIR%"
REM 処理(stdout=通常ログ / stderr=エラーログ に分離)
robocopy C:\src D:\dst /E /LOG+:"%INFOLOG%" 2>> "%ERRLOG%"
set RC=%ERRORLEVEL%
if %RC% geq 8 (
echo [%DATE% %TIME%] [ERROR] robocopy RC=%RC% >> "%ERRLOG%"
)
REM 90 日以上古いログを削除(エラーログ・通常ログ両方)
forfiles /p "%LOGDIR%" /m "error_*.log" /d -90 /c "cmd /c del @path" >nul 2>&1
forfiles /p "%LOGDIR%" /m "info_*.log" /d -90 /c "cmd /c del @path" >nul 2>&1
endlocal
ログのローテーション設計の詳細はログを日付別ファイルに自動保存する完全ガイドも参照してください。
エラーログを解析してエラー件数・重大度を集計する
蓄積されたエラーログを解析し、今日のエラー件数・FATAL の有無・頻発コマンドを集計できます。findstr と for /f を組み合わせて実装します。
@echo off
setlocal enabledelayedexpansion
set "TODAY=%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%"
set "LOGFILE=C:\logs\app_%TODAY%.log"
if not exist "%LOGFILE%" (
echo ログファイルが見つかりません: %LOGFILE%
exit /b 0
)
set "CNT_ERROR=0"
set "CNT_WARN=0"
set "CNT_FATAL=0"
for /f "usebackq delims=" %%L in ("%LOGFILE%") do (
echo %%L | findstr /c:"[ERROR]" >nul 2>&1 && set /a "CNT_ERROR+=1"
echo %%L | findstr /c:"[WARN]" >nul 2>&1 && set /a "CNT_WARN+=1"
echo %%L | findstr /c:"[FATAL]" >nul 2>&1 && set /a "CNT_FATAL+=1"
)
echo === %TODAY% エラーサマリー ===
echo FATAL : !CNT_FATAL!
echo ERROR : !CNT_ERROR!
echo WARN : !CNT_WARN!
if !CNT_FATAL! gtr 0 (
echo [FATAL あり] 至急確認してください: %LOGFILE%
exit /b 2
) else if !CNT_ERROR! gtr 0 (
exit /b 1
) else (
exit /b 0
)
endlocal
@echo off
setlocal
set "LOGDIR=C:\logs"
set "TODAY=%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%"
REM ERROR または FATAL を含む行だけを抽出
findstr /i /c:"[ERROR]" /c:"[FATAL]" "%LOGDIR%\app_%TODAY%.log" > "%LOGDIR%\critical_%TODAY%.log" 2>nul
REM 抽出されたファイルが空でなければ報告
for %%F in ("%LOGDIR%\critical_%TODAY%.log") do (
if %%~zF gtr 0 (
echo [重大エラーあり] %%~zF bytes >> "%LOGDIR%\summary_%TODAY%.txt"
type "%LOGDIR%\critical_%TODAY%.log"
)
)
endlocal
エラー発生時に PowerShell でアラート通知を送信する
エラーログにFATAL/ERRORが記録されたタイミングで、PowerShell から通知メールや Slack Webhook を呼び出すことで無人運用の監視体制を構築できます。
@echo off
setlocal
set "LOGFILE=C:\logs\app_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log"
set "MAIL_TO=admin@example.com"
set "SMTP=smtp.example.com"
REM 処理実行
call :RUN_BACKUP
set RC=%ERRORLEVEL%
if %RC% neq 0 (
echo [%DATE% %TIME%] [FATAL] バックアップ失敗 RC=%RC% >> "%LOGFILE%"
REM PowerShell でメール通知
powershell -NoProfile -Command ^
"Send-MailMessage -To '%MAIL_TO%' -From 'batch@example.com' ^
-Subject '[ALERT] バッチエラー %DATE%' ^
-Body (Get-Content '%LOGFILE%' -Raw) ^
-SmtpServer '%SMTP%'"
)
exit /b %RC%
:RUN_BACKUP
robocopy C:\src D:\dst /E /NFL /NDL >nul
if %ERRORLEVEL% geq 8 exit /b %ERRORLEVEL%
exit /b 0
@echo off
setlocal
set "WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ"
set "CHANNEL=#alerts"
set "MSG=[ALERT] バッチエラー発生 %DATE% %TIME%"
REM PowerShell で Slack Webhook に POST
powershell -NoProfile -Command ^
"$body = ConvertTo-Json @{text='%MSG%'; channel='%CHANNEL%'}; ^
Invoke-RestMethod -Uri '%WEBHOOK_URL%' ^
-Method Post -Body $body -ContentType 'application/json'"
endlocal
メール・Slack・Blat などの通知手段の詳細はエラー通知メールを自動送信する完全ガイドも参照してください。
実践パターン:バックアップ処理のエラーログ完全構成
ここまで解説した技術をすべて組み合わせた、実運用に耐えるバックアップバッチのエラーログ完全構成です。
@echo off
setlocal enabledelayedexpansion
REM ===== 設定 =====
set "SRC=C:\data"
set "DST=D:\backup"
set "LOGDIR=C:\logs\backup"
set "TODAY=%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%"
set "LOGFILE=%LOGDIR%\backup_%TODAY%.log"
set "ERRFILE=%LOGDIR%\error_%TODAY%.log"
set "MAIL_TO=admin@example.com"
if not exist "%LOGDIR%\" mkdir "%LOGDIR%"
REM ===== ログ初期化 =====
set "ERRORS=0"
call :LOG INFO "======== バックアップ開始 ========"
call :LOG INFO "SRC=%SRC% DST=%DST%"
REM ===== ステップ1: robocopy バックアップ =====
robocopy "%SRC%" "%DST%" /E /XD ".git" "node_modules" ^
/LOG+:"%LOGFILE%" /NJH /NJS /NFL /NDL 2>> "%ERRFILE%"
set RC=%ERRORLEVEL%
if %RC% geq 8 (
call :LOG ERROR "robocopy 失敗 RC=%RC%"
set /a "ERRORS+=1"
) else (
call :LOG INFO "robocopy 完了 RC=%RC%"
)
REM ===== ステップ2: 90日以上古いバックアップを削除 =====
forfiles /p "%DST%" /d -90 /c "cmd /c if @isdir==FALSE del @path" >nul 2>> "%ERRFILE%"
set RC=%ERRORLEVEL%
if %RC% neq 0 (
call :LOG WARN "forfiles 終了コード=%RC%(削除対象なしの可能性あり)"
)
REM ===== サマリー =====
call :LOG INFO "======== バックアップ終了 ERRORS=!ERRORS! ========"
if !ERRORS! gtr 0 (
REM エラーログをメール通知
powershell -NoProfile -Command ^
"Send-MailMessage -To '%MAIL_TO%' -From 'batch@srv' ^
-Subject '[ERROR] バックアップ失敗 %TODAY%' ^
-Body (Get-Content '%ERRFILE%' -Raw -ErrorAction SilentlyContinue) ^
-SmtpServer 'smtp.example.com'" >nul 2>&1
exit /b 1
) else (
REM エラーログが空なら削除してディスクを節約
for %%F in ("%ERRFILE%") do if %%~zF equ 0 del "%%F" >nul 2>&1
exit /b 0
)
:LOG
set "LVL=%~1"
set "MSG=%~2"
echo [%DATE% %TIME%] [%LVL%] %MSG% >> "%LOGFILE%"
if /i "!LVL!"=="ERROR" echo [%DATE% %TIME%] [ERROR] %MSG%
if /i "!LVL!"=="FATAL" echo [%DATE% %TIME%] [FATAL] %MSG%
exit /b 0
並列処理でのログ管理はバッチファイルで並列処理を実行する方法、自動デプロイでのエラーハンドリングはバッチファイルで自動デプロイを実現する完全ガイドも参照してください。
エラーログ収集のトラブルシューティング
| 症状 | 原因 | 対処法 |
|---|---|---|
| 2>> でログが空になる | コマンドが stderr を使わない(robocopy など) | ERRORLEVEL 判定で手動記録する |
| ログに文字化けが出る | コマンドプロンプトのコードページ(CP932)とログの不一致 | バッチ先頭に chcp 65001 >nul でUTF-8に統一 |
| ERRORLEVEL が常に 0 になる | コマンドが終了コードを設定しない | stderr ファイルのサイズで判定する方法を使う |
| forfiles がエラーコード 1 を返す | 削除対象ファイルが存在しない(正常) | forfiles の終了コードは無視し stderr のみ確認する |
| ループ内のカウンタが増えない | enabledelayedexpansion なし | setlocal enabledelayedexpansion を追加し !VAR! で参照する |
| ログファイルが日付ごとに増えすぎる | ローテーション未設定 | forfiles /d -N で古いログを定期削除する |
まとめ
- 2>> error.log: stderr だけをエラーログに分離保存。正常出力との混在を防ぐ基本
- ERRORLEVEL 即時保存:
set RC=%ERRORLEVEL%をコマンド実行直後に記述。別コマンドで上書きされる前に確保する - 重大度ラベル付き構造化ログ:
call :LOG ERROR "メッセージ"パターンで INFO / WARN / ERROR / FATAL を統一フォーマットで記録 - コマンド名付きエラー集約: エラー発生コマンド名を変数に蓄積してサマリー出力。どのステップで失敗したか即特定
- エラーログ解析:
findstr /c:"[ERROR]"とfor /fでエラー件数を集計。FATAL があれば即アラート - アラート通知: エラー時のみ PowerShell Send-MailMessage / Slack Webhook で通知。正常時は通知ゼロで運用負担を減らす
- エラーログが空なら削除: エラーのなかった日のファイルを削除するとエラー発生日がひと目でわかる
関連記事: ログを出力する方法完全ガイド / ログを日付別ファイルに自動保存する完全ガイド / 実行時刻・経過時間をログに記録する完全ガイド / エラー通知メールを自動送信する完全ガイド
よくある質問(FAQ)
2>> error.log で保存しているのにファイルが空です。robocopy・dir・echo などが代表例です。その場合は ERRORLEVEL を判定して手動でログに書き込む方法を使ってください。stderr を使うかどうかは コマンド 2>test.log && echo stdout のように実際に試して確認するのが確実です。%DATE% の書式もロケール依存があるため、wmic os get LocalDateTime を使う方法が安全です。詳細はログを日付別ファイルに自動保存する完全ガイドを参照してください。chcp 65001 >nul を追加してUTF-8に変更するか、ログファイルをShift-JIS対応のエディタ(メモ帳など)で開いてください。ただし chcp 65001 に変更すると一部の日本語環境コマンドが正常動作しなくなる場合があるため、動作確認を行ってから適用してください。if %ERRORLEVEL% neq 0 で判定しているのに、エラーなのに 0 と判定されます。%ERRORLEVEL% の参照前に別のコマンドが実行されて上書きされた(直後の set・echo でも書き換わる)。コマンド実行直後に set RC=%ERRORLEVEL% で変数に退避してください。② コマンド自体が失敗しても終了コード 0 を返す仕様の場合。その場合は stderr のファイルサイズで判定する方法を使ってください。forfiles /p "C:\logs" /m "error_*.log" /d -90 /c "cmd /c del @path" で90日以上前のエラーログを自動削除できます。タスクスケジューラで月1回実行するように設定しておくと管理が楽です。エラーが発生しなかった日のログが0バイトになる場合は、for %%F in (C:\logs\error_*.log) do if %%~zF equ 0 del "%%F"で空ファイルをまとめて削除できます。ローテーション設計の詳細はログを出力する方法完全ガイドを参照してください。

