-
Notifications
You must be signed in to change notification settings - Fork 0
MS_ApacheLog4net
Apache log4net について。
補足(現況:メンテナンス モードから復帰した): 本ページを読む前に、
log4net の現在の立ち位置を押さえておく。【経緯】 2004頃 Java の log4j から移植。.NET のログの事実上の標準に 2010年代 更新が滞る(2.0.8 が長く続いた) 2020 【開発休止が示唆され、NLog 等への移行が進む】★ ([NLog](MS_NLog) の参考リンクがその時期のもの) 2021 Log4Shell(log4j 2 の重大な脆弱性)で注目が集まる ※ log4net は【影響を受けなかった】(別実装のため) 2022~ 【メンテナンスが再開】。2.0.15 以降が継続的に更新 現在 3.x 系がリリースされ、.NET 8 等に対応とはいえ、新規採用の第一候補ではない。
【現在の選択】 新規 → 【ILogger(Microsoft.Extensions.Logging)】★ + Serilog / NLog をプロバイダーとして 既存資産 → log4net のまま保守(無理に移行しない) → ILogger 経由に寄せると、後で差し替えられる → [.NETのログ](MS_DotNetLogging) 参照本ページの価値は、概念(レイアウト/アペンダ/ロガー)の解説にある。
この 3 分割は NLog にも Serilog にも共通するため、
1 つ理解すれば他にも通じる。
-
log4net では、3 つの主要なコンポーネント
- レイアウト
- アペンダ
- ロガー
の設定を定義ファイルに定義できる。
-
アペンダ・ロガーについては、以下が参考になる。
-
- Log4J の基本 | TECHSCORE(テックスコア)
https://www.techscore.com/tech/Java/ApacheJakarta/Log4J/1/#log1-3
- Log4J の基本 | TECHSCORE(テックスコア)
- log4j - Wikipedia
https://ja.wikipedia.org/wiki/Log4j
-
移行メモ(原文の綴り): 原文は「lon4net」となっている箇所が
複数あるが、正しくは log4net である(本ページでは修正した)。
アペンダが出力するログのフォーマットを定義する。
- 具体的な出力処理を行う。
- 出力先毎にアペンダの種類が存在する。
- アペンダの種類毎に設定可能な項目が異なる。
- 出力先、ローリング設定等を定義する。
- 論理的なログファイル名
- アペンダをロガーで束ねると複数の出力先へ出力できる。
- ログレベル毎に Filter することができる。
- ルートロガーとロガーがあり階層構造をとる。
補足(3 分割は現在も共通の設計である): この構造は
ログ ライブラリ全般に共通するため、対応表を示しておく。
概念 log4net NLog Serilog ILogger(標準) 何を書くか(書式) Layout Layout Formatter / Template (プロバイダー依存) どこへ書くか(出力先) Appender Target Sink Provider 誰が書くか(分類・絞り込み) Logger Logger Logger カテゴリ( ILogger<T>)【階層構造の意味】★ これが最も実用的な機能 ロガー名を【名前空間に合わせる】のが慣習 MyApp MyApp.Data MyApp.Data.OrderRepository 設定は【上位から継承される】 <root level="INFO"> ← 既定は INFO <logger name="MyApp.Data" level="DEBUG"> ← Data 配下だけ DEBUG → 【障害調査のとき、特定の層だけ詳細ログにできる】★ 本番でも全体を DEBUG にせずに済む// ロガー名を型から取るのが定石(log4net) private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);// ILogger(現在)では、型パラメータがそのままカテゴリになる ★ public class OrderRepository(ILogger<OrderRepository> logger) { } // → カテゴリは "MyApp.Data.OrderRepository" // → appsettings.json の "Logging:LogLevel" で同じ階層制御ができる
アペンダには以下のような種類がある。
-
名前空間は、log4net.Appender
-
ベースクラスは、.AppenderSkeleton
-
参考:Apache log4net – Apache log4net: Config Examples
https://logging.apache.org/log4net/release/config-examples.html
- .FileAppender
テキストファイル - .RollingFileAppender
ローリング・テキストファイル
- .ConsoleAppender
- .ColoredConsoleAppender
- .AnsiColorTerminalAppender
- .EventLogAppender
- .AdoNetAppender
- .AdoNetAppenderParameter
- .UdpAppender
- .NetSendAppender
- .TelnetAppender
- .SmtpAppender
- .SmtpPickupDirAppender
- .RemotingAppender
- .DebugAppender
System.Diagnostics.Debug system - .TraceAppender
System.Diagnostics.Trace system - .AspNetTraceAppender
ASP.NET TraceContext
- .LocalSyslogAppender
- .RemoteSyslogAppender
-
.TextWriterAppender
.TextWriter クラス -
.OutputDebugStringAppender
OutputDebugString Win32API -
.MemoryAppender
-
.AppenderCollection
-
.ForwardingAppender
-
.BufferingForwardingAppender
補足(アペンダの選び方と、避けるべきもの): 種類は多いが、
実務で使うのはごく一部である。
アペンダ 評価 RollingFileAppender 最も使われる。オンプレ/VM 環境の基本 ★ ConsoleAppender コンテナでは必須(後述) EventLogAppender 少量の重要イベントのみ(.NETのログ) AdoNetAppender 推奨しない(後述) SmtpAppender 推奨しない(後述) UdpAppender / RemoteSyslogAppender ログ集約基盤へ送る場合 RemotingAppender .NET Remoting は廃止済み。使わない TelnetAppender / NetSendAppender 歴史的 【AdoNetAppender を推奨しない理由】★ ・ログ出力のたびに【DB へ書き込む】 → DB が遅い/落ちると、アプリまで遅くなる/落ちる → 「ログを出すために業務処理が止まる」本末転倒 ・DB 障害の調査ログが DB に書けない(一番必要なときに使えない) ・トランザクションとの相互作用が読みにくい 【代替】 ファイルに出し、収集基盤が取り込む ([ログ収集いろいろ](MS_LogCollection)) 【SmtpAppender を推奨しない理由】 ・障害時に【大量のメールが飛ぶ】(メールストーム)★ ・SMTP が遅いとアプリが詰まる 【代替】 監視基盤(アラート ルール)で通知する → ログ出力と通知は【責務を分ける】【コンテナ環境での定石】★ ・ファイルではなく【標準出力(Console)に出す】 → コンテナのログ ドライバが回収する → Kubernetes / Docker の標準的な流儀 ・ファイルに出すと、 ・コンテナが消えるとログも消える ・ローリングの管理が二重になる ・ボリュームのマウントが必要になる
定義ファイルでレイアウトを定義することにより、
アペンダ毎、ログ ヘッダを設定できる。
(例)
↓時間 ↓レベル ↓スレッドID ↓メッセージ
[2007/10/25 15:22:21,750], [DEBUG], [9], 任意のメッセージ
補足(ログに含めるべき項目): 例に挙がっている
時刻・レベル・スレッド ID・メッセージは最低限であり、
現在はさらに項目が要る。
項目 理由 時刻 タイムゾーンを明示( yyyy-MM-ddTHH:mm:ss.fffzzz)★レベル 絞り込みの基本 ロガー名(カテゴリ) どの層のログか。原文の例には入っていない スレッド ID 並行処理の追跡(ただし後述) 相関 ID / TraceId 1 リクエストを串刺しで追う ★ ユーザー ID / テナント ID 誰の操作か(個人情報に注意) マシン名 / インスタンス ID 複数台構成での特定 【スレッド ID の限界】★ async/await では【await の前後でスレッドが変わる】 → スレッド ID では 1 つの処理を追えない → [async/await](MS_AsyncAwait) 参照 【現在の解】 相関 ID(TraceId)を使う ・ASP.NET Core は Activity.Current.Id を持っている ・W3C Trace Context(traceparent ヘッダ)で 【サービスをまたいで引き継がれる】<!-- log4net で相関 ID を出す(%property を使う) --> <param name="ConversionPattern" value="%date{yyyy-MM-ddTHH:mm:ss.fffzzz} [%-5level] [%logger] [%property{TraceId}] %message%newline" />// 値を積む(LogicalThreadContext は async でも引き継がれる) log4net.LogicalThreadContext.Properties["TraceId"] = Activity.Current?.TraceId.ToString();【ThreadContext と LogicalThreadContext の違い】★ ThreadContext … スレッド単位。【async をまたぐと失われる】 LogicalThreadContext … 論理的な呼び出しコンテキスト単位。引き継がれる → 非同期処理があるなら【必ず LogicalThreadContext】時刻のタイムゾーンも重要である。
【ローカル時刻だけで記録すると】 ・サーバが海外にある/UTC 設定だと、時差で混乱する ・サマータイムで【同じ時刻が 2 回現れる】 → [国際化対応項目](MS_InternationalizationItems) 参照 【対策】 オフセット付き(ISO 8601)で出す。または UTC で統一する
定義ファイルでロガー(Logger)を定義することにより、
出力するログ レベルのフィルタを設定できる。
ログ レベルには次の5つのレベルがあり、
ロガー(Logger)のログ出力 API を使い分ける。
| レベル | 説明 |
|---|---|
| Fatal | システム停止するような致命的な障害 |
| Error | システム停止はしないが、問題となる障害 |
| Warn | 障害ではない注意警告 |
| Info | 操作ログなどの情報 |
| Debug | 開発用のデバッグメッセージ |
補足(レベルの対応表): .NETのログ で挙げた
ILoggerのレベルとの対応を示しておく。
log4net NLog ILogger(標準) — Trace Trace Debug Debug Debug Info Info Information Warn Warn Warning Error Error Error Fatal Fatal Critical 【log4net には Trace がない】 → [NLog](MS_NLog) が「Traceが増設されている」と述べている点 → ILogger には Trace / None があるレベルの使い分けの原則(.NETのログ と共通):
【判断基準】 Fatal/Critical … 【即座に人を起こす】必要があるか? Error … 【調査が必要】か?(アラートの対象) Warn … 続いたら問題になるか?(傾向を見る) Info … 【平常時に残す】業務の節目 Debug/Trace … 【本番では出さない】 【よくある誤り】 ・何でも Error にする → アラートが鳴りっぱなしで無視される ★ ・想定内の例外を Error にする(入力エラー等)→ Warn か Info ・本番で Debug を出しっぱなし → ディスク圧迫、性能低下、情報漏洩
- 既定(日付でローリング、バックアップ数管理無し)
<!-- ローリング・ログファイル出力用アペンダ -->
<appender name="ACCESS" type="log4net.Appender.RollingFileAppender">
<param name="File" value="C:\root\files\resource\Log\ACCESS" />
<!-- ローリングの設定 -->
<param name="StaticLogFileName" value="false" />
<param name="RollingStyle" value="date " />
<param name="DatePattern" value='"."yyyy-MM-dd".log"' />
<!-- 書き込み時の設定(追加 or 上書き、出力エンコーディング) -->
<param name="AppendToFile" value="true" />
<encoding value="utf-8" />
<!-- メッセージのフォーマット -->
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="[%date{yyyy/MM/dd HH:mm:ss,fff}],[%-5level],[%thread],%message%newline" />
</layout>
<!-- フィルタ(範囲)の設定 -->
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="DEBUG" />
<levelMax value="FATAL" />
</filter>
</appender>- バックアップ数が固定となるローリング
指定のサイズを超えている場合にローリングを行う。
ファイル サイズは必ず、この設定値未満になるわけではない。
<!-- ローリングの設定-->
<param name="StaticLogFileName" value="true" />
<param name="RollingStyle" value="size" />
<param name="MaximumFileSize" value="10MB" />
<param name="MaxSizeRollBackups" value="2" />
<param name="CountDirection" value="-1" />-
付与される番号の順番は、CountDirection パラメタ値により制御する。
-
サイズ 10MB(MaximumFileSize)毎にローリングし、
下記のように、2 つ (MaxSizeRollBackups) のバックアップを保持する 。- (ログファイル名) → 現在出力中のログ
- (ログファイル名).1 → 過去のログバックアップ(古い)
- (ログファイル名).2 → 過去のログバックアップ(最も古い)
-
日付とサイズを合わせたローリング
<!-- ローリングの設定 -->
<param name="StaticLogFileName" value="false" />
<param name="RollingStyle" value="composite" />
<param name="DatePattern" value='"."yyyy"-"MM"-"dd".log"' />
<param name="MaximumFileSize" value="10MB" />
<param name="MaxSizeRollBackups" value="10" />
<param name="CountDirection" value="-1" />- MaxSizeRollBackups パラメタ値は、サイズによるローリングにのみ適用される。
- 日付とサイズを合わせたローリング(composite)では、
ログファイル数を一定数に保つ役割を果たさない。
補足(この節が本ページで最も価値がある): ローリングの落とし穴を
実際に検証して記録している点が有用である。要点を整理・補強する。① 原文が指摘する 2 つの落とし穴(どちらも実際に起きる)
【落とし穴 1】「ファイル サイズは必ず設定値未満になるわけではない」 ・log4net は【1 行を書き終えてからサイズを判定する】 ・10MB を超えた「後」にローリングする → 10MB ちょうどには収まらない → ディスク容量の見積りでは【余裕を持つ】★ 【落とし穴 2】「composite ではファイル数を一定に保てない」★ ・MaxSizeRollBackups は【同一日付内】のバックアップ数にしか効かない ・日付が変わると新しい系列が始まり、【古い日付は消えない】 → 放置すると【ディスクを食い潰す】 → 実際にディスク枯渇の障害原因になる② composite を使う場合の対策
・OS 側で古いファイルを削除する(タスク スケジューラ / cron)★ ・ログ収集基盤に転送し、ローカルは短期保持にする ・監視で【ディスク使用率にアラート】を設定する ・そもそも【日付のみ】または【サイズのみ】にする③
CountDirectionの意味(分かりにくいので明記)CountDirection = -1(既定) ログ.1 が【最新のバックアップ】、番号が大きいほど古い → ローリングのたびに【全ファイルの名前を変える】(.1→.2、.2→.3) → ファイル数が多いと【リネームのコストが増える】 CountDirection = 1 以上 番号が大きいほど新しい(インクリメンタル) → リネームが発生しない= 速い ★ → ただし「.1 が最新」という直感には反する④ 複数プロセスからの書き込み(原文にない重要な点)
【問題】 IIS のワーカー プロセスが複数(Web ガーデン) あるいは複数のサービスが【同じログ ファイルに書く】 → 既定ではファイルがロックされ、 後発のプロセスが【ログを出せない】★ 【対策】 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" /> → 書き込みのたびに開閉する(安全だが【遅い】) あるいは【プロセスごとにファイルを分ける】(推奨) <file type="log4net.Util.PatternString" value="Log\app_%processid.log" />⑤ 文字コード
<encoding value="utf-8" /> → 原文の設定は正しい → 【BOM が付かない】ので、grep 等での扱いも良い → 指定しないと環境依存(CP932)になり、 Linux で収集した際に化ける ([エンコーディング](MS_Encoding) 参照)⑥ 設定ファイルの読み込み
// AssemblyInfo.cs に書くのが伝統的なやり方 [assembly: log4net.Config.XmlConfigurator( ConfigFile = "log4net.config", Watch = true)] // ↑ 【実行中の設定変更を反映】★【Watch = true の価値】 本番で「調査のため一時的に DEBUG にしたい」場合、 【再起動せずに】設定ファイルを書き換えるだけで反映される → 障害調査で極めて有用 【注意】 .NET Core 以降は AssemblyInfo の属性が使いにくいため、 起動時にコードで読み込む方式が一般的 var repo = LogManager.GetRepository(Assembly.GetEntryAssembly()); XmlConfigurator.ConfigureAndWatch(repo, new FileInfo("log4net.config"));
ログ出力方式 (log4net) - Open 棟梁 Wiki(OTR_LoggingLog4net.md)
-
Apache log4net – Apache log4net: Home - Apache log4net
https://logging.apache.org/log4net/ -
オープンソースのロギング・サービス「log4net」を使う:
連載:VBで実践! 外部コンポーネント活用術 - @IT -
Log4Net を利用してログを記録する - Qiita
https://qiita.com/rohinomiya/items/2b86c4e8d5afd5c2fb39 -
log4net の config ファイルを 埋め込みリソース から読み込む
https://mseeeen.msen.jp/load-log4net-config-from-embedded-resources/
- .NET でのログ記録
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/logging - サード パーティ製のログ プロバイダー
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/logging-providers
Tags: 移行, プログラミング, .NET開発
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。