バッチファイルを毎日定期実行していると、ある日突然「ディスクがいっぱいです」というエラーが出て処理が止まる——そんな経験はありませんか?
1日1ファイルでも1年で365件。5年運用すれば1,800件を超えます。手動での削除は手間がかかり、いつかは必ず忘れます。
解決策はログローテーションの自動化です。「N日分だけ保持して古いファイルは自動削除」「一定サイズを超えたら新ファイルへ切替」という仕組みをバッチ内に組み込むことで、ログ管理を完全に自動化できます。
この記事では FORFILES による日付方式の自動削除・世代管理・サイズ制限・ZIP圧縮アーカイブ・INFO/WARN/ERRORのログレベル実装まで、コピーしてすぐ使える実装例を体系的に解説します。
ログローテーションとは
ログローテーション(Log Rotation)とは、古いログファイルを自動的に削除・圧縮・リネームして、保持するログ量を一定に保つ仕組みです。
| ローテーション方式 |
概要 |
向いている用途 |
| 日付方式 |
1日1ファイル、古いファイルをN日後削除 |
定期バッチ・毎日実行 |
| 世代方式 |
最新N件だけ保持、超えたら古い順に削除 |
実行回数ベースの管理 |
| サイズ方式 |
ファイルサイズがN MBを超えたらローテーション |
長時間連続稼働 |
基本:日付付きログファイルを作る
日付ローテーションの出発点は、%DATE% を整形してファイル名に使うことです。ロケールに依存しない安全な取り出し方と、%TIME% を組み合わせた実装を押さえましょう。
log-basic.bat
@echo off
setlocal
:: ── 日付・時刻の取得(ロケール非依存) ──────────────
for /f "tokens=1-3 delims=/" %%a in ('wmic os get LocalDateTime /value | find "="') do (
for /f "tokens=2 delims==" %%d in ("%%a") do set DT=%%d
)
set YYYY=%DT:~0,4%
set MM=%DT:~4,2%
set DD=%DT:~6,2%
set HH=%DT:~8,2%
set MI=%DT:~10,2%
set SS=%DT:~12,2%
:: ── ログファイルパス ─────────────────────────────
set LOGDIR=%~dp0logs
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
set LOGFILE=%LOGDIR%%YYYY%-%MM%-%DD%.log
:: ── ログ出力 ─────────────────────────────────────
set TIMESTAMP=%YYYY%-%MM%-%DD% %HH%:%MI%:%SS%
echo [%TIMESTAMP%] [INFO] バッチ処理を開始 ^>> "%LOGFILE%"
:: ── メイン処理 ───────────────────────────────────
xcopy C:data D:ackup /D /Y ^>> "%LOGFILE%"
echo [%TIMESTAMP%] [INFO] バックアップ完了 ^>> "%LOGFILE%"
endlocal
ポイント
%DATE% はロケール設定(地域)によって形式が異なります(2026/03/13 や 2026-03-13 など)。wmic os get LocalDateTime を使うと YYYYMMDDHHmmss 固定形式で取得でき、どの環境でも動作します。
FORFILES で古いログを自動削除する
FORFILES コマンドは「N日以上前のファイルを対象にコマンドを実行する」という処理に最適です。ログローテーションの核心です。
bat
:: 30日以上前の .log ファイルを削除
FORFILES /p "%LOGDIR%" /m *.log /d -30 /c "cmd /c del @path"
:: エラー抑制(対象ファイルなしでも止まらない)
FORFILES /p "%LOGDIR%" /m *.log /d -30 /c "cmd /c del @path" 2^>nul
| オプション |
説明 |
| /p パス |
対象フォルダ |
| /m パターン |
ファイル名パターン(例: *.log) |
| /d -N |
N日以上前のファイルを対象(-30 = 30日前以前) |
| /d +N |
N日以内(未来含む)のファイルを対象 |
| /c “コマンド” |
各ファイルに対して実行するコマンド。@path = フルパス、@fname = ファイル名 |
日付方式ローテーションの完全実装
ログ出力 + 古いファイルの自動削除をひとつのバッチにまとめた実装例です。
log-rotate-date.bat
@echo off
setlocal
:: ── 設定 ──────────────────────────────────────────
set LOGDIR=%~dp0logs
set KEEP_DAYS=30 &:: 保持日数
:: ── 日時取得(ロケール非依存) ────────────────────
for /f "skip=1 tokens=1" %%t in ('wmic os get LocalDateTime') do (
if not "%%t"=="" set DT=%%t
)
set YYYY=%DT:~0,4% & set MM=%DT:~4,2% & set DD=%DT:~6,2%
set HH=%DT:~8,2% & set MI=%DT:~10,2% & set SS=%DT:~12,2%
set TS=%YYYY%-%MM%-%DD% %HH%:%MI%:%SS%
set LOGFILE=%LOGDIR%%YYYY%-%MM%-%DD%.log
:: ── ログフォルダ作成 ───────────────────────────────
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
:: ── ログ書き込み関数(CALL :LOG レベル メッセージ) ──
call :LOG INFO "処理開始"
:: ── メイン処理 ───────────────────────────────────
xcopy "C:data" "D:ackup" /D /Y ^>> "%LOGFILE%" 2^>&1
if %ERRORLEVEL% neq 0 (
call :LOG ERROR "xcopy 失敗 ERRORLEVEL=%ERRORLEVEL%"
) else (
call :LOG INFO "バックアップ完了"
)
:: ── ローテーション: KEEP_DAYS日より古いログを削除 ─────
FORFILES /p "%LOGDIR%" /m *.log /d -%KEEP_DAYS% /c "cmd /c del @path" 2^>nul
call :LOG INFO "ローテーション完了(%KEEP_DAYS%日超ログ削除)"
endlocal
exit /b 0
:: ── :LOG サブルーチン ────────────────────────────
:LOG
:: 使い方: call :LOG [INFO|WARN|ERROR] "メッセージ"
echo [%TS%] [%~1] %~2 ^>> "%LOGFILE%"
exit /b 0
世代管理方式(最新N件だけ残す)
日付ではなく「何個まで保持するか」でローテーションしたい場合の実装です。ファイル数が一定以上になったら古い順に削除します。
log-rotate-gen.bat
@echo off
setlocal enabledelayedexpansion
set LOGDIR=%~dp0logs
set MAX_FILES=10 &:: 保持するファイル数
:: ── ファイル数を数える ───────────────────────────
set COUNT=0
for %%f in ("%LOGDIR%*.log") do set /a COUNT+=1
:: ── MAX_FILES を超えたら古い順に削除 ───────────────
if %COUNT% gtr %MAX_FILES% (
set DEL_COUNT=0
set /a DEL_TARGET=%COUNT% - %MAX_FILES%
:: 更新日時の昇順(古い順)でループ
for /f "delims=" %%f in ('dir /b /o:d "%LOGDIR%*.log"') do (
if !DEL_COUNT! lss !DEL_TARGET! (
del "%LOGDIR%\%%f"
echo 削除: %%f
set /a DEL_COUNT+=1
)
)
)
echo 世代管理完了(現在: %COUNT%件 → 上限: %MAX_FILES%件)
サイズ制限方式(ファイルサイズでローテーション)
長時間連続稼働するバッチでは、1つのログファイルが肥大化します。一定サイズを超えたら新しいファイルに切り替える実装です。
log-rotate-size.bat
@echo off
setlocal enabledelayedexpansion
set LOGDIR=%~dp0logs
set LOGBASE=app
set MAX_SIZE=10485760 &:: 10MB(バイト単位)
set LOGFILE=%LOGDIR%%LOGBASE%.log
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
:: ── 既存ログファイルのサイズ確認 ───────────────────
if exist "%LOGFILE%" (
for %%s in ("%LOGFILE%") do set FILESIZE=%%~zs
if !FILESIZE! gtr %MAX_SIZE% (
:: タイムスタンプ付きでリネーム
for /f "skip=1 tokens=1" %%t in ('wmic os get LocalDateTime') do (
if not "%%t"=="" set DT2=%%t
)
ren "%LOGFILE%" "%LOGBASE%_!DT2:~0,14!.log"
echo ローテーション: !FILESIZE! bytes ^> 新ファイルへ切替
)
)
:: ── ログ書き込み ─────────────────────────────────
echo [処理実行] ^>> "%LOGFILE%"
古いログをZIP圧縮してアーカイブする
削除せずに圧縮保管したいケースでは、PowerShell の Compress-Archive と組み合わせます。
log-archive.bat
@echo off
setlocal enabledelayedexpansion
set LOGDIR=%~dp0logs
set ARCHIVEDIR=%~dp0logsarchive
set ARCHIVE_DAYS=7 &:: 7日以上前を圧縮対象
if not exist "%ARCHIVEDIR%" mkdir "%ARCHIVEDIR%"
:: 7日以上前のログを1ファイルずつZIP圧縮して元ファイルを削除
FORFILES /p "%LOGDIR%" /m *.log /d -%ARCHIVE_DAYS% /c ^
"cmd /c powershell -NoProfile -Command "Compress-Archive -Path @path -DestinationPath '%ARCHIVEDIR%\@fname.zip' -Force" && del @path" 2^>nul
echo アーカイブ完了: %ARCHIVEDIR%
ログレベル対応の汎用ロガーを作る
INFO/WARN/ERROR の3段階ログレベルを持つサブルーチンを実装すれば、どのバッチでも call :LOG レベル "メッセージ" の一行で書けます。
logger.bat(共通ロガー)
@echo off
setlocal enabledelayedexpansion
:: ── 設定 ──────────────────────────────────────────
set LOGDIR=%~dp0logs
set KEEP_DAYS=30
set LOG_LEVEL_MIN=INFO &:: INFO / WARN / ERROR(これ未満は出力しない)
:: ── 日時取得 ───────────────────────────────────────
for /f "skip=1 tokens=1" %%t in ('wmic os get LocalDateTime') do (
if not "%%t"=="" set DT=%%t
)
set YYYY=%DT:~0,4% & set MM=%DT:~4,2% & set DD=%DT:~6,2%
set HH=%DT:~8,2% & set MI=%DT:~10,2% & set SS=%DT:~12,2%
set TS=%YYYY%-%MM%-%DD% %HH%:%MI%:%SS%
set LOGFILE=%LOGDIR%%YYYY%-%MM%-%DD%.log
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
:: ── メイン処理 ───────────────────────────────────
call :LOG INFO "===== バッチ開始 ====="
call :LOG INFO "バックアップ実行中"
xcopy "C:data" "D:ackup" /D /Y 1^>>"%LOGFILE%" 2^>&1
if %ERRORLEVEL% neq 0 (
call :LOG ERROR "バックアップ失敗 code=%ERRORLEVEL%"
exit /b 1
)
call :LOG INFO "バックアップ完了"
call :LOG INFO "===== バッチ終了 ====="
:: ローテーション
FORFILES /p "%LOGDIR%" /m *.log /d -%KEEP_DAYS% /c "cmd /c del @path" 2^>nul
endlocal
exit /b 0
:: ═══════════════════════════════════════════════════
:: :LOG サブルーチン
:: 引数1: レベル (INFO / WARN / ERROR)
:: 引数2: メッセージ
:: ═══════════════════════════════════════════════════
:LOG
set _LV=%~1
set _MSG=%~2
:: ファイルに記録
echo [%TS%] [%_LV%] %_MSG% ^>> "%LOGFILE%"
:: ERRORだけコンソールにも表示
if /i "%_LV%"=="ERROR" echo [ERROR] %_MSG%
exit /b 0
出力されるログの例:
2026-03-13.log(出力例)
[2026-03-13 02:30:01] [INFO] ===== バッチ開始 =====
[2026-03-13 02:30:01] [INFO] バックアップ実行中
[2026-03-13 02:30:05] [INFO] バックアップ完了
[2026-03-13 02:30:05] [INFO] ===== バッチ終了 =====
タスクスケジューラで毎日自動実行する
ローテーション付きバッチをタスクスケジューラに登録すれば、完全自動化できます。schtasks コマンドで登録できます。
bat
:: 毎日 02:30 に logger.bat を実行するタスクを登録
schtasks /create ^
/tn "DailyBackupLogger" ^
/tr "C:scriptslogger.bat" ^
/sc daily ^
/st 02:30 ^
/ru SYSTEM ^
/f
:: 登録内容を確認
schtasks /query /tn "DailyBackupLogger"
よくある問題と対処法
❓ FORFILES でエラー「指定したファイルが見つかりません」 クリックで開閉
対象ファイルが1件も存在しないとエラーになります。2^>nul でエラー出力を抑制してください。
FORFILES /p "%LOGDIR%" /m *.log /d -30 /c "cmd /c del @path" 2^>nul
❓ 日付取得が文字化けする・ロケール依存でずれる クリックで開閉
%DATE% の代わりに wmic os get LocalDateTime を使うと、どのロケール設定でも YYYYMMDDHHmmss 固定形式で取得でき安全です。
for /f "skip=1 tokens=1" %%t in ('wmic os get LocalDateTime') do (
if not "%%t"=="" set DT=%%t
)
:: DT = 20260313023001.000000+540 (先頭14桁だけ使う)
❓ タスクスケジューラから実行するとログが別の場所に作られる クリックで開閉
タスクスケジューラ実行時のカレントディレクトリは C:WindowsSystem32 です。%~dp0 でバッチファイル自身のディレクトリを基準にすると、どこから実行しても同じ場所にログが作られます。
:: NG: 相対パス(カレントディレクトリ依存)
set LOGDIR=logs
:: OK: %~dp0 でバッチと同じ場所に固定
set LOGDIR=%~dp0logs
まとめ
| やりたいこと |
使うもの |
| 日付付きファイル名を作る |
wmic os get LocalDateTime + 文字列切り出し |
| N日以上前のファイルを削除 |
FORFILES /d -N /c “cmd /c del @path” |
| 最新N件だけ残す |
dir /b /o:d + for /f でカウントしながら削除 |
| ファイルサイズでローテーション |
for %%s in (…) do set SIZE=%%~zs で比較 |
| 古いログをZIP圧縮 |
FORFILES + PowerShell Compress-Archive |
| INFO/WARN/ERRORのログレベル |
call :LOG サブルーチン |
| 毎日自動実行 |
schtasks /create /sc daily |
ログローテーションは一度組み込めば完全自動で機能します。KEEP_DAYS などの定数を先頭にまとめておくと、後から保持日数だけ変更するメンテナンスも簡単です。この記事のコードをそのままコピーして、自社バッチのロガーとして使ってみてください。