【実務・中級編】【マルチ端末実行ログ安全一元化】共有フォルダへのログ書き込み衝突を回避する追記ロック・転送アルゴリズム – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【マルチ端末実行ログ安全一元化】共有フォルダへのログ書き込み衝突を回避する追記ロック・転送アルゴリズム

開発現場でよくある悪夢を語ろう。
朝一、あるいは一斉定時処理のタイミングで、数十台のクライアント端末からVBScriptが社内LANの共有フォルダへ一斉にログを書き込みに行く。
結果はどうなるか? 「権限がありません」「別のプロセスによってファイルが使用されています」 という無慈悲なエラーの嵐。ログは文字化けして吹き飛び、トラブルシューティングすらできない。

「ファイルI/Oの排他制御なんて、VBScriptごときに求めすぎだ」などと諦めていないか?
いや、WSH(Windows Script Host)の特性とOSのプリミティブを正しく理解していれば、VBScriptであっても極めて堅牢なログ集約基盤を構築することは十分に可能だ。

今回は、ネットワークの瞬断やファイル競合を完全にいなす 「一時ローカルバッファ + 非同期風アトミック転送 + リトライアルゴリズム」 を実装した、プロダクション品質のログ一元化モジュールを伝授する。

なぜ素朴な `ForAppending` は破綻するのか?

多くのプログラマブルな初心者がやりがちなのが、以下のコードだ。

‘ 【アンチパターン】絶対にやってはいけない直接書き込み
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
‘ 共有フォルダのファイルを開く(複数端末から同時に叩かれると即死する)
Set objFile = objFSO.OpenTextFile(“\\server\share\logs\app.log”, 8, True)
objFile.WriteLine “Log message…”
objFile.Close

致命的な理由

1. ネットワークファイルロックの脆弱性: SMBプロトコル経由でのファイルオープンは、OS間のタイミングによって容易に衝突(Sharing Violation)を起こす。
2. I/O待ちの非効率: 共有フォルダへの直接アクセスは、ネットワークの遅延をダイレクトに受けるため、スクリプト全体の実行速度が致命的に低下する。
3. トランザクションの欠落: 書き込みの途中で端末がシャットダウンしたりネットワークが切断されたりすると、ファイルが破損(ゼロバイト化や文字化け)する。

堅牢なログ集約アーキテクチャの設計思想

この問題を根本から解決するアプローチは一つしかない。
「共有フォルダへ直接書くな。まずローカルに安全に蓄え、一瞬の隙を突いてアトミック(不可分)に転送・追記せよ」

[各クライアント端末]
VBScript実行
↓ 1. まずローカルの安全な領域に一時保存(完全ノンブロッキング)
C:\Temp\AppLog\local.log
↓ 2. 処理終了間際、ファイルロックを伴う排他制御で共有フォルダへ安全に「アトミック追記」
[共有フォルダ \\server\share\logs\master.log]

この設計により、ネットワーク上の競合時間は極限まで短縮され、万が一のネットワーク切断時もローカルにログが確実に保全される。

プロダクションコード:セキュア・ログ集約モジュール

実務の現場でそのままコピー&ペーストし、即座に組み込めるクラスライブラリ形式(VBScriptのクラス構文)の完成形コードだ。エラーハンドリングとリトライ機構を完璧に組み込んである。

‘ ==============================================================================
‘ 共有フォルダ安全ログ集約モジュール (SecureLogger.vbs)
‘ Author: Chief System Architect
‘ Description: ローカルバッファリングと排他リトライにより、書き込み衝突を完全に回避
‘ ==============================================================================

Class SecureLogger
Private m_FSO
Private m_LocalLogPath
Private m_SharedLogPath
Private m_ComputerName

‘ コンストラクタ
Private Sub Class_Initialize()
Set m_FSO = CreateObject(“Scripting.FileSystemObject”)

‘ コンピュータ名を取得(マルチ端末環境での識別用)
Dim objNet
Set objNet = CreateObject(“WScript.Network”)
m_ComputerName = objNet.ComputerName
Set objNet = Nothing

‘ ローカルの一時保存パスを決定 (%TEMP%\AppExecutionLogs\)
Dim wshShell
Set wshShell = CreateObject(“WScript.Shell”)
m_LocalLogPath = wshShell.ExpandEnvironmentStrings(“%TEMP%”) & “\AppExecutionLogs_” & m_FSO.GetTempName & “.log”
Set wshShell = Nothing

‘ 共有フォルダ側のマスターログパス(環境に合わせて変更してください)
m_SharedLogPath = “\\your-server\share\logs\master_execution.log”
End Sub

‘ デストラクタ(スクリプト終了時にローカル一時ファイルをクリーンアップ)
Private Sub Class_Terminate()
On Error Resume Next
If m_FSO.FileExists(m_LocalLogPath) Then
m_FSO.DeleteFile m_LocalLogPath, True
End If
Set m_FSO = Nothing
End Sub

‘ 1. ローカルバッファへの書き込み(処理中は一切ネットワークに触れないため高速かつ安全)
Public Sub Log(ByVal strMessage, ByVal strLevel)
On Error Resume Next
Dim objFile, timestamp
timestamp = Now()

‘ ログフォーマット: [日時] [レベル] [端末名] メッセージ
Dim logLine
logLine = “[” & FormatDateTime(timestamp, 0) & “] [” & UCase(strLevel) & “] [” & m_ComputerName & “] ” & strMessage

‘ フォルダーが存在しない場合は作成
Dim parentDir
parentDir = m_FSO.GetParentFolderName(m_LocalLogPath)
If Not m_FSO.FolderExists(parentDir) Then
m_FSO.CreateFolder parentDir
End If

