私は.net(c#)のロギングフレームワークを探しており、ここでStackoverflowに関するいくつかの質問/回答スレッドを読んだ後、log4netを試してみることにしました。 log4netのラッパークラスを使用していることを何度も言及している人がいますが、それがどのように見えるのでしょうか。
コードを複数のプロジェクトに分割しています(データアクセス/ビジネス/ウェブサービス/ ..)。 log4netラッパークラスはどのように見えますか?ラッパークラスをすべてのプロジェクトに含める必要がありますか?それをすべて別個のプロジェクトとしてビルドする必要がありますか?
ラッパーはシングルトンクラスにする必要がありますか?
基本的には、インターフェースを作成し、Log4netのクラスとメソッドを直接ラップするインターフェースの具体的な実装を作成します。追加のロギングシステムは、それらのシステムの他のクラスとメソッドをラップするより具体的なクラスを作成することによってラップできます。最後に、ファクトリーを使用して、構成設定またはコード変更の行に基づいてラッパーのインスタンスを作成します。 (注: StructureMap などの Inversion of Control コンテナーを使用すると、より柔軟かつ複雑になります。)
public interface ILogger
{
void Debug(object message);
bool IsDebugEnabled { get; }
// continue for all methods like Error, Fatal ...
}
public class Log4NetWrapper : ILogger
{
private readonly log4net.ILog _logger;
public Log4NetWrapper(Type type)
{
_logger = log4net.LogManager.GetLogger(type);
}
public void Debug(object message)
{
_logger.Debug(message);
}
public bool IsDebugEnabled
{
get { return _logger.IsDebugEnabled; }
}
// complete ILogger interface implementation
}
public static class LogManager
{
public static ILogger GetLogger(Type type)
{
// if configuration file says log4net...
return new Log4NetWrapper(type);
// if it says Joe's Logger...
// return new JoesLoggerWrapper(type);
}
}
そして、このコードをクラスで使用する例(静的な読み取り専用フィールドとして宣言):
private static readonly ILogger _logger =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
次のコマンドを使用すると、パフォーマンスにやさしい同じ効果を得ることができます。
private static readonly ILogger _logger =
LogManager.GetLogger(typeof(YourTypeName));
前者の例は、より保守しやすいと考えられています。
すべてのログを処理するシングルトンを作成したくないのは、呼び出しタイプのLog4Netログのためです。すべてのメッセージを報告するログファイルで単一のタイプを表示するだけでなく、各タイプに独自のロガーを使用させるほうがはるかにクリーンで便利です。
実装はかなり再利用可能である必要があるため(組織内の他のプロジェクト)、それを独自のアセンブリにするか、理想的には独自の個人/組織のフレームワーク/ユーティリティアセンブリに含めることができます。維持できないため、ビジネス、データ、UIの各アセンブリでクラスを個別に再宣言しないでください。
上記のcfedukeの答え のようなものを使っていたとすると、次のようにLogManager
にオーバーロードを追加することもできます:
_public static ILogger GetLogger()
{
var stack = new StackTrace();
var frame = stack.GetFrame(1);
return new Log4NetWrapper(frame.GetMethod().DeclaringType);
}
_
このようにして、コードで次のように使用できます。
_private static readonly ILogger _logger = LogManager.GetLogger();
_
これらのいずれかの代わりに:
_private static readonly ILogger _logger =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly ILogger _logger =
LogManager.GetLogger(typeof(YourTypeName));
_
これは最初の代替案(つまりMethodBase.GetCurrentMethod().DeclaringType
を使用する代替案)と実質的に同等ですが、少しだけ簡単です。
Log4netのラッパーを作成しないことを計画している利点は何ですか。ラッパーを記述する前に、まずlog4netクラスに慣れることをお勧めします。 cfedukeは上記のラッパーの記述方法についての彼の回答に正解ですが、実際の機能を彼の例に追加する必要がない限り、ラッパーはロギングプロセスの速度を落とし、将来のメンテナーの複雑さを増すだけで成功します。これは、.Netで利用可能なリファクタリングツールがそのような変更を非常に簡単にする場合に特に当てはまります。
Log4netの依存関係を1つのプロジェクトに分離することに成功しました。同じことをするつもりなら、これが私のラッパークラスのようになります。
using System;
namespace Framework.Logging
{
public class Logger
{
private readonly log4net.ILog _log;
public Logger()
{
_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
}
public Logger(string name)
{
_log = log4net.LogManager.GetLogger(name);
}
public Logger(Type type)
{
_log = log4net.LogManager.GetLogger(type);
}
public void Debug(object message, Exception ex = null)
{
if (_log.IsDebugEnabled)
{
if (ex == null)
{
_log.Debug(message);
}
else
{
_log.Debug(message, ex);
}
}
}
public void Info(object message, Exception ex = null)
{
if (_log.IsInfoEnabled)
{
if (ex == null)
{
_log.Info(message);
}
else
{
_log.Info(message, ex);
}
}
}
public void Warn(object message, Exception ex = null)
{
if (_log.IsWarnEnabled)
{
if (ex == null)
{
_log.Warn(message);
}
else
{
_log.Warn(message, ex);
}
}
}
public void Error(object message, Exception ex = null)
{
if (_log.IsErrorEnabled)
{
if (ex == null)
{
_log.Error(message);
}
else
{
_log.Error(message, ex);
}
}
}
public void Fatal(object message, Exception ex = null)
{
if (_log.IsFatalEnabled)
{
if (ex == null)
{
_log.Fatal(message);
}
else
{
_log.Fatal(message, ex);
}
}
}
}
}
そして、これをインターフェースプロジェクトのAssemblyInfo.cs
に追加することを忘れないでください(これを見つけるのに数時間かかった)
[Assembly: log4net.Config.XmlConfigurator(Watch = true, ConfigFile = "log4net.config")]
そして、log4net構成xmlをlog4net.config
ファイルに入れ、Content
、Copy Always
として設定します
WPFのプリズムライブラリ のようなフレームワークがあり、選択したロギングフレームワークで facade の使用を促進します。
これは log4net を使用する例です:
using System;
using log4net;
using log4net.Core;
using Prism.Logging;
public class Log4NetLoggerFacade : ILoggerFacade
{
private static readonly ILog Log4NetLog = LogManager.GetLogger(typeof (Log4NetLoggerFacade));
public void Log(string message, Category category, Priority priority)
{
switch (category)
{
case Category.Debug:
Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Debug, message, null);
break;
case Category.Exception:
Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Error, message, null);
break;
case Category.Info:
Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Info, message, null);
break;
case Category.Warn:
Log4NetLog.Logger.Log(typeof(Log4NetLoggerFacade), Level.Warn, message, null);
break;
default:
throw new ArgumentOutOfRangeException(nameof(category), category, null);
}
}
}
callerStackBoundaryDeclaringType
を指定すると、ロギング要求を発行する呼び出し元のクラス名を取得できることに注意してください。変換パターンに%C %M
を含めるだけです。
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %C.%M - %message%newline" />
</layout>
ただし、 documentation が警告するように、呼び出し元クラス情報の生成は遅いため、賢明に使用する必要があります。
私の理解では、log4netのラッパークラスは、app.config/web.configから、またはコード(NUnitとの統合など)によるロギングオブジェクトの初期化を処理する静的クラスになります。
log4netラッパーの可能な使用法は、リフレクションを介して呼び出し元のクラスとメソッドを取得するクラスであり、ロギングエントリが発生した場所を把握することができます。少なくとも私はこれを頻繁に使用しています。
Alconja、スタックトレースを使用して呼び出し元のメソッドに戻るというアイデアが気に入っています。ロガーオブジェクトを取得するだけでなく、実際にロギングを実行するために、呼び出しをさらにカプセル化することを考えていました。私が欲しいのは、使用されている特定の実装から抽象化することにより、ロギングを処理する静的クラスです。つまり.
LoggingService.LogError("my error message");
そうすれば、後で別のロギングシステムを使用することにした場合にのみ、静的クラスの内部を変更するだけで済みます。
だから私はあなたのアイデアを使って、スタックトレースを使って呼び出しオブジェクトを取得しました:
public static class LoggingService
{
private static ILog GetLogger()
{
var stack = new StackTrace();
var frame = stack.GetFrame(2);
return log4net.LogManager.GetLogger(frame.GetMethod().DeclaringType);
}
public static void LogError(string message)
{
ILog logger = GetLogger();
if (logger.IsErrorEnabled)
logger.Error(message);
}
...
}
誰かがこのアプローチに問題を見ていますか?