使用log4net为.NET项目记录ClassName和MethodName

Ste*_*ker 9 c# logging log4net

我一直在寻找一种方法来记录类名和方法名作为我的日志记录基础结构的一部分.显然,我想在运行时使用简单快捷.我已经做了大量关于记录类名和方法名的阅读,但我遇到了2个主题.

  1. log4net使用内部抛出异常来生成堆栈帧,如果通常对所有日志记录使用它,则会变得昂贵.
  2. 混乱.那里有很多文献.我已经尝试了一堆它并没有得到一些有用的东西.

如果你幽默我一秒钟,我想重置.

我在我的项目中创建了这样一个类

public static class Log {
    private static Dictionary<Type, ILog> _loggers = new Dictionary<Type, ILog>();
    private static bool _logInitialized = false;
    private static object _lock = new object();

    public static string SerializeException(Exception e) {
        return SerializeException(e, string.Empty);
    }

    private static string SerializeException(Exception e, string exceptionMessage) {
        if (e == null) return string.Empty;

        exceptionMessage = string.Format(
            "{0}{1}{2}\n{3}",
            exceptionMessage,
            (exceptionMessage == string.Empty) ? string.Empty : "\n\n",
            e.Message,
            e.StackTrace);

        if (e.InnerException != null)
            exceptionMessage = SerializeException(e.InnerException, exceptionMessage);

        return exceptionMessage;
    }

    private static ILog getLogger(Type source) {
        lock (_lock) {
            if (_loggers.ContainsKey(source)) {
                return _loggers[source];
            }

            ILog logger = log4net.LogManager.GetLogger(source);
            _loggers.Add(source, logger);
            return logger;
        }
    }

    public static void Debug(object source, object message) {
        Debug(source.GetType(), message);
    }

    public static void Debug(Type source, object message) {
        getLogger(source).Debug(message);
    }

    public static void Info(object source, object message) {
        Info(source.GetType(), message);
    }

    public static void Info(Type source, object message) {
        getLogger(source).Info(message);
    }
Run Code Online (Sandbox Code Playgroud)

...

    private static void initialize() {
        XmlConfigurator.Configure(); 
    }

