【実務・中級編】【実務中級者向け】巨大ログ処理中の進捗率(%)をコンソールやタイトルバーへリアルタイム表示するループ設計 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【VBScript極限講座】巨大ログ処理の黑暗を断つ!進捗率リアルタイム可視化ループの設計思想

開発現場でふと直面する「数ギガバイトに及ぶ巨大ログファイルの解析・集計」。
VBScriptの `TextStream.ReadLine` を素朴にループさせ、黒いコンソール画面のままでじっと数十分待たされた経験はないだろうか?

「本当に処理が進んでいるのか? フリーズしているのではないか?」

この恐怖と不確実性は、業務自動化ツールにおいて最大の悪徳である。
今回は、FSO(FileSystemObject)の隠された挙動とメモリ管理の限界を見据え、巨大ログを高速かつ安全に読み込み、コンソール(あるいはウィンドウタイトル)へリアルタイムに進捗率(%)を描画するプロダクションコードの設計手法を伝授する。

—

1. なぜ「素朴なループ」は実務で破綻するのか?

多くの初中級プログラマが書くコードはこうだ:

‘ 【アンチパターン】これでは進捗が分からない上にメモリを圧迫する
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set ts = fso.OpenTextFile(“C:\Logs\huge_monster.log”, 1)

Do Until ts.AtEndOfStream
line = ts.ReadLine
‘ 処理ロジック…
Loop
ts.Close

このアプローチには、実務上3つの致命的な欠陥がある。

1. 全体量が不明: ファイル全体のサイズ(バイト数)に対する現在の位置が不明なため、「あと何パーセントで終わるか」が計算できない。
2. I/Oの過剰なオーバヘッド: すべての行に対して画面描画(WScript.Echo等)を行うと、コンソールのI/Oロックがボトルネックになり、処理速度が桁違いに低下する。
3. 文字コードの罠: VBScriptの `OpenTextFile` はデフォルトでANSI(Shift_JIS等)かUnicodeしか扱えず、UTF-8(BOMなし)の巨大ログを読み込むと文字化けを起こすか、最悪の場合クラッシュする。

これらをすべてクリアし、エンタープライズ環境に耐えうる堅牢な設計を構築する。

—

2. 堅牢な進捗率計算のメカニズム

進捗率を算出する方程式は極めてシンプルだ。

$$\text{進捗率(\%)} = \left( \frac{\text{現在読み込んだバイト数}}{\text{ファイル総バイト数}} \right) \times 100$$

しかし、ここで大きな問題がある。`TextStream` オブジェクトには「今何バイト目を読んでいるか」を直接取得するプロパティが存在しない。

ではどうするか?
テキストストリームを一行ずつ読むのではなく、`Read` メソッドや、あるいは効率的に「文字数・バイト数」をカウントしながら進めるアプローチが必要になる。だが、一行単位のパース性を捨てたくはない。

ここで、「行数ではなく、定期的なタイマー(あるいはカウンタ)による近似と、WScript.StdOut.Writeを用いたインプレース更新」というプロの技を使う。すべての行で計算するのではなく、例えば「1,000行に1回」バイト位置(あるいは処理行数)を評価し、コンソールの同じ行を書き換えるのだ。

さらに、WScript環境においてコンソールへ流麗に出力するためには、`WScript.StdOut.Write` とキャリッジリターン(`vbCr`)を組み合わせる必要がある。

—

3. 【プロダクションコード】リアルタイム進捗表示付きログ解析エンジン

以下のコードは、数GB規模のログファイルを安全に走査し、コンソールに進捗率をリアルタイムアニメーション表示する完成されたVBScriptである。

‘ ==============================================================================
‘ Script Name : HugeLogProcessor.vbs
‘ Description : 巨大ログファイルを安全に走査し、進捗率をコンソールにリアルタイム表示する
‘ ==============================================================================
Option Explicit

Sub Main()
Dim fso, targetFile, fileSize, ts
Dim targetPath, lineCount, processedBytes, percent
Dim startTime, currentTime, elapsedTime

targetPath = “C:\Logs\application_massive.log”

Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ ファイルの存在確認
If Not fso.FileExists(targetPath) Then
WScript.StdOut.WriteLine “[ERROR] 対象ファイルが存在しません: ” & targetPath
Exit Sub
End If

‘ ファイルサイズ(バイト)の取得
Set targetFile = fso.GetFile(targetPath)
fileSize = targetFile.Size
Set targetFile = Nothing

If fileSize = 0 Then
WScript.StdOut.WriteLine “[INFO] ファイルサイズが0バイトです。”
Exit Sub
End If

WScript.StdOut.WriteLine “=== ログ解析処理を開始します ===”
WScript.StdOut.WriteLine “対象: ” & targetPath
WScript.StdOut.WriteLine “サイズ: ” & FormatNumber(fileSize / 1024 / 1024, 2) & ” MB”
WScript.StdOut.WriteLine “————————————————–”

‘ テキストファイルのオープン (ForReading = 1)
Set ts = fso.OpenTextFile(targetPath, 1, False)

lineCount = 0
processedBytes = 0
startTime = Timer

Do Until ts.AtEndOfStream
Dim currentLine
currentLine = ts.ReadLine
lineCount = lineCount + 1

‘ 1行あたりのバイト数を加算(改行コード分として概算で+2バイト付加)
‘ ※厳密なバイト数が必要な場合はLenBを使用するが、パフォーマンスを考慮してLenで近似
processedBytes = processedBytes + LenB(currentLine) + 2

