如何在包装Log4net时记录MethodName?

34

我已经用一个静态的包装器包装了Log4net,并想记录日志

loggingEvent.LocationInformation.MethodName
loggingEvent.LocationInformation.ClassName

但是我只得到了我的包装器的名称。

我如何使用ForwardingAppender和一个静态包装器类记录该信息?

Logger.Debug("Logging to Debug");
Logger.Info("Logging to Info");
Logger.Warn("Logging to Warn");
Logger.Error(ex);
Logger.Fatal(ex);

如果我没记错的话,log4net会从你传递给LogManager.GetLogger(Type)调用的Type中填充LocationInformation,因此它呈现来自你的包装器的信息是合理的(我假设你的包装器会这样做:ILog log = LogManager.GetLogger(typeof(MyLogWrapper))。 - Panos
1
实际上,我像这样包装它:LoggerManager.GetLogger(Assembly.GetCallingAssembly(),"MyDefaultLoggger"),以避免它。 - Claus Thomsen
11个回答

32

对于我来说,+1应该是这个问题的答案。您对此是否更高效的评论如何? - Josep
12
请注意主题中的“when wrapping”。在问题作者的示例中(当包装ILog时),%M将等同于“Debug”,“Info”等,这是没有用的。 - nightcoder
我很惊讶这个帖子有那么多点赞,因为它并没有解决问题。 - Welcor

29

错误出现在我的appender中,但为了完整起见,我将包括我所知道的最好答案:

你需要的Facade应该包装ILogger而不是ILog。

 public static class Logger
 {
    private readonly static Type ThisDeclaringType = typeof(Logger);
    private static readonly ILogger defaultLogger;

    static Logger()
    {
      defaultLogger =
        LoggerManager.GetLogger(Assembly.GetCallingAssembly(),"MyDefaultLoggger");

...

    public static void Info(string message)
    {
        if (defaultLogger.IsEnabledFor(infoLevel))
        {
            defaultLogger.Log(typeof(Logger), infoLevel, message, null);
        }
    }

2
我简直不敢相信这个信息竟然这么难找到。谢谢! - Billy Jake O'Connor

8
我会简单地使用像%stacktrace{2}这样的转换模式。
输出示例:

MyNamespace.ClassName.Method > Common.Log.Warning

其中MyNamespace.ClassName.Method是调用我的包装器的方法,而Common.Log.Warning是包装器类的一个方法。
可以在此处找到转换模式。

1
%stacktrace{2} 对我帮助很大 - 谢谢! - ppenchev

5
只需像这样声明您的日志变量...
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

然后您就可以正常使用它了。

4
我对此问题实现了以下解决方案(.Net Framework 4.5+):log4net的包装方法(例如Info、Warn、Error)可以利用CallerMemberName和CallerFilePath获取调用日志的代码所在类和方法名称。然后,您可以将它们聚合到自定义的log4net属性中。
请随意使用您自己的log4net包装器实现,这里唯一重要的是:Info和Error方法的签名以及GetLogger方法的实现。
当调用Logger.Info或Logger.Error方法时,不应指定“memberName”和“sourceFilePath”参数,它们由.Net自动填充。
public static class Logger
{
    private class LogSingletonWrapper
    {
        public ILog Log { get; set; }
        public LogSingletonWrapper()
        {
            Log = LogManager.GetLogger(GetType());
        }
    }

    private static readonly Lazy<LogSingletonWrapper> _logger = new Lazy<LogSingletonWrapper>();

    public static void Info(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "") 
        => GetLogger(memberName, sourceFilePath).Info(message);
    
    public static void Error(string message,Exception ex, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "") 
        => GetLogger(memberName, sourceFilePath).Error(message, ex);

    private static ILog GetLogger(string memberName, string sourceFilePath)
    {
        var classname = sourceFilePath.Split('\\').Last().Split('.').First();
        log4net.ThreadContext.Properties["Source"] = $"{classname}.{memberName.Replace(".", "")}";
        return _logger.Value.Log;
    }
}

那么你可以在.config文件中使用以下日志转换模式:

最初的回答:

然后,您可以在.config文件中使用此日志转换模式:
<conversionPattern value="[%level][%date][Thd%thread: %property{Source}][Message: %message]%newline" />

这将会导致日志如下所示:

[信息][2019-07-03 16:42:00,936][Thd1: Application.Start][消息: 应用程序正在启动...]

[错误][2019-07-03 16:42:44,145][Thd6: DataProcessor.ProcessDataBatch][消息: 试图除以零。]

上面的示例中调用了以下方法:Application类的Start方法和DataProcessor类的ProcessDataBatch方法。请注意保留HTML标签。

4
这篇文章帮助我发现如何编写自己的包装器。为了回报,我想与您分享我的完整类来包装记录器。这个类似乎非常好用,而且实际上只需要使用ILog的一半时间!只需在配置文件中设置适当的xml即可开始日志记录。
[assembly: log4net.Config.XmlConfigurator(Watch = true)] 

在您的AssemblyInfo.cs中添加这段代码,它应该很容易地工作。
注意:我使用了非常简单的Log4NetDash设置,所以在错误字段中放置了一些信息(例如,在Domain字段中放置了堆栈跟踪),这对我仍然有效,因为我不关心信息显示在哪里,但如果您有空闲时间并且正在正确设置东西,您可能需要修复这个问题!
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using log4net;
using log4net.Core;

namespace Utility
{
    public class Logger
    {
        static Logger()
        {
            LogManager.GetLogger(typeof(Logger));
        }

        public static void Debug(string message, params object[] parameters)
        {
            Log(message, Level.Debug, null, parameters);
        }

        public static void Info(string message, params object[] parameters)
        {
            Log(message, Level.Info, null, parameters);
        }

        public static void Warn(string message, params object[] parameters)
        {
            Log(message, Level.Warn, null, parameters);
        }

        public static void Error(string message, params object[] parameters)
        {
            Error(message, null, parameters);
        }

        public static void Error(Exception exception)
        {
            if (exception==null)
                return;
            Error(exception.Message, exception);
        }

        public static void Error(string message, Exception exception, params object[] parameters)
        {
            string exceptionStack = "";

            if (exception != null)
            {
                exceptionStack = exception.GetType().Name + " : " + exception.Message + Environment.NewLine;
                Exception loopException = exception;
                while (loopException.InnerException != null)
                {
                    loopException = loopException.InnerException;
                    exceptionStack += loopException.GetType().Name + " : " + loopException.Message + Environment.NewLine;
                }
            }

            Log(message, Level.Error, exceptionStack, parameters);
        }



        private static void Log(string message, Level logLevel, string exceptionMessage, params object[] parameters)
        {
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += LogEvent;
            worker.RunWorkerAsync(new LogMessageSpec
                                      {
                                          ExceptionMessage = exceptionMessage,
                                          LogLevel = logLevel,
                                          Message = message,
                                          Parameters = parameters,
                                          Stack = new StackTrace(),
                                          LogTime = DateTime.Now
                                      });
        }

        private static void LogEvent(object sender, DoWorkEventArgs e)
        {
            try
            {
                LogMessageSpec messageSpec = (LogMessageSpec) e.Argument;

                StackFrame frame = messageSpec.Stack.GetFrame(2);
                MethodBase method = frame.GetMethod();
                Type reflectedType = method.ReflectedType;

                ILogger log = LoggerManager.GetLogger(reflectedType.Assembly, reflectedType);
                Level currenLoggingLevel = ((log4net.Repository.Hierarchy.Logger) log).Parent.Level;

                if (messageSpec.LogLevel<currenLoggingLevel)
                    return;

                messageSpec.Message = string.Format(messageSpec.Message, messageSpec.Parameters);
                string stackTrace = "";
                StackFrame[] frames = messageSpec.Stack.GetFrames();
                if (frames != null)
                {
                    foreach (StackFrame tempFrame in frames)
                    {

                        MethodBase tempMethod = tempFrame.GetMethod();
                        stackTrace += tempMethod.Name + Environment.NewLine;
                    }
                }
                string userName = Thread.CurrentPrincipal.Identity.Name;
                LoggingEventData evdat = new LoggingEventData
                                             {
                                                 Domain = stackTrace,
                                                 Identity = userName,
                                                 Level = messageSpec.LogLevel,
                                                 LocationInfo = new LocationInfo(reflectedType.FullName,
                                                                                 method.Name,
                                                                                 frame.GetFileName(),
                                                                                 frame.GetFileLineNumber().ToString()),
                                                 LoggerName = reflectedType.Name,
                                                 Message = messageSpec.Message,
                                                 TimeStamp = messageSpec.LogTime,
                                                 UserName = userName,
                                                 ExceptionString = messageSpec.ExceptionMessage
                                             };
                log.Log(new LoggingEvent(evdat));
            }
            catch (Exception)
            {}//don't throw exceptions on background thread especially about logging!
        }

        private class LogMessageSpec
        {
            public StackTrace Stack { get; set; }
            public string Message { get; set; }
            public Level LogLevel { get; set; }
            public string ExceptionMessage { get; set; }
            public object[] Parameters { get; set; }
            public DateTime LogTime { get; set; }
        }
    }
}

3
反射对于记录日志来说是相当昂贵的。你不会希望因为记录日志而导致代码变得缓慢。如果使用C# 5,我更倾向于使用CallerMemberName等类似属性。 - Junaid
非常正确。我倾向于在可能的情况下使用Log的线程版本,以便记录发生在关键路径之外。不过这并不总是可行的,我应该真正考虑更新我的日志记录,因为它已经超过五年了。谢啦。 - Stu


0

我将只是编写Claus正确答案的更多代码

在包装类中

public static class Logger
{
   private static readonly ILogger DefaultLogger;

   static Logger()
   {
      defaultLogger = LoggerManager.GetLogger(Assembly.GetCallingAssembly(), "MyDefaultLoggger"); // MyDefaultLoggger is the name of Logger
   }

  public static void LogError(object message)
  {
      Level errorLevel = Level.Error;
      if (DefaultLogger.IsEnabledFor(errorLevel))
      {
          DefaultLogger.Log(typeof(Logger), errorLevel, message, null);
      }
  }

  public static void LogError(object message, Exception exception)
  {
      Level errorLevel = Level.Error;
      if (DefaultLogger.IsEnabledFor(errorLevel))
      {
          DefaultLogger.Log(typeof(Logger), errorLevel, message, exception);
      }
  }

对于其余的方法,同样适用。

在 web.config 或 app.config 中,log4net.Layout.PatternLayout 可以使用一些转换模式,例如:

%location %method %line

<layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date{dd/MM/yyyy hh:mm:ss.fff tt} [%thread] %level %logger [%location %method %line] [%C %M] - %newline%message%newline%exception"/>
  </layout>

0

我能想到的唯一做法(因为我目前不使用log4net)是请求一个堆栈跟踪(new StackTrace),然后返回一帧以获取所需信息。但是,我不确定这样做对运行时性能的影响。


log4net已经在LocationInformation类中实现了此功能,但在包装Ilog时无法正常工作。 - Claus Thomsen
1
log4net在幕后执行相同的操作,当模式布局包含%m和%c变量时。由于明显的性能原因,他们警告不要使用它们。 - bryanbcook

0
在类似的情况下,我不想像log4net那样依赖于堆栈跟踪,而是希望从[CallerMemberName]属性中获取方法名。经过一番努力,我想出了以下的包装方法:
// Works with a pattern like: (%file.%method:%line) %message%newline
public void WriteLog(ILogger logger, Level level, object msg, Exception? ex = null, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNo = 0)
{
    if (!logger.IsEnabledFor(level)) { return; }

    RendererMap rendererMap = logger.Repository.RendererMap;
    string fileName = Path.GetFileNameWithoutExtension(filePath);
    LoggingEventData data = new LoggingEventData()
    {
        Level = level,
        Message = rendererMap.FindAndRender(msg),
        ExceptionString = ex is null ? "" : rendererMap.FindAndRender(ex),
        LoggerName = logger.Name,
        LocationInfo = new LocationInfo(fileName, memberName, fileName, lineNo.ToString(CultureInfo.InvariantCulture)),
        TimeStampUtc = DateTime.UtcNow,
    };

    const FixFlags flags = FixFlags.All & ~FixFlags.ThreadName;
    LoggingEvent e = new LoggingEvent(null, logger.Repository, data, flags);

    logger.Log(e);
}

这个示例覆盖了log4net的位置信息。你可能会发现@Quantum_Joe's answer更简单,它只是将位置信息分别放入ThreadContext.Properties中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接