【bat】バッチファイルで実行時刻・経過時間をログに記録する完全ガイド|タイムスタンプ書式・ステップ計測・性能監視まで

【bat】バッチファイルで実行時刻・経過時間をログに記録する完全ガイド|タイムスタンプ書式・ステップ計測・性能監視まで bat

バッチファイルのログに「いつ実行したか」を残すだけでは不十分なケースがあります。「どのステップで何秒かかったか」「処理全体の経過時間はいくつか」を記録することで、ボトルネックの特定・定期実行の遅延検知・障害時の時系列把握が可能になります。

本記事では %TIME% の書式固定・ステップ別タイムスタンプ・経過時間の計算・wmic による精密取得まで、実践で使える実行時刻ログの手法を体系的に解説します。

この記事でわかること

  • %DATE% %TIME% をログに追記する基本と注意点
  • 時刻フォーマットを HH:MM:SS に固定してロケール差異を吸収する方法
  • 開始・終了時刻と経過時間をまとめて記録する :log_time サブルーチン
  • 経過時間を秒・センチ秒単位で計算するバッチ純正の手法
  • 処理ステップごとにタイムスタンプ付きでログ出力するパターン
  • wmic でロケール非依存の精密な時刻を取得する方法
スポンサーリンク

時刻取得方法の比較

方法 精度 ロケール依存 形式の固定 主な用途
%TIME% センチ秒(1/100秒) あり(地域設定で変化) 要整形 経過時間計算・ほぼすべての用途
%DATE% 日付のみ あり 要整形 日付付きタイムスタンプ
wmic os get LocalDateTime なし(ISO形式固定) 不要 ロケール非依存の厳密な日時
PowerShell Get-Date ミリ秒 なし(書式指定可) 不要 高精度・詳細な書式が必要な場合
%TIME% はロケール設定で形式が変わることに注意
日本語環境では通常 H:MM:SS.cc(時が1桁の場合あり)ですが、英語環境や一部のサーバー環境では異なることがあります。環境を問わず安定して使うには wmicPowerShell を使う、または %TIME% を整形して固定書式にする処理を挟むのが安全です。

基本:ログファイルに実行時刻を追記する

echo %DATE% %TIME% >> logfile.txt が最もシンプルな実装です。追記演算子 >> を使うことで、実行するたびに時刻が蓄積されます。

基本: 実行時刻をログに追記
@echo off
setlocal

set LOGFILE=C:\logs\batch.log

REM ログフォルダがなければ作成
if not exist "C:\logs" mkdir "C:\logs"

REM 実行時刻を追記
echo ===== 実行開始: %DATE% %TIME% ===== >> "%LOGFILE%"

REM ここに実際の処理を記述
echo 処理1: データ取得 >> "%LOGFILE%"
echo 処理2: 変換 >> "%LOGFILE%"

echo ===== 実行終了: %DATE% %TIME% ===== >> "%LOGFILE%"
endlocal
>> と > の違いに注意
>> は追記(ログが蓄積される)、> は上書き(毎回リセット)です。ログとして使う場合は必ず >> を使ってください。またログファイルへの出力は echo の直後にスペースなし>> を続けることで、コンソールには表示せずファイルにのみ出力できます。

時刻フォーマットを HH:MM:SS に固定する

%TIME% はロケールや環境によって桁数が変わるため、ログのソートや比較が難しくなることがあります。for /f でパースして HH:MM:SS 形式に固定することで、どの環境でも一貫したタイムスタンプになります。

時刻フォーマット固定: HH:MM:SS 形式
@echo off
setlocal

set LOGFILE=C:\logs\batch.log

REM %TIME% を HH:MM:SS に整形(先頭スペースを 0 に変換)
set T=%TIME: =0%
set HH=%T:~0,2%
set MM=%T:~3,2%
set SS=%T:~6,2%
set TIMESTAMP=%HH%:%MM%:%SS%

echo [%TIMESTAMP%] バッチ処理開始 >> "%LOGFILE%"
echo [%TIMESTAMP%] 対象: C:\data\input >> "%LOGFILE%"

REM 処理後の時刻を再取得
set T=%TIME: =0%
set HH=%T:~0,2%
set MM=%T:~3,2%
set SS=%T:~6,2%
set TIMESTAMP=%HH%:%MM%:%SS%

