【実務・中級編】【高速キーワードフィルタ】FileSystemObject ReadLine と正規表現を組み合わせた超巨大ログからの対象行抽出 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【高速キーワードフィルタ】FileSystemObject ReadLine と正規表現を組み合わせた超巨大ログからの対象行抽出

開発現場のインフラやバッチ処理で、突如として直面するのが「数十分の1GB、あるいは数十GBに達する巨大テキストログの解析」だ。
「夜間バッチが異常終了したが、エラーログが巨大すぎてテキストエディタがフリーズする」
「特定のトランザクションIDやエラーコードだけを数千万行のログからミリ秒単位で抜き出したい」

こうした修羅場において、GUIのエディタや、安易に `ReadAll` でファイル全体をメモリにロードするアプローチは自殺行為に等しい。VBScript(WSH)であっても、適切なオブジェクトのライフサイクル管理とストリーミング処理を実装すれば、モダンな言語に引けを取らない省メモリかつ高速なログ抽出エンジンを構築できる。

今回は、業務自動化の現場で即座に使える、「FileSystemObject (FSO) の ReadLine」「VBScript RegExp」を極限まで最適化したプロダクションコードを伝授する。

なぜ「あの書き方」ではメモリが枯渇するのか?

多くのプログラマ(あるいは生成AIの初期出力)は、巨大ファイルを扱う際に次のようなコードを書く。

‘ 【アンチパターン】絶対にやってはいけない実装
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set file = fso.OpenTextFile(“C:\Logs\huge_monster.log”, 1)
allText = file.ReadAll ‘ ← ここで数十GBのファイルを一気にメモリへ展開!
file.Close

‘ 配列に分割
lines = Split(allText, vbCrLf)
For i = 0 To UBound(lines)
‘ 処理…
Next

チーフアーキテクトの警告:

1. `ReadAll` の呪縛: ファイルサイズを超えるメモリ(VBScriptのバッファ上限やOSの仮想記憶の限界)を瞬時に消費し、Out of Memory (メモリ不足) でスクリプトがクラッシュする。
2. `Split` によるメモリ倍増: 巨大な文字列を `Split` すると、元データの数倍のメモリ断片(ヒープ領域)が生成され、GC(ガベージコレクション)が追いつかずにプロセスがスワップアウトを起こす。

【正しいアプローチ】
ファイルを1行ずつ読み込む `ReadLine` によるストリーミング処理 を採用し、メモリ上のフットプリントを常に「1行分+α」に固定しなければならない。

堅牢なログフィルタリングエンジンの設計思想

今回のプロダクションコードでは、以下の要件を満たす設計を組んでいる。

1. 完全なストリーミング処理: `OpenTextFile` の `ReadLine` を使用し、EOF(ファイルの終端)まで1行ずつメモリにロードして破棄を繰り返す。
2. 正規表現オブジェクトのループ外インスタンス化: `RegExp` の生成をループ内で行うと、COMオブジェクトの生成・破棄コストで性能が激落ちする。必ずループの外部で1回だけ生成し、`Pattern` プロパティを使い回す。
3. 文字コードの考慮: Windows環境のログに多い `Shift-JIS (CP932)` や `UTF-8` に対するファイルオープン時の注意点(FSOの仕様の限界と回避策)をクリアする。
4. I/Oの最適化: 抽出結果の書き込みには `CreateTextFile` を用い、不要なバトリングを避けるため適切なタイミングでバッファを流す。

プロダクションコード:`LogFilterEngine.vbs`

以下のコードをメモ帳などに貼り付け、拡張子を `.vbs` として保存して実行してほしい。実務でそのまま組み込める堅牢なエラーハンドリングと構造化を行っている。

‘ ==============================================================================
‘ 処理名: 超巨大ログ高速キーワードフィルタリングエンジン
‘ 概要 : FileSystemObjectのストリーミング読み込みと正規表現を組み合わせ、
‘ メモリ枯渇を起こさずに数GB超のログから対象行を高精度・高速抽出する。
‘ 依存 : WScript / FileSystemObject (FSO) / VBScript.RegExp
‘ ==============================================================================

Option Explicit

‘ — 設定パラメータ —
Const LOG_FILE_PATH = “C:\Logs\production_giant_2023.log”
Const OUT_FILE_PATH = “C:\Logs\extracted_errors.log”
Const TARGET_PATTERN = “\[ERROR\]|\[FATAL\]|ERR-\d{4}” ‘ 抽出したい正規表現パターン

Sub Main()
Dim fso, tsIn, tsOut
Dim lineCount, matchCount
Dim objRegExp
Dim currentLine
Dim startTime

startTime = Timer
lineCount = 0
matchCount = 0

‘ 1. オブジェクトの初期化
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ ファイル存在チェック
If Not fso.FileExists(LOG_FILE_PATH) Then
WScript.Echo “【致命的エラー】対象ログファイルが存在しません: ” & LOG_FILE_PATH
Exit Sub
End If