    public static void EnsureInitialized() {
        if (!_logInitialized) {
            initialize();
            _logInitialized = true;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(如果这段代码看起来很熟悉,那是因为它是从例子中借来的!)

无论如何,在我的项目中,我使用这样的行来记录:

        Log.Info(typeof(Program).Name, "System Start");
Run Code Online (Sandbox Code Playgroud)

好吧,这种作品.最重要的是,我获得了类名但没有方法名.更重要的是,我用这种"类型"的垃圾来污染我的代码.如果我在文件等之间复制并粘贴一段代码,那么日志框架就会撒谎!

我尝试使用PatternLayout(%C {1}.{M}),但这不起作用(它只是将"Log.Info"写入日志 - 因为一切都是通过Log.X静态方法进行路由!).此外,这应该是缓慢的.

那么,考虑到我的设置和我想要简单快速的最佳方式,最好的方法是什么?

提前感谢任何帮助.

wag*_*ghe 9

log4net(和NLog)都公开了一种日志记录方法,可以"包装"他们的记录器并仍然可以正确调用站点信息.本质上,需要告诉log4net(或NLog)记录器,它构成了记录代码和应用程​​序代码之间的"边界".我认为他们称之为"记录器类型"或类似的东西.当库获取调用站点信息时,它们向上导航调用堆栈,直到MethodBase.DeclaringType等于(或者可能是AssignableFrom)"记录器类型".下一个堆栈帧将是应用程序调用代码.

下面是一个如何从包装器中通过NLog进行日志记录的示例(log4net类似 - 在logognet(而不是ILog)接口的log4net文档中查看:

  LogEventInfo logEvent = new LogEventInfo(level, _logger.Name, null, "{0}", new object[] { message }, exception);

  _logger.Log(declaringType, logEvent);
Run Code Online (Sandbox Code Playgroud)

其中declaringType是一个成员变量,其设置如下:

  private readonly static Type declaringType = typeof(AbstractLogger);
Run Code Online (Sandbox Code Playgroud)

"AbstractLogger"是记录器包装器的类型.在你的情况下,它可能看起来像这样:

  private readonly static Type declaringType = typeof(Log);
Run Code Online (Sandbox Code Playgroud)

如果NLog需要获取调用站点信息(因为布局中的调用站点操作符),它将向上导航到堆栈,直到当前帧的MethodBase.DeclaringType相等(或AssignableFrom)declaringType.堆栈中的下一帧将是实际的呼叫站点.

下面是一些代码,可用于使用"包装"的log4net记录器进行日志记录.它使用log4net ILogger接口并传递"包装"记录器的类型以保留呼叫站点信息.您不必使用此方法填充事件类/结构:

  _logger.Log(declaringType, level, message, exception);
Run Code Online (Sandbox Code Playgroud)

同样,"declaringType"是包装器的类型._logger是log4net记录器,Level是log4net.LogLevel值,message是消息,exception是异常(如果有的话,否则为null).

至于使用Typeof(无论如何)污染您的呼叫站点,我认为如果您想使用单个静态"Log"对象,您将坚持使用它.或者,在"Log"对象的日志记录方法中,您可以获得调用方法,就像本文中接受的答案一样

如何找到调用当前方法的方法?

该链接显示了如何获得前一个调用者.如果您需要获取调用日志记录功能的方法,但是您的工作正在进行更深层次的处理,则需要向上堆栈一些帧而不是一帧.

把所有这些放在一起,你会写这样的调试方法(再次,这是NLog,因为这就是我面前的情况):

public static void Debug(object message)
{
  MethodBase mb = GetCallingMethod();
  Type t = mb.DeclaringType;
  LogEventInfo logEvent = new LogEventInfo(LogLevel.Debug, t.Name, null, "{0}", new object [] message, null);
  ILogger logger = getLogger(t) As ILogger;
  logger.Log(declaringType, logEvent)
}
Run Code Online (Sandbox Code Playgroud)

请注意,您可能在StackOverflow上找不到很多人会建议编写这样的日志包装函数(显式获取永远日志调用的调用方法).我不能说我会推荐它,但它或多或少地回答了你提出的问题.如果要使用静态"Log"对象,则必须在每个日志记录调用站点显式传递Type(以获取正确的类记录器),或者必须在日志记录调用内添加代码以导航堆叠并为自己找出这些信息.我不认为这些选择中的任何一个都特别有吸引力.

现在,尽管如此,您可以考虑直接使用log4net或NLog,而不是添加这个复杂的(并不一定是可靠的)代码来获取呼叫站点信息.正如Matthew所指出的,NLog提供了一种简单的方法来获取当前类的记录器.要使用log4net获取当前类的记录器,您可以在每个类中执行此操作:

private static readonly log4net.ILog log = log4net.LogManager.GetLogger( 
        System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 
Run Code Online (Sandbox Code Playgroud)

用NLog这种方式:

  private static readonly NLog.logger log = NLog.LogManager.GetCurrentClassLogger();
Run Code Online (Sandbox Code Playgroud)

这是一种非常常见的用法.

如果您不想依赖于特定的日志记录实现,则可以使用其中一个可用的日志记录抽象,例如Common.Logging(NET)Simple Logging Facade(SLF).

即使您不使用其中一个抽象,也请下载Common.Logging的源代码并查看log4net的抽象.它将准确显示如何包装log4net记录器,以便保留调用站点信息(并且可供布局操作员使用).


小智 1

我知道您已经有了依赖于 log4net 的代码,但是您是否考虑过另一个可以更好地满足您的要求的日志框架?我个人将NLog用于我自己的应用程序。它允许这样的代码:

class Stuff
{
    private static readonly Logger logger = LogManager.GetCurrentClassLogger();

    // ...

    void DoStuff()
    {
        logger.Info("blah blah");
    }
}
Run Code Online (Sandbox Code Playgroud)

默认情况下,NLog 会将类名和方法名添加到其记录的消息中。它的 API 与 log4net 非常相似,并且包括 XML 和编程配置。这可能值得您花时间。