ログ出力は「記録」ではない。「システムの心電図」である
多くのVB.NET開発者が、`Debug.WriteLine`や不完全なテキストファイルへの追記でログを済ませている。だが、業務システムにおけるログとは、障害発生時の「黒箱」であるべきだ。何が起きたかを知るだけでなく、「なぜそうなったのか」を推論するためのデータ構造を最初から設計しなければならない。
今回は、NLogをエンジンに据え、レガシーとモダンが混在する環境下でも耐えうる「構造化ログ基盤」の構築について、アーキテクトの視点から説く。
—
1. なぜ「構造化ログ」なのか?
文字列としてログを吐き出す時代は終わった。ログはJSONやデータベースに解析可能な形で残すべきだ。
- 相関ID (Correlation ID) の付与: Web API呼び出しや非同期処理の連鎖を追跡するために必須。
- 構造化: ログを解析ツール(ELKスタックやAzure Monitor)に流し込むことで、特定のユーザーや特定の取引単位での検索を瞬時に行う。
- パフォーマンス: ファイルI/Oを非同期で行う構造にしなければ、業務アプリケーションのレスポンスそのものを殺すことになる。
—
2. 実装:NLogを用いたプロ仕様の基盤構築
まずはNuGetから `NLog` と `NLog.Config` をインストールする。
設定ファイル(NLog.config)は実行ファイルと同階層に置くが、ハードコーディングは避け、環境変数でログ出力先を制御できるようにしておくのが定石だ。
構造化ログを出力するための設定例 (NLog.config)
—
3. VB.NETにおけるメモリ管理とライフサイクル
VB.NET、特にGC(ガベージコレクション)が介入する環境下では、ログ出力によるメモリリークを警戒する必要がある。ログ出力のために巨大な文字列を結合し続けると、LOH (Large Object Heap) を圧迫し、フルGCを誘発してシステムが停止する。
ログラッパーの極意
ログ出力はシングルトンで管理し、`NLog.MappedDiagnosticsLogicalContext` (MDLC) を活用してコンテキスト情報を管理する。
Imports NLog
Public NotInheritable Class LoggerService
Private Shared ReadOnly logger As Logger = LogManager.GetCurrentClassLogger()
‘ コンテキストのスコープ制御。IDisposableを実装することで、
‘ usingブロック終了時に自動的にIDをクリアする
Public Shared Function BeginScope(correlationId As String) As IDisposable
MappedDiagnosticsLogicalContext.Set(“CorrelationId”, correlationId)
Return New ScopeDisposer()
End Function
Private Class ScopeDisposer
Implements IDisposable
Public Sub Dispose() Implements IDisposable.Dispose
MappedDiagnosticsLogicalContext.Remove(“CorrelationId”)
End Sub
End Class
Public Shared Sub LogError(ex As Exception, message As String)
‘ 構造化された例外情報を渡し、スタックトレースを破壊しない
logger.Error(ex, message)
End Sub
End Class
—
4. 伝説のアーキテクトからの忠告
① Windows APIとの協調
レガシーなVB6/VBAシステムからの移行期であれば、Windowsイベントログへの書き込みも併用すべきだ。`System.Diagnostics.EventLog` を使い、OS側の管理者に「異常検知」を通知せよ。ただし、イベントログは容量制限があるため、「重要エラーのみ」に絞るのが鉄則だ。
② パフォーマンスの重み
ログ出力は常に「同期」で行うべきではない。NLogのターゲット設定で `AsyncTargetWrapper` を使用し、ログ書き込みを別スレッドに逃がせ。これを怠ると、ファイルロック待ちでメイン処理が止まり、ユーザーから「システムが固まった」という苦情が飛んでくることになる。
③ オブジェクトの明示的解放
ログ出力先がデータベースの場合、接続のライフサイクル管理が命だ。`Using` ステートメントを徹底し、例外発生時にも確実にコネクションがクローズされることを保証せよ。
‘ 正しいUsingの使い方。リソースリークは許されない
Using conn As New SqlConnection(connectionString)
Try
conn.Open()
‘ … ログ書き込み処理
Catch ex As SqlException
‘ データベースへの書き込み失敗時は、最終防衛線としてローカルファイルへフォールバックする
FallbackLogger.Write(ex)
End Try
End Using
結論
ログとは、システムが放つ「声」である。
その声をいかにクリアに、いかに迅速に、いかに解析可能な形で拾い上げるか。それが、真のシニアエンジニアと、ただコードを書く者の分かれ道だ。
ログ設計を疎かにする者は、障害発生時に暗闇の中で迷路を彷徨うことになる。構造化ログという羅針盤を持ち、堅牢なシステムを構築せよ。
