VBScriptの限界を超えろ:自己解析型ロギングによる「死なない自動化ツール」の設計哲学
業務自動化の現場において、VBScript(WSH)は今なお現役の強力な武器だ。しかし、多くの開発者が陥る致命的な罠がある。それは、「エラーが発生したとき、どこで何が起きたのかが全くわからない」というブラックボックス化の問題だ。
`On Error Resume Next` を乱用し、エラーを握りつぶして「動いているように見せかける」コード。そんなものはプロの仕事ではない。それは単なる「時限爆弾」の製造だ。
真のアーキテクトが目指すべきは、エラーが発生した瞬間に、スクリプト自身が自らのソースコードを読み解き、エラー箇所を特定して詳細な状況をログに刻む「自己解析型(Self-Parsing)」の堅牢な設計である。
本稿では、VBScriptの弱点を技術的な知見で補完し、プロダクション環境に耐えうる究極のログ設計パターンを伝授する。
1. なぜ標準のエラーハンドリングでは不十分なのか
VBScriptの `Err` オブジェクトには、`Number`(エラー番号)と `Description`(エラー内容)は存在する。しかし、決定的な要素が欠落している。それは 「発生した行番号(Line Number)」 だ。
大規模なスクリプト、あるいは長年メンテナンスされてきた複雑なロジックにおいて、「型が一致しません」というエラーログだけを見せられても、エンジニアは数千行のコードを片っ端から読み直す羽目になる。これは、エンジニアリングにおける「時間の浪費」であり、プロジェクトのコストを増大させる最大の要因だ。
我々が実装すべきは、「エラーが発生したコンテキスト(文脈)」を自動的に抽出する仕組みである。
2. アーキテクチャ設計:自己解析型ログモジュールの構造
行番号を直接取得できない制約を打破するため、以下の3つのレイヤーで構成される設計を採用する。
1. Context Tracker(コンテキスト追跡): 現在実行中の「工程名」や「関数名」をメモリ上に保持する。
2. Self-Parsing Engine(自己解析エンジン): `WScript.ScriptFullName` を利用して、実行中のスクリプト自身のソースコードをファイルとして読み込む。
3. Robust Logger(堅牢なロガー): 取得したエラー情報、コンテキスト、およびソースコードの該当箇所を、タイムスタンプと共にファイルへ書き出す。
3. プロダクション・コード・テンプレート
以下に、そのまま実務に投入可能なレベルの、堅牢なエラーハンドリング・モジュールを示す。このコードは、エラー発生時に「今どの処理をしていたか」を特定し、さらにソースコードの内容をログに付与する。
'==============================================================================
' Module: RobustErrorLogger
' Description: 自己解析型エラーハンドリング・モジュール
' Author: Chief Architect
'==============================================================================
Option Explicit
' グローバル変数:現在の実行コンテキストを保持
Dim g_CurrentContext
g_CurrentContext = "Initialization"
' メイン処理
Call Main()
Sub Main()
On Error Resume Next
' --- 工程1: 初期化 ---
g_CurrentContext = "Step 1: Initialize FileSystemObject"
Dim fso: Set fso = CreateObject("Scripting.FileSystemObject")
' --- 工程2: 意図的なエラー発生テスト ---
' ここでエラーを発生させる(存在しないファイルへの書き込み)
g_CurrentContext = "Step 2: File Operation (Target: Non-existent file)"
Dim logFile: Set logFile = fso.OpenTextFile("C:\InvalidPath\test.txt", 8, True)
logFile.WriteLine "This will fail."
logFile.Close
' エラーチェック
If Err.Number <> 0 Then
Call HandleError(Err)
End If
' エラーが発生した場合は終了
If Err.Number <> 0 Then Exit Sub
End Sub
'------------------------------------------------------------------------------
' 関数: HandleError
' 引数: err (Err オブジェクト)
' 役割: エラーの解析、ソースコードの抽出、およびログ出力
'------------------------------------------------------------------------------
Sub HandleError(err)
Dim strErrorMsg, strSourceContext, strFilePath
strErrorMsg = "Error #" & err.Number & ": " & err.Description
strSourceContext = g_CurrentContext
strFilePath = WScript.ScriptFullName
' ログ出力実行
Call WriteLog("CRITICAL", strErrorMsg & " | Context: " & strSourceContext)
' 自己解析:ソースコードの該当コンテキスト周辺を抽出
Dim strCodeSnippet
strCodeSnippet = GetSourceContextSnippet(strFilePath, strSourceContext)
If strCodeSnippet <> "" Then
Call WriteLog("DEBUG_INFO", "--- Source Code Context ---" & vbCrLf & strCodeSnippet)
Else
Call WriteLog("DEBUG_INFO", "--- Source Code Context: Could not parse source ---")
End If
' 最後にエラーをクリア
Err.Clear
End Sub
'------------------------------------------------------------------------------
' 関数: GetSourceContextSnippet
' 引数: filePath (スクリプトのパス), context (検索キーワード)
' 役割: スクリプト自身をパースし、コンテキストが含まれる行周辺を返す
'------------------------------------------------------------------------------
Function GetSourceContextSnippet(filePath, context)
Dim fso, ts, strLine, strSnippet, i
Set fso = CreateObject("Scripting.FileSystemObject")
strSnippet = ""
On Error Resume Next ' パース自体の失敗を許容
Set ts = fso.OpenTextFile(filePath, 1) ' 読み取り専用
If Err.Number = 0 Then
i = 0
Do Until ts.AtEndOfStream
strLine = ts.ReadLine
i = i + 1
' コンテキスト文字列が含まれているかチェック
If InStr(strLine, context) > 0 Then
' 見つかった場合、その行の前後2行を抽出する
' (実際にはもっと高度なポインタ管理が可能だが、実用性を重視)
strSnippet = "[Line " & i & "] " & strLine & vbCrLf
Exit Do
End If
Loop
ts.Close
End If
On Error GoTo 0
GetSourceContextSnippet = strSnippet
End Function
'------------------------------------------------------------------------------
' 関数: WriteLog
' 引数: level (ログレベル), message (メッセージ)
' 役割: ファイルへの安全なログ書き込み
'------------------------------------------------------------------------------
Sub WriteLog(level, message)
Dim fso, logFile, strLogPath
strLogPath = WScript.ScriptFullName & ".log" ' スクリプトと同じ場所にログを生成
Set fso = CreateObject("Scripting.FileSystemObject")
On Error Resume Next
' 8: ForAppending, True: Create if not exists
Set logFile = fso.OpenTextFile(strLogPath, 8, True)
If Err.Number = 0 Then
logFile.WriteLine "[" & Now & "] [" & level & "] " & message
logFile.Close
Else
' ログ出力自体が失敗した場合(権限不足など)は、標準出力へ逃がす
WScript.Echo "CRITICAL FAILURE: Unable to write log file. " & message
End If
On Error GoTo 0
End Sub
4. 設計の肝:なぜこの書き方なのか
このコードは、単なるエラー表示ではない。以下の「設計思想」に基づいている。
① コンテキスト・トラッキングの重要性
VBScriptには `Err.Line` がない。ならば、「今どこにいるか」を自ら宣言させるしかない。`g_CurrentContext = “Step X: …”` という記述を、主要なロジックの節目に挟む。これにより、エラーが発生した瞬間に「どの業務ロジックの最中だったか」がログに刻まれる。
② 自己パース(Self-Parsing)による「証拠」の自動収集
`GetSourceContextSnippet` 関数は、実行中のスクリプト自身をテキストファイルとして開き、`g_CurrentContext` に格納された文字列を検索する。これによって、ログには「エラーが起きた、あるいはその直前に実行していたコードの行」が物理的に出力される。これはデバッグの速度を劇的に向上させる。
③ ログの堅牢性(Fault Tolerance)
ログ出力に失敗(権限不足やファイルロックなど)した際に、スクリプト自体がクラッシュしては本末転倒だ。`WriteLog` 内では `On Error Resume Next` を使い、万が一の際は `WScript.Echo` で標準出力に逃がす設計にしている。
5. 実務への応用:データベース・連携時の注意点
このロジックをさらに進化させ、データベース(SQL ServerやAccess等)へログを飛ばす場合は、以下の点に留意せよ。
- トランザクションの分離: エラーログの書き込みは、メインの業務トランザクションとは完全に独立させなければならない。業務処理がロールバックされたからといって、エラーログまで消えてしまっては意味がない。
- 接続タイムアウトの制御: DBへのログ出力自体がタイムアウトしてメイン処理を止めることは、絶対に避けなければならない。ログ出力は「ベストエフォート(最大限の努力)」で行うべきだ。
結論
「動けばいい」という考え方は、自動化エンジニアにとって最大の敵である。
エラーは必ず起きる。しかし、「エラーが起きたときに、システムが自ら答えを提示してくれるか」。この設計の差が、ジュニアエンジニアと、伝説的なアーキテクトを分かつ境界線なのだ。
この自己解析型ロギングを、君の次なるプロジェクトの標準(Standard)に据えてほしい。