echo [%TIMESTAMP%] 処理完了 >> "%LOGFILE%"
endlocal
set T=%TIME: =0% のしくみ
%TIME: =0% は変数の文字列置換で、スペースをゼロ (0) に変換します。%TIME% が 9:05:03.21(先頭スペースあり)のとき、09:05:03.21 に変換されるため、桁数が一定になり ~0,2 などの部分取得が正確に動作します。

:log_time サブルーチンで再利用可能なタイムスタンプ出力を作る

毎回 %TIME% の整形コードを書くのは冗長です。:log_time サブルーチンにまとめることで、call :log_time "メッセージ" の1行でタイムスタンプ付きのログ出力ができます。

再利用可能な :log_time サブルーチン
@echo off
setlocal

set LOGFILE=C:\logs\batch.log
if not exist "C:\logs" mkdir "C:\logs"

REM サブルーチンを呼び出すだけでタイムスタンプ付きログ出力
call :log_time "処理開始"

REM ここに処理を記述
call :log_time "ステップ1: ファイル取得完了"
timeout /t 2 >nul
call :log_time "ステップ2: データ変換完了"
timeout /t 1 >nul
call :log_time "処理終了"

goto :eof

:log_time
set T=%TIME: =0%
set _TS=%T:~0,2%:%T:~3,2%:%T:~6,2%
echo [%_TS%] %~1 >> "%LOGFILE%"
echo [%_TS%] %~1
goto :eof

出力例(batch.log):

batch.log(出力例)
[09:15:00] 処理開始
[09:15:00] ステップ1: ファイル取得完了
[09:15:02] ステップ2: データ変換完了
[09:15:03] 処理終了

経過時間を計算してログに記録する

処理全体や各ステップの所要時間を秒単位で記録することで、パフォーマンス劣化や異常な遅延を検出できます。バッチ純正の計算のみで経過時間を算出する方法を示します。

経過時間をセンチ秒単位で計算する :elapsed_sec サブルーチン
@echo off
setlocal enabledelayedexpansion

set LOGFILE=C:\logs\perf.log
if not exist "C:\logs" mkdir "C:\logs"

REM === 処理開始 ===
set START_TIME=%TIME%
call :log_time "処理開始"

REM ---- 実際の処理(例)----
timeout /t 3 >nul
call :log_time "ステップ1完了"

timeout /t 2 >nul
call :log_time "ステップ2完了"

REM === 経過時間を計算して記録 ===
set END_TIME=%TIME%
call :calc_elapsed "%START_TIME%" "%END_TIME%"
call :log_time "処理終了 (経過時間: !ELAPSED_S!.!ELAPSED_CS! 秒)"

goto :eof

:log_time
set T=%TIME: =0%
set _TS=%T:~0,2%:%T:~3,2%:%T:~6,2%
echo [%_TS%] %~1 >> "%LOGFILE%"
echo [%_TS%] %~1
goto :eof

:calc_elapsed
REM 引数: %1=開始時刻(%TIME%)  %2=終了時刻(%TIME%)
REM 結果: ELAPSED_S(秒), ELAPSED_CS(センチ秒の端数)をセット
setlocal
set T1=%~1
set T2=%~2
REM 先頭スペースを 0 に変換してから分解
set T1=%T1: =0%
set T2=%T2: =0%
for /f "tokens=1-4 delims=:." %%a in ("%T1%") do (
    set /a "s_h=%%a, s_m=%%b, s_s=%%c, s_cs=%%d"
)
for /f "tokens=1-4 delims=:." %%a in ("%T2%") do (
    set /a "e_h=%%a, e_m=%%b, e_s=%%c, e_cs=%%d"
)
set /a start_cs = s_h*360000 + s_m*6000 + s_s*100 + s_cs
set /a end_cs   = e_h*360000 + e_m*6000 + e_s*100 + e_cs
set /a total_cs = end_cs - start_cs
if %total_cs% lss 0 set /a total_cs += 8640000
set /a _elapsed_s  = total_cs / 100
set /a _elapsed_cs = total_cs %% 100
endlocal & set ELAPSED_S=%_elapsed_s% & set ELAPSED_CS=%_elapsed_cs%
goto :eof
センチ秒(1/100秒)で経過時間を計算するしくみ
%TIME% は HH:MM:SS.cc 形式で、cc がセンチ秒です。時・分・秒・センチ秒をそれぞれセンチ秒に換算して合計し差し引くことで、外部コマンド不要で経過時間を算出できます。日付またぎ(処理が真夜中にまたがる)には 8640000(1日のセンチ秒数)を加算して対応します。
endlocal & set の書き方に注意
endlocal & set ELAPSED_S=%_elapsed_s%endlocal 実行後に外側のスコープへ変数を渡すテクニックです。endlocal した後の set は外側のスコープで実行されます。遅延展開を使わず複数の変数を返せるため、サブルーチンの返り値として広く使われます。

処理ステップごとに経過時間をタイムスタンプ付きで記録する

大規模なバッチ処理では「どのステップが遅いか」を特定することが重要です。各ステップの開始時刻と経過時間を記録することで、ボトルネックを可視化できます。

ステップ別の実行時間を記録するバッチ
@echo off
setlocal enabledelayedexpansion

set LOGFILE=C:\logs\steps.log
if not exist "C:\logs" mkdir "C:\logs"

set TOTAL_START=%TIME%
call :log_step "=== バッチ開始 ==="

REM --- ステップ1 ---
set STEP_START=%TIME%
timeout /t 2 >nul
set STEP_END=%TIME%
call :calc_elapsed "%STEP_START%" "%STEP_END%"
call :log_step "ステップ1: データ取得 完了 (%ELAPSED_S%.%ELAPSED_CS%秒)"

REM --- ステップ2 ---
set STEP_START=%TIME%
timeout /t 4 >nul
set STEP_END=%TIME%
call :calc_elapsed "%STEP_START%" "%STEP_END%"
call :log_step "ステップ2: 変換処理 完了 (%ELAPSED_S%.%ELAPSED_CS%秒)"

REM --- ステップ3 ---
set STEP_START=%TIME%
timeout /t 1 >nul
set STEP_END=%TIME%
call :calc_elapsed "%STEP_START%" "%STEP_END%"
call :log_step "ステップ3: ファイル出力 完了 (%ELAPSED_S%.%ELAPSED_CS%秒)"

REM --- 合計経過時間 ---
call :calc_elapsed "%TOTAL_START%" "%TIME%"
call :log_step "=== バッチ終了 合計: %ELAPSED_S%.%ELAPSED_CS%秒 ==="

goto :eof

:log_step
set T=%TIME: =0%
set _TS=%T:~0,2%:%T:~3,2%:%T:~6,2%
echo [%_TS%] %~1 >> "%LOGFILE%"
echo [%_TS%] %~1
goto :eof

:calc_elapsed
setlocal
set T1=%~1
set T2=%~2
set T1=%T1: =0%
set T2=%T2: =0%
for /f "tokens=1-4 delims=:." %%a in ("%T1%") do set /a "s_h=%%a, s_m=%%b, s_s=%%c, s_cs=%%d"
for /f "tokens=1-4 delims=:." %%a in ("%T2%") do set /a "e_h=%%a, e_m=%%b, e_s=%%c, e_cs=%%d"
set /a start_cs = s_h*360000 + s_m*6000 + s_s*100 + s_cs
set /a end_cs   = e_h*360000 + e_m*6000 + e_s*100 + e_cs
set /a total_cs = end_cs - start_cs
if %total_cs% lss 0 set /a total_cs += 8640000
set /a _s = total_cs / 100
set /a _cs = total_cs %% 100
endlocal & set ELAPSED_S=%_s% & set ELAPSED_CS=%_cs%
goto :eof

wmic でロケール非依存の精密な時刻を取得する

wmic の LocalDateTimeYYYYMMDDHHmmss.ffffff+000 形式で返るため、ロケール設定に左右されません。サーバー・複数環境での実行や厳密なタイムスタンプが必要な場合に適しています。

wmic でロケール非依存のタイムスタンプをログに記録
@echo off
setlocal

set LOGFILE=C:\logs\strict.log
if not exist "C:\logs" mkdir "C:\logs"

REM wmic で ISO 形式の日時を取得 (YYYYMMDDHHmmss)
for /f "tokens=2 delims==" %%A in ('wmic os get LocalDateTime /value') do set DT=%%A

REM 年月日・時分秒を取り出す
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 TIMESTAMP=%YYYY%-%MM%-%DD% %HH%:%MI%:%SS%

echo [%TIMESTAMP%] バッチ実行 >> "%LOGFILE%"
echo タイムスタンプ: %TIMESTAMP%
endlocal
wmic vs %TIME% の使い分け
経過時間の計算には %TIME% が向いています(センチ秒単位で計算可能)。一方、ログファイルの先頭タイムスタンプなどロケール非依存の固定書式が必要な場合は wmic を使います。日付付きファイル名の生成は日付と時間をファイル名に挿入する方法も参照してください。

