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

时间:2010-09-22 15:30:11

标签: c# logging log4net

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

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

    我在我的项目

    中创建了一个这样的类
    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);
        }
    

    ...

        private static void initialize() {
            XmlConfigurator.Configure(); 
        }
    
        public static void EnsureInitialized() {
            if (!_logInitialized) {
                initialize();
                _logInitialized = true;
            }
        }
    }
    

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

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

            Log.Info(typeof(Program).Name, "System Start");
    
    嗯,这种作品。最重要的是,我获得了类名但没有方法名。更重要的是,我用这种“类型”的垃圾来污染我的代码。如果我在文件等之间复制并粘贴一段代码,那么日志框架就会撒谎!

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

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

    提前感谢任何帮助。

4 个答案:

答案 0 :(得分: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);

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

  private readonly static Type declaringType = typeof(AbstractLogger);

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

  private readonly static Type declaringType = typeof(Log);

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

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

  _logger.Log(declaringType, level, message, exception);

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

就使用Typeof(无论如何)污染您的呼叫站点而言,如果您想使用单个静态“Log”对象,我认为您会坚持使用它。或者,在“Log”对象的日志记录方法中,您可以获得调用方法,如此帖子中接受的答案

How can I find the method that called the current method?

该链接显示如何获取前一个调用者。如果您需要获取调用日志记录功能的方法,但是您的工作正在深入了解几层,则需要向上堆叠一些帧而不是一帧。

把所有这些放在一起,你会写这样的调试方法(再次,这是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)
}

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

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

private static readonly log4net.ILog log = log4net.LogManager.GetLogger( 
        System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 

使用NLog以这种方式:

  private static readonly NLog.logger log = NLog.LogManager.GetCurrentClassLogger();

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

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

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

答案 1 :(得分:3)

我更喜欢以下模式,它适用于Log4Net和类似的API:

class MyClass
{
    private static readonly ILog logger = LogManager.GetLogger(typeof(MyClass));

    void SomeMethod(...)
    {
        logger.Info("some message");

        ...

        if (logger.IsInfoEnabled)
        {
            logger.Info(... something that is expensive to generate ...);
        }
    }

}

一些评论:

  • 使用此模式,您只评估typeof(MyClass)一次 - 与您在每次日志记录调用时调用object.GetType()的示例进行比较,无论是否启用了相应的日志记录级别。没什么大不了的,但总的来说,日志记录的开销很小。

  • 您仍然需要使用typeof,并确保在使用复制/粘贴时没有获得错误的类名。我更喜欢这样,因为替代方案(例如,Matthew Ferreira的回复中描述的NLog的LogManager.GetCurrentClassLogger)需要获得具有性能开销的StackFrame,并要求调用代码具有UnmanagedCode权限。顺便说一句,我认为如果C#提供一些编译时语法来引用当前类 - 比如C ++ _ _ 宏,那将会很好。

  • 由于三个原因,我会放弃任何获取当前方法名称的尝试。 (1)存在显着的性能开销,并且记录应该很快。 (2)内联意味着您可能无法获得您认为自己获得的方法。 (3)它强制要求UnmanagedCode权限。

答案 2 :(得分:3)

我也对此做了一些研究,我相信有效地做到这一点的唯一方法是包装日志记录功能,就像我讨厌这样做:

public static void InfoWithCallerInfo(this ILog logger, 
    object message, Exception e = null, [CallerMemberName] string memberName = "",
    [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
    if (!logger.IsInfoEnabled)
        return;
    if (e == null)
        logger.Info(string.Format("{0}:{1}:{2} {3}", sourceFilePath, 
            memberName, sourceLineNumber, message));
    else
        logger.Info(string.Format("{0}:{1}:{2} {3}", sourceFilePath, 
            memberName, sourceLineNumber, message), e);
}

注意:

  • 这是我在ILog :: Info周围编写的包装函数,你需要围绕其他日志记录级别。
  • 这需要Caller Information,只能从.Net 4.5开始。最重要的是,这些变量在编译时被字符串文字替换,所以它们都非常有效。
  • 为简单起见,我按原样保留sourceFilePath参数,您可能更愿意对其进行格式化(修剪其大部分/全部路径)

答案 3 :(得分:1)

我知道您已经拥有依赖于log4net的代码,但您是否考虑过另一个可能更符合您要求的日志框架?我个人使用NLog作为我自己的应用程序。它允许这样的代码:

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

    // ...

    void DoStuff()
    {
        logger.Info("blah blah");
    }
}

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