【実務・中級編】【実務中級者向け】FSOログ解析による特定アラート(FATAL/WARN)出現回数カウントと閾値超過メッセージ生成 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

ログ解析スクリプトを「ただの使い捨てツール」で終わらせるな:FSOを用いた堅牢な異常検知設計論

「ログファイルを読み込んで、エラーの回数を数える。そんなの、数行のコードで書けるじゃないか」

もし君がそう考えているなら、君はまだ「自動化エンジニア」ではなく、単なる「スクリプトの書き手」に過ぎない。

実務、それもミッションクリティカルな運用環境において、スクリプトは「動けばいい」のではない。「予測不能な事態に直面しても、システムを壊さず、正しく失敗し、確実に結果を報告すること」が求められる。

今回は、FileSystemObject (FSO) を用いたログ解析において、中級者が陥りがちな罠を回避し、プロフェッショナルが設計すべき「堅牢な集計・通知システム」の極意を伝授する。

1. 素人が書くコード、プロが設計するアーキテクチャ

多くの開発者が書く「典型的な失敗例」は、以下のような構造だ。

  • ファイルを開く $\rightarrow$ 全行ループ $\rightarrow$ 文字列比較 $\rightarrow$ カウント $\rightarrow$ 終了。

これでは、以下のリスクを制御できない。
1. ファイルロック問題: ログ出力プロセスがファイルを開いている最中に、読み取り権限エラーでスクリプトが即死する。
2. メモリ枯渇: 数GBある巨大なログファイルを `ReadAll` で一気にメモリに展開し、サーバーをフリーズさせる。
3. エラーの隠蔽: `On Error Resume Next` を乱用し、なぜ解析に失敗したのか分からぬまま「異常なし」として処理を終えてしまう。

プロフェッショナルは、「リソースの局所化」「ストリーム処理による低メモリ消費」「厳格なエラーハンドリング」を設計の根幹に据える。

2. 堅牢な実装のための3つの鉄則

① Dictionaryオブジェクトによる集計の高速化

`If`文を重ねて変数を増やすような泥臭い実装はやめろ。`Scripting.Dictionary` を使い、キーワードをキー、出現回数を値として管理せよ。これにより、監視対象のログレベル(FATAL, WARN, ERROR等)が増えたとしても、コードのロジックを変更せずに拡張できる。

② Line-by-Line 処理の徹底

巨大なログファイルは、メモリの敵だ。`ReadLine` メソッドを用い、一行ずつ読み込みながら判定を行う。これにより、メモリ使用量を一定(定数レベル)に保ち、ファイルサイズに依存しないパフォーマンスを実現できる。

③ 「失敗」を設計に組み込む

ファイルが存在しない、読み取り権限がない、ディスクフルで書き込めない……。これらの例外を「想定外」として扱うな。すべての例外に対して、適切なログ出力または通知を行う構造を構築せよ。

3. プロダクション・クオリティのコード例

以下に、実務でそのまま利用可能な、堅牢性を担保したVBScriptのテンプレートを示す。これは単なるサンプルではない。保守性と信頼性を考慮した設計図だ。

' ==============================================================================
' Script Name: LogAnalyzer_Pro.vbs
' Description: FSOを用いたログ解析および閾値超過検知システム
' Author: Chief Architect
' ==============================================================================
Option Explicit

' --- 設定領域 (Configuration) ---
' 本来は外部のINIファイルや環境変数から読み込むべきだが、ここでは定数として定義
Const TARGET_LOG_PATH    = "C:\Logs\app_server.log"
Const SUMMARY_OUT_PATH   = "C:\Logs\alert_summary.txt"
Const THRESHOLD_FATAL    = 1  ' FATALは1回でも即通知
Const THRESHOLD_WARN     = 5  ' WARNは5回以上で通知
Const LOG_LEVEL_FATAL    = "FATAL"
Const LOG_LEVEL_WARN     = "WARN"

' --- メインルーチン ---
Main

Sub Main()
    Dim fso, dictCounts, strSummary
    Dim errCode, errDesc

    Set fso = CreateObject("Scripting.FileSystemObject")
    Set dictCounts = CreateObject("Scripting.Dictionary")

    ' 1. 初期化: カウント用の辞書をセットアップ
    dictCounts.Add LOG_LEVEL_FATAL, 0
    dictCounts.Add LOG_LEVEL_WARN, 0

    ' 2. 解析処理の実行
    On Error Resume Next
    Call AnalyzeLogFile(fso, dictCounts)
    
    If Err.Number <> 0 Then
        errCode = Err.Number
        errDesc = Err.Description
        On Error GoTo 0
        Call HandleFatalError("解析中に致命的なエラーが発生しました: " & errDesc & " (Code: " & errCode & ")")
        Exit Sub
    End If
    On Error GoTo 0

    ' 3. 閾値判定とサマリー生成
    strSummary = GenerateSummary(dictCounts)

    ' 4. 結果の出力(閾値を超えた場合のみ書き出し)
    If strSummary <> "" Then
        If Not WriteSummaryFile(fso, strSummary) Then
            ' 書き出し失敗は運用上の重大な問題
            Call HandleFatalError("サマリーファイルの書き出しに失敗しました。")
        End If
    End If

    ' 5. 後処理 (Cleanup)
    Set dictCounts = Nothing
    Set fso = Nothing
    
    ' WScript.Echo "Analysis completed successfully." ' デバッグ用
