简体   繁体   English

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

[英]Logging ClassName and MethodName using log4net for a .NET project

I have been looking for a way to log class names and method names as part of my logging infrastructure. 我一直在寻找一种方法来记录类名和方法名作为我的日志记录基础结构的一部分。 Obviously I'd like to make it simple to use and fast at runtime. 显然,我希望在运行时使用简单快捷。 I've done a lot of reading about logging class names and method names, but I've run across 2 themes. 我已经做了很多关于记录类名和方法名的阅读,但我遇到了2个主题。

  1. That log4net uses an internally throws exception to generate a stack frame and that gets expensive if you use that generally for all logging. log4net使用内部抛出异常来生成堆栈帧,如果通常对所有日志记录使用它,则会变得昂贵。
  2. Confusion. 混乱。 There is a lot of literature out there. 那里有很多文献。 I've tried a bunch of it and not gotten to something useful. 我已经尝试了一堆它并没有得到一些有用的东西。

If you humor me for a second, I'd like to reset. 如果你幽默我一秒钟,我想重置。

I created a class like this in my project 我在我的项目中创建了这样一个类

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;
        }
    }
}

(If this code looks familiar it's because it is borrowed from the examples!) (如果这段代码看起来很熟悉,那是因为它是从例子中借来的!)

In any case, throughout my project I use lines like this to log: 无论如何,在我的项目中,我使用这样的行来记录:

        Log.Info(typeof(Program).Name, "System Start");

Well, this kind of works. 好吧,这种作品。 Most importantly, I get the class name but no method name. 最重要的是,我获得了类名但没有方法名。 Less, importantly, I am polluting my code with this "typeof" junk. 更重要的是,我用这种“类型”的垃圾来污染我的代码。 If I copy and paste a snippet of code between files, etc. the logging framework will be lying! 如果我在文件等之间复制并粘贴一段代码,那么日志框架就会撒谎!

I tried playing with the PatternLayout (%C{1}.{M}) but that didn't work (all it did was write "Log.Info" to the log--because everything is routing through the Log.X static methods!). 我尝试使用PatternLayout(%C {1}。{M}),但这不起作用(它只是将“Log.Info”写入日志 - 因为一切都是通过Log.X静态方法进行路由!)。 Besides, that's supposed to be slow. 此外,这应该是缓慢的。

So, what is the best way, given my setup and my desire to be simple and fast? 那么,考虑到我的设置和我想要简单快速的最佳方式,最好的方法是什么?

Appreciate any help in advance. 提前感谢任何帮助。

log4net (and NLog) both expose a logging method that makes it possible to "wrap" their loggers and still get correct the call site information. log4net(和NLog)都公开了一种日志记录方法,可以“包装”他们的记录器并仍然可以正确调用站点信息。 Essentially, the log4net (or NLog) logger needs to be told the Type that forms the "boundary" between logging code and application code. 本质上,需要告诉log4net(或NLog)记录器,它构成了记录代码和应用程​​序代码之间的“边界”。 I think they call this the "logger type" or something similar. 我认为他们称之为“记录器类型”或类似的东西。 When the libraries get the call site information, they navigate up the call stack until the MethodBase.DeclaringType is equal (or maybe AssignableFrom) the "logger type". 当库获取调用站点信息时,它们向上导航调用堆栈,直到MethodBase.DeclaringType等于(或者可能是AssignableFrom)“记录器类型”。 The next stack frame will be the application calling code. 下一个堆栈帧将是应用程序调用代码。

Here is an example of how to log via NLog from within a wrapper (log4net would be similar - look in log4net docs for ILogger (not ILog) interface: 下面是一个如何从包装器中通过NLog进行日志记录的示例(log4net类似 - 在logognet(而不是ILog)接口的log4net文档中查看:

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

  _logger.Log(declaringType, logEvent);

Where declaringType is a member variable that was set something like this: 其中declaringType是一个成员变量,其设置如下:

  private readonly static Type declaringType = typeof(AbstractLogger);

And "AbstractLogger" is the type of your logger wrapper. “AbstractLogger”是记录器包装器的类型。 In your case, it would probably look like this: 在你的情况下,它可能看起来像这样:

  private readonly static Type declaringType = typeof(Log);

If NLog needs to get the call site info (because of call site operators in the layout), it will navigate up the stack until the MethodBase.DeclaringType for the current frame is equal (or AssignableFrom) declaringType. 如果NLog需要获取调用站点信息(因为布局中的调用站点操作符),它将向上导航到堆栈,直到当前帧的MethodBase.DeclaringType相等(或AssignableFrom)declaringType。 The next frame in the stack will be the actual call site. 堆栈中的下一帧将是实际的呼叫站点。

Here is some code that will work for logging with a "wrapped" log4net logger. 下面是一些代码,可用于使用“包装”的log4net记录器进行日志记录。 It uses the log4net ILogger interface and passes the type of the "wrapping" logger to preserve the call site information. 它使用log4net ILogger接口并传递“包装”记录器的类型以保留呼叫站点信息。 You don't have to fill in an event class/structure with this method: 您不必使用此方法填充事件类/结构:

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

Again, "declaringType" is the type of your wrapper. 同样,“declaringType”是包装器的类型。 _logger is the log4net logger, Level is the log4net.LogLevel value, message is the message, exception is the exception (if any, null otherwise). _logger是log4net记录器,Level是log4net.LogLevel值,message是消息,exception是异常(如果有的话,否则为null)。

As far as polluting your call sites with Typeof(whatever), I think you are stuck with that if you want to use a single static "Log" object. 至于使用Typeof(无论如何)污染您的呼叫站点,我认为如果您想使用单个静态“Log”对象,您将坚持使用它。 Alternatively, inside of the "Log" object's logging methods, you could get the calling method like the accepted answer in this post 或者,在“Log”对象的日志记录方法内部,您可以获得此帖子中的接受答案之类的调用方法

How can I find the method that called the current method? 如何找到调用当前方法的方法?

That link shows how to get the immediately preceding caller. 该链接显示了如何获得前一个调用者。 If you needed to get the method that called the logging function, but your work is being done a couple of layers deeper, you will need to go up the stack some number of frames rather than just one frame. 如果您需要获取调用日志记录功能的方法,但是您的工作正在进行更深层次的处理,则需要向上堆栈一些帧而不是一帧。

Taking all of this together, you would write your Debug method something like this (again, this is in terms of NLog because that is what I have in front of me): 把所有这些放在一起,你会写这样的调试方法(再次,这是在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)
}

Note that you probably won't find many people here on StackOverflow that would recommend writing a logging wrapper function like this (that explicitly gets the calling method for ever log call). 请注意,您可能在StackOverflow上找不到很多人会建议编写这样的日志包装函数(显式获取永远日志调用的调用方法)。 I can't say that I would recommend it either, but it does more or less answer the question that you asked. 我不能说我会推荐它,但它或多或少地回答了你提出的问题。 If you want to use a static "Log" object, then you will either have to explicitly pass the Type at each logging call site (to get the correct class logger) or you will have to add code inside of the logging call to navigate the stack and figure out that information for yourself. 如果要使用静态“Log”对象,则必须在每个日志记录调用站点显式传递Type(以获取正确的类记录器),或者必须在日志记录调用内添加代码以导航堆叠并为自己找出这些信息。 I don't think that either of those options are particularly attractive. 我不认为这些选择中的任何一个都特别有吸引力。

Now, having said all of that, you might consider using either log4net or NLog directly rather than adding this complicated (and not necessarily reliable) code for getting the call site information. 现在,尽管如此,您可以考虑直接使用log4net或NLog,而不是添加这个复杂的(并不一定是可靠的)代码来获取呼叫站点信息。 As Matthew points out, NLog provides an easy way to get the logger for the current class. 正如Matthew所指出的,NLog提供了一种简单的方法来获取当前类的记录器。 To get the logger for the current class using log4net, you would do this in every class: 要使用log4net获取当前类的记录器,您可以在每个类中执行此操作:

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

vs this way with NLog: 用NLog这种方式:

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

That is a pretty common usage. 这是一种非常常见的用法。

If you don't want to be dependent on a particular logging implementation, you could use one of the logging abstractions available such as Common.Logging (NET) or Simple Logging Facade (SLF) . 如果您不想依赖于特定的日志记录实现,则可以使用其中一个可用的日志记录抽象,例如Common.Logging(NET)Simple Logging Facade(SLF)

Even if you don't use one of these abstractions, download the source for Common.Logging and look at the abstraction for log4net. 即使您不使用其中一个抽象,也请下载Common.Logging的源代码并查看log4net的抽象。 It will show exactly how to wrap a log4net logger such that the call site information is preserved (and available to the layout operators). 它将准确显示如何包装log4net记录器,以便保留调用站点信息(并且可供布局操作员使用)。

I also did some research on this and I believe the only way to do this efficiently is to wrap the logging functions, as much as I hate to do so: 我也对此做了一些研究,我相信有效地做到这一点的唯一方法是包装日志记录功能,就像我讨厌这样做:

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);
}