実践: ERRORLEVEL を時刻とともにログに記録する

処理の成否(ERRORLEVEL)と実行時刻をセットで記録することで、「いつ・どこで・どんなエラーが出たか」を後から追跡できます。

処理結果と実行時刻をセットでログに記録
@echo off
setlocal

set LOGFILE=C:\logs\result.log
if not exist "C:\logs" mkdir "C:\logs"

call :log_time "=== 実行開始 ==="

REM 外部コマンドを実行
xcopy /e /y "C:\src" "C:\dst" >nul 2>&1
set XCOPY_RC=%ERRORLEVEL%

if %XCOPY_RC% equ 0 (
    call :log_time "[OK] xcopy 成功"
) else (
    call :log_time "[NG] xcopy 失敗 ERRORLEVEL=%XCOPY_RC%"
)

call :log_time "=== 実行終了 ==="
goto :eof

:log_time
set T=%TIME: =0%
set _TS=%T:~0,2%:%T:~3,2%:%T:~6,2%
echo [%_TS%] %~1 >> "%LOGFILE%"
echo [%_TS%] %~1
goto :eof
ログ出力の関連記事
ログ全体の設計(リダイレクト・PC情報・ローテーション)はバッチファイルでログを出力する方法完全ガイド、日付別ファイルへの分割保存はログを日付別ファイルに自動保存する完全ガイド、ログの自動削除・世代管理はログローテーション完全ガイドを参照してください。

まとめ

  • 基本: echo %DATE% %TIME% >> log.txt でタイムスタンプを追記
  • 書式固定: set T=%TIME: =0% でスペースをゼロに変換し HH:MM:SS 形式を安定取得
  • サブルーチン化: :log_timecall :log_time "msg" の1行でログ出力
  • 経過時間計算: 開始・終了時刻をセンチ秒に変換して差分を算出(外部コマンド不要)
  • ステップ計測: ステップごとに開始・終了を記録してボトルネックを特定
  • ロケール非依存: wmic LocalDateTime で ISO 形式の固定タイムスタンプ
  • 成否セット記録: ERRORLEVEL と時刻を同時に出力して障害追跡を容易に

関連記事: バッチファイルでログを出力する完全ガイド / ログを日付別ファイルに自動保存する完全ガイド / 日付と時間をファイル名に挿入する方法

よくある質問(FAQ)

Q%TIME% が「 9:05:03.21」のように1桁の時間になっていてずれます。
A日本語環境では時が1桁の場合、%TIME% の先頭がスペースになります。set T=%TIME: =0% を使うとスペースが 0 に変換されて 09:05:03.21 になります。その後 %T:~0,2% で先頭2文字を取得すれば常に2桁の時間が得られます。
Qfor ループ内でタイムスタンプを取得しても時刻が変わりません。
Aバッチの変数は % で参照する場合、ループ開始時の値が展開されます。ループ内でリアルタイムに %TIME% を取得するにはsetlocal enabledelayedexpansion を宣言して !TIME!(感嘆符)で参照してください。または call :log_time のようにサブルーチン内で %TIME% を参照する方法でも解決できます。
Q経過時間が負の値になってしまいます。
A処理が日付またぎ(例: 23:59:58 開始 → 00:00:05 終了)になった場合、終了センチ秒 − 開始センチ秒 が負になります。if %total_cs% lss 0 set /a total_cs += 8640000(1日 = 86400秒 = 8,640,000センチ秒)を加算することで正しい経過時間になります。
Qログファイルが文字化けします。
Aバッチファイルの保存エンコードとコマンドプロンプトのコードページが一致していない場合に文字化けが起きます。バッチファイルは Shift-JIS(CP932) で保存するのが基本です。または冒頭に chcp 65001 を追記して UTF-8 コードページに切り替える方法もありますが、一部コマンドの出力に影響する場合があります。文字化けの詳細な原因と対策はバッチファイルで日本語が文字化けする完全解説を参照してください。
Qログファイルが肥大化してきました。古いログを自動削除したいです。
Aログローテーション(世代管理・古いファイルの自動削除)はバッチファイルでログローテーションを実装する方法を参照してください。日付別にファイルを分けて管理する方法はログを日付別ファイルに自動保存する完全ガイドで解説しています。