【実務・中級編】【上級者向け】ページ追加・削除イベントをフックし、変更履歴を自動的にテキストログへ追記する – Visio VBA解析バイブル

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

【極限設計】Visio VBAにおける監査証跡(Audit Trail)の構築術 ― イベントハンドリングと堅牢なロギングの実装

「動けばいい」という思考は、プロトタイプを作る段階では許容される。しかし、エンタープライズ環境で運用される業務自動化ツールにおいては、その甘さが致命的な欠陥を招く。

特に、設計図やフローチャートを管理するVisioにおいて、「誰が、いつ、どのページを操作したのか」という履歴(監査証跡:Audit Trail)を管理することは、コンプライアンスとプロセスの透明性を担保する上で不可欠だ。

本記事では、単に「ページ追加を検知する」というレベルの話はしない。「例外に強く、パフォーマンスを阻害せず、かつ保守性を極限まで高めたイベント駆動型ロギングシステム」をいかに設計するか。その真髄を伝授する。

1. なぜ、素人のコードは「現場」で死ぬのか

多くの開発者が陥る罠がある。それは、`Document`オブジェクトのイベントを、メインのロジックの中に直接、泥臭く書き込んでしまうことだ。

' --- これが典型的な「素人」のコードだ ---
Private Sub Document_NewPage(ByVal Page As Visio.Page)
    Dim f As Integer: f = FreeFile
    Open "C:\log.txt" For Append As #f
    Print #f, Now & " - Page Added: " & Page.Name
    Close #f
End Sub

このコードがなぜ「死ぬ」のか。理由は3つある。

1. 密結合(Tight Coupling): ロギングロジックがイベントハンドラと一体化しているため、ログの出力先を変えるだけで、ページ操作のロジック全体を再テストしなければならない。
2. ファイルI/Oの脆弱性: ログファイルが他のプロセスによってロックされていた場合、`Open`ステートメントでランタイムエラーが発生し、ユーザーの本来の業務(ページ追加)が中断される。
3. リソース管理の欠如: エラー発生時にファイルハンドルが閉じられず、メモリリークやファイル破損を招く。

プロフェッショナルは、「イベントの検知」と「記録の実行」を完全に分離(Decoupling)する。

2. アーキテクチャ設計:関心の分離

堅牢なシステムを構築するために、以下の3層構造を採用する。

1. Event Layer (検知層): Visioのイベントをキャッチし、即座に「イベント情報」をデータ構造に詰め込む。
2. Service Layer (制御層): データの加工や、記録すべきかどうかの判定を行う。
3. Infrastructure Layer (実行層): ファイルシステムやデータベースへの物理的な書き込みを行う。

これにより、将来的にログの出力先をテキストファイルからSQL ServerやAzure Monitorに変更したとしても、Event Layerには一切の手を加える必要がなくなる。

3. 実装:プロダクション・グレードのコード例

以下に、そのまま実戦投入可能な設計を示す。標準モジュールとクラスモジュールを適切に使い分けることが、VBAにおける「設計」の第一歩だ。

A. Infrastructure Layer: `LoggerService` (標準モジュール)

ログの書き込みという「副作用」を単一の責任に集約する。

Option Explicit

' ログの出力先(環境変数や設定ファイルから取得するのが理想)
Private Const LOG_FILE_PATH As String = "C:\Logs\Visio_Audit_Trail.log"

''' <summary>
''' 監査ログをファイルに追記する。
''' 業務を阻害しないよう、エラーが発生しても致命的な停止を避ける設計。
''' </summary>
Public Sub WriteAuditLog(ByVal message As String)
    On Error GoTo ErrorHandler
    
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ' フォルダが存在しない場合は作成(堅牢性の確保)
    Dim parentFolder As String
    parentFolder = fso.GetParentFolderName(LOG_FILE_PATH)
    If Not fso.FolderExists(parentFolder) Then
        ' 本来はここで権限チェック等が必要
        MsgBox "Log directory not found: " & parentFolder, vbCritical
        Exit Sub
    End If

    ' Appendモードで書き込み
    Dim ts As Object
    Set ts = fso.OpenTextFile(LOG_FILE_PATH, 8, True) ' 8 = ForAppending
    ts.WriteLine Format(Now, "yyyy-mm-dd HH:mm:ss") & " | " & message
    ts.Close
    
    Set ts = Nothing
    Set fso = Nothing
    Exit Sub