Notes: 笔记:

  • This is the wrapper function I wrote around ILog::Info, you would need one around the other logging levels as well. 这是我在ILog :: Info周围编写的包装函数,你需要一个围绕其他日志记录级别。
  • This requires Caller Information which is only available starting .Net 4.5. 这需要来电信息 ,只能从.Net 4.5开始。 The up-side is that these variables are replaced at compile-time with string literals, so it's all very efficient. 另一方面,这些变量在编译时被字符串文字替换,因此它们都非常有效。
  • For simplicity I left the sourceFilePath parameter as-is, you would probably prefer to format it (trim most/all of its path) 为简单起见,我按原样保留了sourceFilePath参数,你可能更喜欢格式化它(修剪它的大部分/全部路径)

I prefer a pattern like the following, which works with Log4Net and similar APIs: 我更喜欢以下模式,它适用于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 ...);
        }
    }

}

Some remarks: 一些评论:

  • With this pattern, you are evaluating typeof(MyClass) only once - compared with your sample where you're calling object.GetType() on every logging call, whether or not the corresponding logging level is enabled. 使用此模式,您只评估typeof(MyClass)一次 - 与您在每次日志记录调用时调用object.GetType()的示例进行比较,无论是否启用了相应的日志记录级别。 Not a big deal, but in general it's desirable for logging to have minimal overhead. 没什么大不了的,但总的来说,日志记录的开销很小。

  • You still need to use typeof, and make sure you don't get the wrong class name when using copy/paste. 您仍然需要使用typeof,并确保在使用复制/粘贴时没有获得错误的类名。 I prefer to live with this, because the alternative (eg NLog's LogManager.GetCurrentClassLogger as described in Matthew Ferreira's reply) requires getting a StackFrame which has a performance overhead, and requires that the calling code have UnmanagedCode permission. 我更喜欢这样,因为替代方案(例如,Matthew Ferreira的回复中描述的NLog的LogManager.GetCurrentClassLogger)需要获得具有性能开销的StackFrame,并要求调用代码具有UnmanagedCode权限。 As an aside, I think it would be nice if C# provided some compile-time syntax to refer to the current class - something like the C++ _ class _ macro. 顺便说一句,我认为如果C#提供了一些编译时语法来引用当前类 - 比如C ++ _ class _ macro,那就太好了。

  • I would abandon any attempt to get the current method name for three reasons. 由于三个原因,我会放弃任何获取当前方法名称的尝试。 (1) There is a significant performance overhead overhead, and logging should be fast. (1)存在显着的性能开销,并且记录应该很快。 (2) Inlining means you may not get the method you think you're getting. (2)内联意味着您可能无法获得您认为自己获得的方法。 (3) It imposes the requirement for UnmanagedCode permission. (3)它强制要求UnmanagedCode权限。

I know you already have code that depends on log4net, but have you given any thought to another logging framework that may better meet your requirements? 我知道你已经有了依赖于log4net的代码,但是你有没有考虑过另一个可能更符合你要求的日志框架? I personally use NLog for my own applications. 我个人使用NLog作为我自己的应用程序。 It allows code like this: 它允许这样的代码:

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

    // ...

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

By default, NLog will add the class name and method name to its logged messages. 默认情况下,NLog会将类名和方法名添加到其记录的消息中。 It has an API very similar to log4net and includes both XML and programmatic configuration. 它有一个非常类似于log4net的API,包括XML和编程配置。 It may be worth your time. 这可能值得你的时间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM