【実務・中級編】ADOの「Errorsコレクション」を駆使した、詳細なDBエラーログの自動生成ツール – Access VBA解析バイブル

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

【極限の知見】Err.Descriptionに頼るな。ADO「Errorsコレクション」を掌握し、SQL Serverの深淵をログに刻め

業務自動化の現場において、Access VBAは「単なるデスクトップアプリの開発言語」ではない。それは、堅牢なSQL Serverという巨大なエンジンを制御するための、フロントエンド・コントローラーである。

しかし、多くの開発者が陥る致命的な罠がある。それは、「エラーハンドリングの設計が、あまりにも浅すぎる」ということだ。

`On Error GoTo ErrHandler` を記述し、`MsgBox Err.Description` を表示して終わる。もし君がこれだけを実装しているなら、君は「バグが起きた時に、原因を推測するしかない」という、エンジニアとして最も不毛な状況を自らに強いていることになる。

特に、バックエンドにSQL Serverを据えた分散システムにおいては、VBA側のエラー(ランタイムエラー)と、SQL Server側から返されるエラー(データベースエンジンエラー)は全くの別物だ。

本稿では、ADO(ActiveX Data Objects)の真の力を引き出し、SQL Serverが吐き出す詳細なエラー情報を余さず捕捉・記録するための、プロフェッショナルなアーキテクチャを伝授する。

1. なぜ `Err.Description` では不十分なのか

VBAの組み込みオブジェクトである `Err` オブジェクトは、あくまで「VBAランタイム」が発生させたエラーを保持するものだ。

一方で、SQL Serverで制約違反(Constraint Violation)やデータ型の不一致、トリガーによるエラーが発生した場合、ADOはそれらを「プロバイダー経由のエラー」として受け取る。このとき、`Err.Description` に格納されるのは、「ADOがラップした抽象的なエラーメッセージ」に過ぎない。

「実行時エラー ‘-2147217900 (80040e14)’: 構文エラーまたはデータ型が不一致です。」

これを見て、君は即座にSQLのどの行の、どのカラムが、どのような制約に抵触したのかを特定できるか? 答えは「NO」だ。現場のエンジニアが真に必要としているのは、SQL Serverが直接返してきた `NativeError` や `SQLState` なのだ。

2. ADO `Errors` コレクションの真価

ADOの `Connection` オブジェクト、あるいは `Command` オブジェクトには、`Errors` というコレクションが存在する。

SQL Serverでエラーが発生した際、プロバイダーは単一のエラーだけでなく、関連するエラーのスタックをこのコレクションに格納する。ここをループで回し、各エラーの詳細を抽出することこそが、プロフェッショナルなログ設計の第一歩である。

捕捉すべき主要なプロパティは以下の通りだ:

  • Description: SQL Serverが返した具体的なメッセージ
  • SQLState: 標準SQLの状態コード
  • NativeError: SQL Server固有のエラー番号(これこそが解決の鍵となる)

3. 実装:堅牢なエラーログ生成アーキテクチャ

ここでは、単なるログ出力関数ではなく、「エラー発生時に、ビジネスロジックを汚さず、かつ詳細なコンテキストを保存する」ための実戦的なコード例を示す。

構成案

1. `modLogger`: ファイルへの書き出しを担当する低レイヤーモジュール。
2. `modDB`: ADO接続と、エラー捕捉ロジックを含むメインモジュール。

# 【Module: modLogger】

ログ出力は、DB接続が切断されている最中に発生する可能性がある。そのため、ログ自体をDBに書き込む設計は避け、必ずファイルシステム(FileSystemObject)を利用せよ。

Option Explicit

' ログ出力の核となる関数
' 引数: logPath - ログファイルのフルパス
'       msg - 出力するメッセージ
'       isCritical - 致命的なエラーかどうかのフラグ
Public Sub WriteLog(ByVal logPath As String, ByVal msg As String, Optional ByVal isCritical As Boolean = False)
    On Error Resume Next ' ログ出力自体の失敗でメイン処理を止めない
    
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    Dim ts As Object
    ' ForAppending: 追記モード, True: ファイルがなければ作成
    Set ts = fso.OpenTextFile(logPath, 8, True)
    
    Dim prefix As String
    prefix = IIf(isCritical, "[CRITICAL] ", "[INFO] ")
    
    ' タイムスタンプとメッセージの書き込み
    ts.WriteLine Now & " " & prefix & msg
    ts.Close
    
    Set ts = Nothing
    Set fso = Nothing
End Sub

# 【Module: modDB】

ここが心臓部だ。`Connection.Errors` を走査し、詳細情報を文字列として集約する。

Option Explicit

Private Const LOG_FILE_PATH As String = "C:\Logs\App_Error_Log.txt"

' データベース操作のメインルーチン
Public Sub ExecuteDatabaseTask()
    Dim conn As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim sql As String
    
    ' 接続文字列(実務では環境変数や設定テーブルから取得せよ)
    Dim connStr As String
    connStr = "Provider=SQLOLEDB;Data Source=YOUR_SERVER;Initial Catalog=YOUR_DB;Integrated Security=SSPI;"

    On Error GoTo ErrorHandler

    Set conn = New ADODB.Connection
    conn.Open connStr

    ' テスト用のエラー誘発SQL(制約違反を想定)
    sql = "INSERT INTO Employees (ID, Name) VALUES (1, 'Test User');" 
    
    Set cmd = New ADODB.Command
    With cmd
        .ActiveConnection = conn
        .CommandText = sql
        .Execute
    End With

    MsgBox "処理が正常に完了しました。", vbInformation
    
CleanUp:
    ' オブジェクトのライフサイクル管理を徹底せよ
    If Not conn Is Nothing Then
        If conn.State = adStateOpen Then conn.Close
    End If
    Set cmd = Nothing
    Set conn = Nothing
    Exit Sub

ErrorHandler:
    ' 1. VBA側のエラー情報を取得
    Dim vbaErrDetail As String
    vbaErrDetail = "VBA Error #" & Err.Number & ": " & Err.Description

    ' 2. ADO/SQL Server側の詳細エラーを収集
    Dim adoErrDetail As String
    adoErrDetail = CaptureADOErrors(conn)

    ' 3. ログへの統合出力
    Dim fullLogMsg As String
    fullLogMsg = vbaErrDetail & vbCrLf & IIf(adoErrDetail <> "", "SQL Server Details:" & vbCrLf & adoErrDetail, "")
    
    WriteLog LOG_FILE_PATH, fullLogMsg, True
    
    MsgBox "システムエラーが発生しました。管理者に連絡してください。" & vbCrLf & _
           "Error Code: " & Err.Number, vbCritical, "Execution Error"
    
    Resume CleanUp
End Sub

' ADOのエラーコレクションを走査する職人芸
Private Function CaptureADOErrors(ByRef conn As ADODB.Connection) As String
    Dim errColl As ADODB.Errors
    Dim errItem As ADODB.Error
    Dim strDetails As String
    
    ' Connectionが有効でない場合は、エラー情報は取れない
    If conn Is Nothing Then Exit Function
    
    Set errColl = conn.Errors
    
    If errColl.Count > 0 Then
        For Each errItem In errColl
            strDetails = strDetails & _
                "  - [SQLState: " & errItem.SQLState & "] " & _
                "[NativeError: " & errItem.NativeError & "] " & _
                "Msg: " & errItem.Description & vbCrLf
        Next errItem
    End If
    
    CaptureADOErrors = strDetails
End Function

4. アーキテクトからの警告:設計上の注意点

この実装をプロダクション環境に投入するにあたり、以下の3点を遵守せよ。

① オブジェクトのライフサイクルと「接続喪失」への備え

エラーが発生した際、既に `Connection` オブジェクトが破棄されていたり、ネットワーク断絶で `State` が `adStateClosed` になっていたりする場合がある。`CaptureADOErrors` 内で `If conn Is Nothing` のチェックを入れているのは、そのためだ。接続が死んでいる状態でのエラー収集は不可能であることを理解しておけ。

② トランザクションの整合性

`On Error GoTo` でエラーを捕捉した際、もしその前に `conn.BeginTrans` を実行していたなら、必ず `conn.RollbackTrans` を呼び出さなければならない。ログ出力だけして処理を終えるのは、データの整合性を破壊する行為だ。

③ I/O パフォーマンスとロック

ログファイルへの書き込みは、頻繁に発生する可能性がある。高頻度なトランザクションが発生するシステムでは、ログを直接ファイルに書くと、ファイルロックがボトルネックになる。その場合は、ログをメモリ上のキューに溜め、バッチ処理で書き出すか、あるいは Windows イベントログへの転送を検討せよ。

結論

「動けばいい」という思考は、保守フェーズにおいて牙を剥く。
`Err.Description` という薄い膜を剥ぎ取り、`Errors` コレクションを通じてサーバーの叫びを直接聞き取ること。それが、トラブルシューティングの時間を劇的に短縮し、システムの信頼性を極限まで高める唯一の道である。

このコードを、君のプロジェクトの「標準」として組み込め。

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