‘ 追記モードでローカルファイルへ書き込み (ForAppending = 8)
Set objFile = m_FSO.OpenTextFile(m_LocalLogPath, 8, True)
objFile.WriteLine logLine
objFile.Close
Set objFile = Nothing

If Err.Number <> 0 Then
‘ ローカル書き込み失敗時はイベントログ等へのフォールバックを検討すべきだが、今回はWScript.Echoに留める
WScript.Echo “CRITICAL: Local log buffer write failed: ” & Err.Description
Err.Clear
End If
End Sub

‘ 2. 共有フォルダへの一括フラッシュ(スクリプトの最後に実行)
Public Function FlushToSharedStorage()
If Not m_FSO.FileExists(m_LocalLogPath) Then
FlushToSharedStorage = True
Exit Function
End If

Const MaxRetries = 5
Const RetryDelayMs = 2000 ‘ 2秒待機
Dim attempt, success
success = False

For attempt = 1 To MaxRetries
On Error Resume Next

‘ 共有フォルダの親ディレクトリの存在確認・作成
Dim sharedDir
sharedDir = m_FSO.GetParentFolderName(m_SharedLogPath)
If Not m_FSO.FolderExists(sharedDir) Then
‘ 共有フォルダ自体にアクセスできない場合のハンドリング
If Err.Number <> 0 Then
WScript.Echo “WARNING: Shared storage inaccessible. Retrying… (” & attempt & “/” & MaxRetries & “)”
WScript.Sleep RetryDelayMs
Err.Clear
Exit For ‘ ネットワーク障害時はループを抜けてローカル保持へ逃げる
End If
End If

‘ 【排他制御の核心】
‘ 共有ログを開いてローカルの内容を追記する
Dim sharedFile, localFile, content

‘ ローカルログの全読み込み
Set localFile = m_FSO.OpenTextFile(m_LocalLogPath, 1, False)
content = localFile.ReadAll
localFile.Close
Set localFile = Nothing

‘ 共有ファイルに追記(他端末と競合した場合はエラーになるため、そこでリトライが活きる)
Set sharedFile = m_FSO.OpenTextFile(m_SharedLogPath, 8, True)
sharedFile.Write content
sharedFile.Close
Set sharedFile = Nothing

If Err.Number = 0 Then
success = True
Exit For ‘ 成功したらループ脱出
Else
‘ 競合エラー(Permission Denied / File in use)の場合、少し待ってリトライ
WScript.Echo “WARN: Log sync conflict detected. Retrying in ” & (RetryDelayMs/1000) & “s… (Attempt ” & attempt & “)”
Err.Clear
WScript.Sleep RetryDelayMs
End If
Next

FlushToSharedStorage = success
End Function
End Class

実際にこのモジュールを組み込むメインスクリプトの例

上記のクラスをどのように実務のルーチンに組み込むべきか、実装例を示す。

‘ ==============================================================================
‘ メイン実行スクリプト (Main.vbs)
‘ ==============================================================================

‘ クラスファイルのインクルード(または同ファイル内記述)
‘ #Include “SecureLogger.vbs” の代わりに関数や別ファイル読み込みを想定

Dim logger
Set logger = New SecureLogger

‘ 処理開始
logger.Log “業務自動化処理を開始します。”, “INFO”

On Error Resume Next
‘ — ここに実際の業務ロジック(Excel操作、API通信、DB処理など)を書く —
‘ 例としてダミーの処理時間を挟む
WScript.Sleep 1000

If Err.Number <> 0 Then
logger.Log “エラーが発生しました: ” & Err.Description, “ERROR”
Err.Clear
Else
logger.Log “業務自動化処理が正常に完了しました。”, “INFO”
End If

‘ 最後に必ず共有ストレージへフラッシュ(転送)を実行
Dim isSynced
isSynced = logger.FlushToSharedStorage()

If Not isSynced Then
‘ 万が一、ネットワーク障害等で共有フォルダへ転送できなかった場合の最終防衛ライン
WScript.Echo “FATAL: 共有フォルダへのログ同期に失敗しました。ログはローカル端末に保持されています。”
‘ 必要に応じてローカルファイルを別の退避フォルダへコピーする処理をここに記述
End If

Set logger = Nothing
WScript.Quit 0

チーフアーキテクトからの実務アドバイス:運用上の注意点

1. NTFSアクセス権の罠
共有フォルダ側のフォルダ権限において、各クライアント端末の実行ユーザー(またはSYSTEM権限)に 「変更(Modify)」および「作成(Create Files / Append Data)」 権限が確実に付与されていることを確認しろ。読み取り専用やファイル削除権限が欠けていると、FSOの `OpenTextFile(…, 8, True)` は沈黙のままエラーを吐く。

2. ログファイルの肥大化とローテーション
一元化された `master_execution.log` は、数ヶ月放置すれば数GBの怪物ファイルへと成長し、メモ帳ですら開けなくなる。タスクスケジューラ等から月に一度、別名保存(アーカイブ)してリセットするバッチを必ず並行して設計に組み込んでおけ。

3. VBScriptの限界を見据えた設計
VBScriptはレガシーな技術だが、Windows環境の奥深く(OSの根幹)に寄り添うことができる強力なツールだ。しかし、ミリ秒単位の高頻度トランザクションをさばく用途には向かない。あくまで「各端末が1日1回〜数回走らせるバッチ処理のログ集約」という適材適所の範囲で運用すべし。

安易な直接書き込みでシステムを止めたりログを消失させたりする開発者は、もはやプロとは呼べない。
この排他・バッファリング・リトライの3点セットを実装することで、あなたの管理する自動化基盤は、どんな過酷なネットワーク環境であっても揺るぎない堅牢性を手に入れる。

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