End Sub

' --- 解析エンジン ---
Sub AnalyzeLogFile(objFSO, objDict)
    Dim objFile, strLine
    
    ' ファイル存在チェック
    If Not objFSO.FileExists(TARGET_LOG_PATH) Then
        Err.Raise 1, "AnalyzeLogFile", "Target log file not found: " & TARGET_LOG_PATH
    End If

    ' ファイルを読み取り専用(1)でオープン
    ' ※ 他のプロセスが書き込み中でも、読み取りモードならアクセス可能なケースが多い
    Set objFile = objFSO.OpenTextFile(TARGET_LOG_PATH, 1) 

    Do Until objFile.AtEndOfStream
        strLine = objFile.ReadLine
        
        ' キーワード判定(大文字小文字を区別しない場合は UCase を使用)
        If InStr(strLine, LOG_LEVEL_FATAL) > 0 Then
            objDict(LOG_LEVEL_FATAL) = objDict(LOG_LEVEL_FATAL) + 1
        ElseIf InStr(strLine, LOG_LEVEL_WARN) > 0 Then
            objDict(LOG_LEVEL_WARN) = objDict(LOG_LEVEL_WARN) + 1
        End If
    Loop

    objFile.Close
    Set objFile = Nothing
End Sub

' --- 判定ロジック ---
Function GenerateSummary(objDict)
    Dim strResult, fatalCount, warnCount
    fatalCount = objDict(LOG_LEVEL_FATAL)
    warnCount = objDict(LOG_LEVEL_WARN)
    
    strResult = ""

    ' 閾値判定
    If fatalCount >= THRESHOLD_FATAL Or warnCount >= THRESHOLD_WARN Then
        strResult = "[ALERT] Log Threshold Exceeded" & vbCrLf & _
                    "Timestamp: " & Now & vbCrLf & _
                    "--------------------------------" & vbCrLf & _
                    "FATAL count: " & fatalCount & " (Threshold: " & THRESHOLD_FATAL & ")" & vbCrLf & _
                    "WARN  count: " & warnCount & " (Threshold: " & THRESHOLD_WARN & ")" & vbCrLf & _
                    "--------------------------------" & vbCrLf
    End If
    
    GenerateSummary = strResult
End Function

' --- 出力エンジン ---
Function WriteSummaryFile(objFSO, strContent)
    On Error Resume Next
    Dim objFile
    ' ForWriting (2) を使用。既存ファイルがある場合は上書き。
    ' 運用要件に応じて ForAppending (8) への変更を検討せよ。
    Set objFile = objFSO.CreateTextFile(SUMMARY_OUT_PATH, True)
    
    If Err.Number <> 0 Then
        WriteSummaryFile = False
    Else
        objFile.WriteLine strContent
        objFile.Close
        WriteSummaryFile = True
    End If
    On Error GoTo 0
End Function

' --- エラーハンドリング ---
Sub HandleFatalError(strMsg)
    ' 本来はここでイベントログへの書き込みや、管理者へのメール送信を行う
    ' ここでは標準エラー出力へ
    WScript.StdErr.WriteLine "[CRITICAL ERROR] " & strMsg
    ' 運用監視ツール(Zabbix/Nagios等)が戻り値を監視できるよう、終了コードを返す
    WScript.Quit 1
End Sub

4. チーフアーキテクトの視点:さらに高みを目指すために

このスクリプトを実戦投入するにあたり、以下の「運用上の勘所」を忘れないでほしい。

文字コードの罠

FSOの `OpenTextFile` は、基本的に ANSI (Shift-JIS) を前提としている。もし解析対象のログが UTF-8 で出力されている場合、このスクリプトは文字化けを起こし、キーワード検知に失敗する。
現代的なシステムではUTF-8が標準だ。その場合は、`ADODB.Stream` オブジェクトを使用し、`Charset = “UTF-8″` を明示的に指定する設計に切り替えろ。

冪等性(Idempotency)の確保

このスクリプトをタスクスケジューラで5分おきに実行する場合、同じエラーを何度もカウントし続けていないか?
「前回どこまで読み込んだか」を管理する チェックポイント・ファイル(ポインタ管理) を導入する設計にステップアップせよ。前回の最終行番号を記録しておけば、差分のみを高速かつ正確にスキャンできる。

終了コードの重要性

`WScript.Quit 1` を適切に使い分けること。監視システムは、スクリプトの中身を見るのではない。「戻り値(Exit Code)」 を見て、死んでいるのか生きているのかを判断するのだ。

結びに

自動化とは、単に作業を減らすことではない。「人間が介在すべきでない判断を、機械に、かつ確実に、ミスなく行わせること」 である。

FSOを使いこなすということは、オブジェクトの挙動、メモリの消費、そしてエラーの発生確率をコントロール下に置くということだ。次に君がコードを書くときは、必ず「もしこのファイルが巨大だったら?」「もしこのファイルがロックされていたら?」と自問自答してほしい。

それが、プロフェッショナルへの第一歩だ。