‘ — 業務ロジック(ここに実際のログ解析・DB登録処理を記述) —
‘ Call ParseLogLine(currentLine)
‘ ————————————————————-

‘ パフォーマンス維持のため、1000行に1回だけ進捗率を計算して描画
If lineCount Mod 1000 = 0 Then
Call ShowProgress(processedBytes, fileSize, lineCount, startTime)
End If
Loop

ts.Close
Set ts = Nothing
Set fso = Nothing

‘ 最終完了メッセージ
currentTime = Timer
elapsedTime = currentTime – startTime
WScript.StdOut.WriteLine vbCrLf & “————————————————–”
WScript.StdOut.WriteLine “[SUCCESS] 処理が正常に完了しました。”
WScript.StdOut.WriteLine “総処理行数: ” & FormatNumber(lineCount, 0) & ” 行”
WScript.StdOut.WriteLine “所要時間: ” & FormatNumber(elapsedTime, 2) & ” 秒”
End Sub

‘ ——————————————————————————
‘ 進捗率をコンソールにインプレース描画するサブルーチン
‘ ——————————————————————————
Sub ShowProgress(currentBytes, totalBytes, lines, startTime)
Dim percent, bar, completedChars, totalChars, i
Dim elapsed, speed, remaining, eta

‘ 割合計算(100を超えないようにガード)
percent = (currentBytes / totalBytes) 100
If percent > 100 Then percent = 100

‘ プログレスバーの生成(幅:30文字)
totalChars = 30
completedChars = Int((percent / 100) totalChars)

bar = “[”
For i = 1 to totalChars
If i <= completedChars Then bar = bar & "=" Else bar = bar & " " End If Next bar = bar & "]" ' 経過時間と推定残り時間の計算 elapsed = Timer - startTime If elapsed > 0 Then
speed = currentBytes / elapsed ‘ Byte/sec
If speed > 0 Then
remaining = (totalBytes – currentBytes) / speed
Else
remaining = 0
End If
Else
remaining = 0
End If

‘ 画面をちらつかせずに同じ行を上書きするため `vbCr` を使用
Dim msg
msg = StringFormat(” {0} {1}% ({2}行) 経過:{3}秒 残り約:{4}秒”, _
Array(bar, FormatPercentDisplay(percent), FormatNumber(lines, 0), Int(elapsed), Int(remaining)))

WScript.StdOut.Write vbCr & msg
End Sub

‘ パーセンテージ表示の整形関数
Function FormatPercentDisplay(p)
Dim s
s = CStr(Int(p 10) / 10)
If InStr(s, “.”) = 0 Then s = s & “.0”
If Len(s) < 4 Then s = Space(4 - Len(s)) & s FormatPercentDisplay = s End Function ' 簡易文字列フォーマット関数 Function FormatFormat(template, args) Dim result, idx result = template For idx = 0 To UBound(args) result = Replace(result & "", "{" & idx & "}", args(idx) & "") Next FormatFormat = result End Function ' StringFormatのエイリアス Function StringFormat(template, args) StringFormat = FormatFormat(template, args) End Function ' エントリーポイント実行 Call Main() ---

4. チーフアーキテクトが解説する実装の急所

このコードが「実務中級者以上」の現場で通用する理由、そしてVBScriptの限界を突破するための技術的ポイントを解説する。

① `WScript.StdOut.Write` と `vbCr` によるちらつきのない描画

`WScript.Echo` は出力のたびに自動で改行コード(CRLF)が付加され、さらにダイアログボックスを表示してしまう(ダブルクリック実行時)。必ず `cscript.exe` で実行させる前提のもと、`WScript.StdOut.Write` で改行なしに出力し、行頭復帰を表す `vbCr`(Carriage Return)を文頭に置くことで、同一行をまるでアニメーションのように上書き更新している。これがコンソールアプリケーションとしてのリッチな挙動を生む。

② モニター頻度の最適化(`Mod 1000` の妙)

すべての行でバイト数を計算し、さらにコンソールに書き込みを行ってはならない。Windowsの標準コンソールI/Oは見た目以上に重い処理である。
`If lineCount Mod 1000 = 0 Then` と制限をかけることで、ファイルIOと描画処理のバランスを最適化し、スクリプト全体のパフォーマンス低下を完全に防いでいる。

③ 経過時間と「推定残り時間(ETA)」の動的算出

単に進捗率(%)を表示するだけでなく、現在の処理速度(Bytes/sec)をベースに残りの必要秒数を動的に割り出している。「あと何秒で終わるかが視覚的に分かる」だけで、ユーザーやオペレーターのストレスは劇的に軽減される。

—

5. 運用時の注意点とさらなる高みへ

  • 実行ホストの指定:

このスクリプトはGUI環境の `wscript.exe` ではなく、CUI環境の `cscript.exe` で実行しなければ意味がない。タスクスケジューラやバッチファイルから呼び出す際は、必ず以下のように明示すること。

cscript //nologo HugeLogProcessor.vbs

  • 文字コードに関する補足:

VBScript標準のFSOはUTF-8(BOMなし)の読み込みに弱い。もし対象ログがUTF-8Nの場合は、ADO.Streamオブジェクト(`ADODB.Stream`)を組み合わせてストリームをラップする設計に拡張すると、より完璧なエコシステムが完成する。

業務自動化ツールであっても、「動けばいい」という時代は終わった。
エンジニアの品格は、こうした細部のユーザー体験(UX)と、リソースへの配慮が行き届いたコードデザインに宿る。ぜひ現場の巨大ログ処理ツールへ組み込んでみてほしい。

タイトルとURLをコピーしました