‘ 2. 正規表現エンジンの事前コンパイル(※ループ内で生成しないのが鉄則)
Set objRegExp = New RegExp
objRegExp.Pattern = TARGET_PATTERN
objRegExp.IgnoreCase = True ‘ 大文字小文字を区別しない
objRegExp.Global = True ‘ 行内の複数マッチング有効化

On Error Resume Next

‘ 3. ストリームのオープン (ForReading = 1)
‘ ※注意: FSOのOpenTextFileはデフォルトでANSI(Shift-JIS)。
‘ UTF-8ファイルを扱う場合はADODB.Streamを経由する設計変更が必要。
Set tsIn = fso.OpenTextFile(LOG_FILE_PATH, 1, False)
Set tsOut = fso.CreateTextFile(OUT_FILE_PATH, True, False) ‘ 上書き作成, Unicode=False

If Err.Number <> 0 Then
WScript.Echo “【I/Oエラー】ファイルを開けませんでした: ” & Err.Description
Exit Sub
End If

WScript.Echo “>>> ログフィルタリング処理を開始します…”

‘ 4. メモリを圧迫しない1行ずつのストリーミング走査
Do While Not tsIn.AtEndOfStream
currentLine = tsIn.ReadLine
lineCount = lineCount + 1

‘ 正規表現マッチ判定
If objRegExp.Test(currentLine) Then
tsOut.WriteLine currentLine
matchCount = matchCount + 1
End If

‘ 進捗確認用ログ(10万行ごとにコンソール出力)
If lineCount Mod 100000 = 0 Then
WScript.Echo lineCount & ” 行スキャン完了… (現在ヒット数: ” & matchCount & “)”
End If
Loop

‘ 5. クリーンアップ
tsIn.Close
tsOut.Close

On Error GoTo 0

‘ オブジェクトの解放
Set tsOut = Nothing
Set tsIn = Nothing
Set objRegExp = Nothing
Set fso = Nothing

‘ 6. 完了レポート
WScript.Echo “==========================================”
WScript.Echo ” 処理が正常終了しました。”
WScript.Echo ” 総スキャン行数 : ” & FormatNumber(lineCount, 0) & ” 行”
WScript.Echo ” 抽出ヒット数 : ” & FormatNumber(matchCount, 0) & ” 行”
WScript.Echo ” 処理時間 : ” & FormatNumber(Timer – startTime, 2) & ” 秒”
WScript.Echo ” 出力先 : ” & OUT_FILE_PATH
WScript.Echo “==========================================”

End Sub

‘ 処理の実行
Main()

チーフアーキテクトが教える、現場でコケないための実装上の急所

1. 文字コード(UTF-8 / Shift-JIS)の罠

VBScriptの `FileSystemObject` の `OpenTextFile` メソッドは、歴史的な背景からANSI(Shift-JIS等)もしくはUnicode (UTF-16LE) しかネイティブで正しく読み込めない。
もし対象の巨大ログが UTF-8 (BOMなし) である場合、FSOで直接開くと日本語が文字化けし、正規表現の判定が狂う。
完全なUTF-8ログを扱う必要がある場合は、FSOではなく `ADODB.Stream` オブジェクトをストリームとして採用する設計に切り替える必要がある。

‘ 【参考】UTF-8ログを安全にストリーミング処理する場合のStream初期化スニペット
Dim adoStream
Set adoStream = CreateObject(“ADODB.Stream”)
adoStream.Type = 2 ‘ adTypeText
adoStream.Charset = “UTF-8”
adoStream.Open
adoStream.LoadFromFile LOG_FILE_PATH

Do While Not adoStream.EOS
currentLine = adoStream.ReadText(-1) ‘ ※注意: ADODBのReadText(-1)は全行読み込みになるため、
‘ 厳密な1行読み込みには ReadLine を使うこと
‘ …
Loop

※実務において、巨大ログの文字コードがShift-JISであれば今回のFSOコードが最もオーバヘッドが少なく最速である。インフラ側の出力仕様をShift-JISに固定させることも、業務効率化の立派なアーキテクチャ判断と言える。

2. エラーハンドリングとリソースリークの防止

VBScriptで最も恐ろしいのは、途中で予期せぬエラー(ディスク容量不足、アクセス権限喪失など)が発生した際、ファイルハンドルが開きっぱなしになり、OSのロックが解除されなくなる現象だ。
プロダクションコードでは `On Error Resume Next` を適切にスコープを絞って使用し、異常終了時であっても確実に `Close` および `Set xxx = Nothing` による参照カウンタの解放(COMオブジェクトのライフサイクル管理)を行う防衛的プログラミングが不可欠となる。

結び:VBScriptを侮るな

「いまどきVBScriptか」とやゆする声を聞くことがある。しかし、Windows環境が標準で備えるWSH/VBScriptは、追加のランタイムインストールや複雑なビルドプロセスを必要とせず、メモ帳一つでインフラの急場を救う最強の「即効性インフラツール」である。

オブジェクトの挙動、メモリモデル、そしてI/Oのボトルネックを正しく理解していれば、数十GBのデータであってもVBScriptは軽快に、そして確実に業務を完遂する。
現場の信頼を勝ち取る堅牢なスクリプト設計を、あなたの武器庫に加えてほしい。

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