Apache log4net
最も歴史のあるApache財団の.NETロギングフレームワーク(Java log4jからの移植)。豊富な設定オプションと柔軟性で知られ、複雑な要件に対応可能。多様な出力先とカスタマイズ性を提供する成熟したソリューション。
ライブラリ
Apache log4net
概要
Apache log4netは「最も歴史のあるApache財団の.NETロギングフレームワーク」として開発された、Java log4jからの移植版ロギングライブラリです。豊富な設定オプションと柔軟性で知られ、複雑な要件に対応可能な成熟したソリューション。多様な出力先とカスタマイズ性を提供し、Enterprise環境での長年の実績を持つ伝統的なフレームワークですが、現代的な.NET開発では他の選択肢が優先される傾向にあります。
詳細
Apache log4net 2025年版では新規プロジェクトでの推奨度が低下しており、構造化ログサポートの限界、メンテナンス性の問題、パフォーマンス面での劣勢により、既存システムの保守でのみ関与する状況となっています。しかし、レガシーシステムでの重要な役割は継続しており、.NET Frameworkとの深い統合、XML設定による詳細制御、豊富なアペンダー(ファイル、データベース、イベントログ等)により、従来のエンタープライズアプリケーションでは依然として使用されています。
主な特徴
- Java log4j互換設計: Java開発者に馴染みのある設定方式とアーキテクチャ
- 豊富な設定オプション: XML設定による詳細なログ制御とカスタマイズ
- 多様なアペンダー: ファイル、データベース、イベントログ、SMTP等への出力
- レベル別制御: DEBUG、INFO、WARN、ERROR、FATALの階層的ログレベル
- カスタムフィルター: 条件付きログ出力とメッセージフィルタリング
- .NET統合: .NET Frameworkとの深い統合とConfiguration API対応
メリット・デメリット
メリット
- Apache財団による長期サポートと豊富な実績による安定性
- XML設定ファイルによる外部設定管理と運用時変更対応
- 多様なアペンダーによる柔軟な出力先設定とカスタマイズ性
- レガシーシステムとの高い互換性と移行コストの低さ
- 豊富なドキュメントとコミュニティサポートの蓄積
- エンタープライズ環境での複雑なログ要件への対応実績
デメリット
- 構造化ログサポートが限定的で現代的な監視ツールとの統合が困難
- パフォーマンス面でSerilog、NLog、Microsoft.Extensions.Loggingに劣る
- 新しい.NET技術(.NET Core/.NET 5+)への対応が不十分
- 設定の複雑性とメンテナンス性の問題による開発効率低下
- 非同期処理サポートが限定的で高負荷環境での制約
- モダンなDevOps実践との統合が困難
参考ページ
書き方の例
インストールと基本セットアップ
<!-- NuGetパッケージ参照 -->
<PackageReference Include="log4net" Version="2.0.17" />
# NuGetパッケージマネージャー
Install-Package log4net
# .NET CLI
dotnet add package log4net
// AssemblyInfo.cs または Global.asax.cs での設定
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]
// または App.config / Web.config での設定属性
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
基本的なログ出力
using log4net;
using log4net.Config;
using System;
using System.IO;
using System.Reflection;
public class BasicLoggingExample
{
// ロガーの取得(クラス名で自動命名)
private static readonly ILog logger = LogManager.GetLogger(typeof(BasicLoggingExample));
static void Main(string[] args)
{
// 設定ファイルの読み込み
var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config"));
// 基本的なログレベル別出力
logger.Debug("デバッグ情報: アプリケーション初期化開始");
logger.Info("一般情報: アプリケーション開始");
logger.Warn("警告: 設定ファイルにデフォルト値を使用");
logger.Error("エラー: データベース接続失敗", new Exception("Connection timeout"));
logger.Fatal("致命的エラー: システム停止");
// フォーマット済みメッセージ
string userId = "user123";
int transactionId = 98765;
logger.InfoFormat("取引処理完了: ユーザー={0}, 取引ID={1}, 金額={2:C}",
userId, transactionId, 15000);
// 条件付きログ出力(パフォーマンス考慮)
if (logger.IsDebugEnabled)
{
string expensiveData = GetExpensiveDebugInfo();
logger.DebugFormat("重い処理結果: {0}", expensiveData);
}
// 例外の詳細ログ
try
{
ProcessData();
}
catch (Exception ex)
{
logger.Error("データ処理エラー", ex);
throw;
}
logger.Info("アプリケーション終了");
}
private static string GetExpensiveDebugInfo()
{
// 重い処理のシミュレーション
System.Threading.Thread.Sleep(100);
return $"デバッグ情報: {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
}
private static void ProcessData()
{
// 業務処理のシミュレーション
logger.Debug("データ処理開始");
// 意図的にエラーを発生
throw new InvalidOperationException("サンプルエラー");
}
}
XML設定ファイル(log4net.config)による詳細設定
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<!-- アペンダー定義 -->
<!-- コンソールアペンダー -->
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %message%newline" />
</layout>
<threshold value="INFO" />
</appender>
<!-- ファイルアペンダー(ローリング) -->
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="logs/application.log" />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<datePattern value="yyyyMMdd" />
<maxSizeRollBackups value="30" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy-MM-dd HH:mm:ss.fff} [%thread] %-5level %logger{2} - %message%newline" />
</layout>
</appender>
<!-- エラー専用ファイルアペンダー -->
<appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="logs/error.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="5MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy-MM-dd HH:mm:ss.fff} [%thread] %-5level %logger - %message%newline%exception" />
</layout>
<threshold value="ERROR" />
</appender>
<!-- データベースアペンダー -->
<appender name="DatabaseAppender" type="log4net.Appender.AdoNetAppender">
<bufferSize value="100" />
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<connectionString value="Data Source=localhost;Initial Catalog=LogDB;Integrated Security=True" />
<commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)" />
<parameter>
<parameterName value="@log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@thread" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%thread" />
</layout>
</parameter>
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="@logger" />
<dbType value="String" />
<size value="255" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="@message" />
<dbType value="String" />
<size value="4000" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</parameter>
<parameter>
<parameterName value="@exception" />
<dbType value="String" />
<size value="2000" />
<layout type="log4net.Layout.ExceptionLayout" />
</parameter>
<threshold value="WARN" />
</appender>
<!-- SMTPアペンダー(重要なエラー通知) -->
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="[email protected]" />
<from value="[email protected]" />
<subject value="[ERROR] アプリケーションエラー" />
<smtpHost value="smtp.example.com" />
<port value="587" />
<username value="[email protected]" />
<password value="password123" />
<enableSsl value="true" />
<bufferSize value="10" />
<threshold value="ERROR" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %message%newline%exception" />
</layout>
</appender>
<!-- イベントログアペンダー -->
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
<applicationName value="MyApplication" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %message" />
</layout>
<threshold value="WARN" />
</appender>
<!-- ロガー設定 -->
<!-- 特定名前空間の詳細ログ -->
<logger name="MyApplication.Services">
<level value="DEBUG" />
<appender-ref ref="RollingFileAppender" />
</logger>
<!-- データアクセス層のログ -->
<logger name="MyApplication.DataAccess">
<level value="TRACE" />
<appender-ref ref="RollingFileAppender" />
</logger>
<!-- フレームワークログの制限 -->
<logger name="System" additivity="false">
<level value="WARN" />
<appender-ref ref="ConsoleAppender" />
</logger>
<!-- ルートロガー -->
<root>
<level value="INFO" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="RollingFileAppender" />
<appender-ref ref="ErrorFileAppender" />
<appender-ref ref="DatabaseAppender" />
<appender-ref ref="SmtpAppender" />
<appender-ref ref="EventLogAppender" />
</root>
</log4net>
カスタムアペンダーと高度な設定
// カスタムアペンダーの実装例
using log4net.Appender;
using log4net.Core;
using System;
using System.IO;
using Newtonsoft.Json;
public class JsonFileAppender : AppenderSkeleton
{
public string FileName { get; set; }
private FileStream fileStream;
private StreamWriter writer;
public override void ActivateOptions()
{
base.ActivateOptions();
// ファイルストリームの初期化
var directory = Path.GetDirectoryName(FileName);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
fileStream = new FileStream(FileName, FileMode.Append, FileAccess.Write, FileShare.Read);
writer = new StreamWriter(fileStream);
}
protected override void Append(LoggingEvent loggingEvent)
{
// JSON形式でログを出力
var logEntry = new
{
Timestamp = loggingEvent.TimeStamp,
Level = loggingEvent.Level.ToString(),
Logger = loggingEvent.LoggerName,
Message = loggingEvent.RenderedMessage,
Exception = loggingEvent.ExceptionObject?.ToString(),
Thread = loggingEvent.ThreadName,
Username = loggingEvent.UserName,
Properties = loggingEvent.Properties
};
string jsonLog = JsonConvert.SerializeObject(logEntry);
writer.WriteLine(jsonLog);
writer.Flush();
}
protected override void OnClose()
{
writer?.Close();
fileStream?.Close();
base.OnClose();
}
}
// カスタムアペンダーの使用例
public class CustomAppenderExample
{
private static readonly ILog logger = LogManager.GetLogger(typeof(CustomAppenderExample));
static void Main(string[] args)
{
// プログラマティック設定
ConfigureLog4Net();
// コンテキスト情報の設定
log4net.GlobalContext.Properties["Environment"] = "Production";
log4net.GlobalContext.Properties["Version"] = "1.0.0";
// スレッドコンテキスト情報
log4net.ThreadContext.Properties["UserId"] = "user123";
log4net.ThreadContext.Properties["SessionId"] = "session456";
// ログ出力
logger.Info("カスタムアペンダーテスト開始");
logger.Warn("警告テスト: メモリ使用量が高いです");
logger.Error("エラーテスト", new Exception("テスト例外"));
// MDC(Mapped Diagnostic Context)の使用
log4net.ThreadContext.Properties["TransactionId"] = "tx789";
logger.Info("取引処理実行");
log4net.ThreadContext.Properties.Remove("TransactionId");
logger.Info("カスタムアペンダーテスト完了");
}
private static void ConfigureLog4Net()
{
// プログラマティック設定の例
var hierarchy = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
// パターンレイアウト
var patternLayout = new log4net.Layout.PatternLayout
{
ConversionPattern = "%date{yyyy-MM-dd HH:mm:ss.fff} [%thread] %-5level %logger - %message%newline"
};
patternLayout.ActivateOptions();
// カスタムJSONアペンダー
var jsonAppender = new JsonFileAppender
{
Name = "JsonFileAppender",
FileName = "logs/application.json"
};
jsonAppender.ActivateOptions();
// ロガー設定
hierarchy.Root.AddAppender(jsonAppender);
hierarchy.Root.Level = log4net.Core.Level.Info;
hierarchy.Configured = true;
}
}
ASP.NET統合とWebアプリケーション対応
// Global.asax.cs
using log4net;
using log4net.Config;
using System;
using System.IO;
using System.Web;
public class Global : HttpApplication
{
private static readonly ILog logger = LogManager.GetLogger(typeof(Global));
protected void Application_Start(object sender, EventArgs e)
{
// log4net設定の初期化
XmlConfigurator.Configure(new FileInfo(Server.MapPath("~/log4net.config")));
logger.Info("Webアプリケーション開始");
}
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
string url = HttpContext.Current.Request.Url.ToString();
string userAgent = HttpContext.Current.Request.UserAgent;
string userIP = HttpContext.Current.Request.UserHostAddress;
logger.Error($"未処理例外発生: URL={url}, UserAgent={userAgent}, IP={userIP}", exception);
}
protected void Session_Start(object sender, EventArgs e)
{
string sessionId = Session.SessionID;
string userIP = Request.UserHostAddress;
logger.Info($"新しいセッション開始: SessionID={sessionId}, IP={userIP}");
}
protected void Application_End(object sender, EventArgs e)
{
logger.Info("Webアプリケーション終了");
LogManager.Shutdown();
}
}
// コントローラーまたはページでの使用例
public partial class UserManager : System.Web.UI.Page
{
private static readonly ILog logger = LogManager.GetLogger(typeof(UserManager));
protected void Page_Load(object sender, EventArgs e)
{
// リクエスト情報のログ
string sessionId = Session.SessionID;
string userIP = Request.UserHostAddress;
string userAgent = Request.UserAgent;
// MDC設定
log4net.ThreadContext.Properties["SessionId"] = sessionId;
log4net.ThreadContext.Properties["UserIP"] = userIP;
logger.Info($"ページ読み込み: {Request.Url}");
try
{
LoadUserData();
}
catch (Exception ex)
{
logger.Error("ユーザーデータ読み込みエラー", ex);
// エラーページへのリダイレクト等
}
finally
{
// MDCのクリア
log4net.ThreadContext.Properties.Clear();
}
}
private void LoadUserData()
{
logger.Debug("ユーザーデータ取得開始");
// データ取得処理
System.Threading.Thread.Sleep(100); // 処理のシミュレーション
logger.Info("ユーザーデータ取得完了: 件数=150");
}
protected void btnSave_Click(object sender, EventArgs e)
{
string userId = Request["userId"];
// 操作ログ
log4net.ThreadContext.Properties["UserId"] = userId;
log4net.ThreadContext.Properties["Action"] = "Save";
try
{
logger.Info("ユーザー保存処理開始");
// 保存処理のシミュレーション
SaveUserData(userId);
logger.Info("ユーザー保存処理完了");
}
catch (Exception ex)
{
logger.Error("ユーザー保存処理エラー", ex);
throw;
}
finally
{
log4net.ThreadContext.Properties.Clear();
}
}
private void SaveUserData(string userId)
{
logger.Debug($"ユーザーデータ保存: UserID={userId}");
// 実際の保存処理
if (string.IsNullOrEmpty(userId))
{
throw new ArgumentException("UserIDが指定されていません");
}
// データベース保存処理のシミュレーション
System.Threading.Thread.Sleep(200);
logger.Info($"ユーザーデータ保存成功: UserID={userId}");
}
}
パフォーマンス考慮とベストプラクティス
// パフォーマンス最適化の例
public class PerformanceOptimizedLogging
{
private static readonly ILog logger = LogManager.GetLogger(typeof(PerformanceOptimizedLogging));
public static void Main(string[] args)
{
// パフォーマンステスト
PerformanceTest();
// メモリ使用量テスト
MemoryUsageTest();
// 非同期処理模擬
AsyncProcessingSimulation();
}
private static void PerformanceTest()
{
logger.Info("パフォーマンステスト開始");
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
int logCount = 10000;
for (int i = 0; i < logCount; i++)
{
// 条件チェックによる最適化
if (logger.IsDebugEnabled)
{
logger.Debug($"デバッグログ: 反復={i}");
}
// INFO以上の場合のみ文字列フォーマット実行
if (logger.IsInfoEnabled)
{
logger.InfoFormat("情報ログ: 反復={0}, タイムスタンプ={1}", i, DateTime.Now);
}
// 重いログは条件付きで
if (i % 1000 == 0 && logger.IsWarnEnabled)
{
logger.WarnFormat("進捗ログ: {0}/{1} 完了", i, logCount);
}
}
stopwatch.Stop();
double throughput = logCount / stopwatch.Elapsed.TotalSeconds;
logger.InfoFormat("パフォーマンステスト完了: 時間={0}ms, スループット={1:F0} logs/sec",
stopwatch.ElapsedMilliseconds, throughput);
}
private static void MemoryUsageTest()
{
logger.Info("メモリ使用量テスト開始");
long startMemory = GC.GetTotalMemory(true);
// 大量のログ出力
for (int i = 0; i < 5000; i++)
{
logger.InfoFormat("メモリテスト: オブジェクト={0}", new { Id = i, Name = $"Object{i}" });
}
long endMemory = GC.GetTotalMemory(true);
long memoryDiff = endMemory - startMemory;
logger.InfoFormat("メモリ使用量: 開始={0:N0} bytes, 終了={1:N0} bytes, 差分={2:N0} bytes",
startMemory, endMemory, memoryDiff);
}
private static void AsyncProcessingSimulation()
{
logger.Info("非同期処理シミュレーション開始");
// 複数スレッドでのログ出力
var tasks = new System.Threading.Tasks.Task[5];
for (int taskId = 0; taskId < tasks.Length; taskId++)
{
int capturedTaskId = taskId;
tasks[taskId] = System.Threading.Tasks.Task.Run(() =>
{
// スレッドローカル情報設定
log4net.ThreadContext.Properties["TaskId"] = capturedTaskId;
for (int i = 0; i < 1000; i++)
{
logger.InfoFormat("タスク{0}: 処理{1}", capturedTaskId, i);
if (i % 100 == 0)
{
logger.DebugFormat("タスク{0}: 進捗 {1}%", capturedTaskId, i / 10);
}
}
logger.InfoFormat("タスク{0}: 完了", capturedTaskId);
log4net.ThreadContext.Properties.Clear();
});
}
System.Threading.Tasks.Task.WaitAll(tasks);
logger.Info("非同期処理シミュレーション完了");
}
}
// 設定によるパフォーマンス最適化
/*
Web.config での最適化設定例:
<appSettings>
<!-- log4netの内部デバッグを無効化 -->
<add key="log4net.Internal.Debug" value="false" />
</appSettings>
<log4net>
<!-- 非同期アペンダーの使用 -->
<appender name="AsyncAppender" type="log4net.Appender.AsyncAppender">
<bufferSize value="1000" />
<appender-ref ref="RollingFileAppender" />
</appender>
<!-- バッファリング設定 -->
<appender name="BufferedFileAppender" type="log4net.Appender.BufferingForwardingAppender">
<bufferSize value="100" />
<appender-ref ref="RollingFileAppender" />
</appender>
</log4net>
*/