ErrorHandler:
    ' ログ出力の失敗が、メイン業務(Visio操作)を止めてはならない。
    ' デバッグ時のみエラーを表示し、本番環境ではサイレントに処理を継続させる。
    Debug.Print "CRITICAL ERROR in LoggerService: " & Err.Description
End Sub

B. Event Layer: `ThisDocument`

Visioのドキュメントイベントをフックし、Service Layerへ委譲する。

Option Explicit

''' <summary>
''' ページが新しく追加された際のイベントハンドラ
''' </summary>
Private Sub Document_NewPage(ByVal Page As Visio.Page)
    Dim logMsg As String
    Dim userName As String
    
    ' コンテキスト情報の取得
    userName = Environ("USERNAME") ' OSのユーザー名を取得
    logMsg = "[PAGE_ADD] User: " & userName & " | PageName: " & Page.Name
    
    ' 処理の委譲(Decoupling)
    ' ここで直接ファイルを開かず、Service Layerに投げる
    Call WriteAuditLog(logMsg)
End Sub

''' <summary>
''' ページが削除された際のイベントハンドラ
''' </summary>
Private Sub Document_DeletePage(ByVal Page As Visio.Page)
    Dim logMsg As String
    Dim userName As String
    
    userName = Environ("USERNAME")
    logMsg = "[PAGE_DEL] User: " & userName & " | PageName: " & Page.Name
    
    Call WriteAuditLog(logMsg)
End Sub

4. 運用における「極限の知見」

コードを書いた後に、君が考えるべきは「コードの美しさ」ではなく「運用時の不確実性」だ。

ファイルI/Oの競合(Race Condition)への対策

複数のユーザーが同じネットワークドライブ上のVisioファイルを開き、同時にページ操作を行った場合、ログファイルへの書き込み権限が競合し、エラーが発生する。

  • 対策: ログ出力部を`On Error Resume Next`で囲むのは最低限の作法だ。より高度な設計では、ログを一度ローカルの「一時ファイル」に書き出し、定期的なバッチ処理で中央のログサーバーに集約する「非同期ロギング」の概念を取り入れるべきだ。

パフォーマンスの重み

イベントハンドラは、ユーザーの操作(ページ追加)と同期して動作する。もしログ出力に3秒かかれば、ユーザーは「Visioがフリーズした」と感じる。

  • 対策: `FileSystemObject`の使用は、低レベルな`Open`ステートメントよりも抽象度が高く安全だが、オーバーヘッドがある。極限のパフォーマンスを求めるなら、メモリ上のバッファ(コレクション)にログを溜め込み、`Document_BeforeSave`などの低頻度イベントで一括書き出しを行う設計を検討せよ。

監査証跡の改ざん防止

テキストファイルに書き出すだけのログは、管理者でも簡単に消去できる。

  • 対策: 真に高いセキュリティが求められるプロジェクトでは、VBAから直接、読み取り専用のデータベース(SQL Server等)へ`ADODB.Connection`を用いて書き込む設計に昇華させよ。

結論

優れたエンジニアは、コードを書く前に「失敗のシナリオ」を書く。
今回の実装のように、「エラーが起きてもメインの業務を止めない」「関心を分離して変更に強くする」という設計思想を持つだけで、君が作るツールの格は、単なるマクロから「システム」へと昇華する。

次なる課題は、このログをどう解析し、業務改善のインサイトに変えていくかだ。設計の旅は、まだ始まったばかりである。

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