Как сохранить информацию о звонках при обертывании NLog

У меня есть класс, который обертывает NLog (называемый NLogger). Мои журналы сохраняются в моей базе данных. У меня проблема с тем, как показать, где произошло ведение журнала. у меня есть это

<parameter name="@Logger" layout="${callsite}"/>  

но это просто показывает Core.Logging.Loggers.NLogLogger.Log, который является моим NlogWrapper, а не классом, который вызывает мою оболочку.

Это мой метод оболочки

        public void Log(LogType messageType, Type context, string message, Exception exception)
        {
            NLog.Logger logger = NLog.LogManager.GetLogger(context.Name);
            LogLevel logLevel = LogLevel.Info; // Default level to info

            switch (messageType)
            {
                case LogType.Debug:
                    logLevel = LogLevel.Debug;
                    break;
                case LogType.Info:
                    logLevel = LogLevel.Info;
                    break;
                case LogType.Warning:
                    logLevel = LogLevel.Warn;
                    break;
                case LogType.Error:
                    logLevel = LogLevel.Error;
                    break;
                case LogType.Fatal:
                    logLevel = LogLevel.Fatal;
                    break;
                default:
                    throw new ArgumentException("Log message type is not supported");                    
            }

            logger.Log(logLevel, message, exception);
        }

person David    schedule 14.09.2011    source источник


Ответы (8)


Проблема в том, что ваша оболочка работает неправильно. Вот пример того, как правильно обернуть NLog, взятый непосредственно из исходного дерева NLog :

using System;
using System.Text;
using NLog;

namespace LoggerWrapper
{    
  /// <summary>    
  /// Provides methods to write messages with event IDs - useful for the Event Log target.    
  /// Wraps a Logger instance.    
  /// </summary>    
  class MyLogger    
  {        
    private Logger _logger;        

    public MyLogger(string name)        
    {            
      _logger = LogManager.GetLogger(name);        
    }        

    public void WriteMessage(string eventID, string message)           
    {            
      ///            
      /// create log event from the passed message            
      ///             
      LogEventInfo logEvent = new LogEventInfo(LogLevel.Info, _logger.Name, message);


      //
      // set event-specific context parameter            
      // this context parameter can be retrieved using ${event-context:EventID}            
      //            
      logEvent.Context["EventID"] = eventID;            
      //             
      // Call the Log() method. It is important to pass typeof(MyLogger) as the            
      // first parameter. If you don't, ${callsite} and other callstack-related             
      // layout renderers will not work properly.            
      //            
      _logger.Log(typeof(MyLogger), logEvent);        
    }    
  }
}

Ключ передает тип вашей оболочки регистратора вызову Log. Когда NLog пытается найти сайт вызова, он поднимается по стеку до первого вызывающего метода, тип объявления которого НЕ является типом, переданным вызову Log. Это будет код, который на самом деле вызывает вашу оболочку.

В вашем случае ваш регистратор будет выглядеть примерно так:

    public void Log(LogType messageType, Type context, string message, Exception exception)
    {
        NLog.Logger logger = NLog.LogManager.GetLogger(context.Name);
        LogLevel logLevel = LogLevel.Info; // Default level to info

        switch (messageType)
        {
            case LogType.Debug:
                logLevel = LogLevel.Debug;
                break;
            case LogType.Info:
                logLevel = LogLevel.Info;
                break;
            case LogType.Warning:
                logLevel = LogLevel.Warn;
                break;
            case LogType.Error:
                logLevel = LogLevel.Error;
                break;
            case LogType.Fatal:
                logLevel = LogLevel.Fatal;
                break;
            default:
                throw new ArgumentException("Log message type is not supported");                    
        }

        //
        // Build LogEvent here...
        //
        LogEventInfo logEvent = new LogEventInfo(logLevel, context.Name, message);
        logEvent.Exception = exception;

        //
        // Pass the type of your wrapper class here...
        //
        logger.Log(typeof(YourWrapperClass), logEvent);
    }
person wageoghe    schedule 20.09.2011
comment
Я расширил NLog в соответствии с примером LoggerWrapper на github.com/NLog/NLog/ tree/master/examples/ExtendingLoggers, чтобы я мог заполнить пользовательские свойства событий, такие как logEvent.Properties["EventID"] = eventID;, как показано в примере. Я хочу добавить callsite в качестве одного из свойств, то есть logEvent.Properties["callsite"] = *caller_method_name*. Как программно получить информацию о телефонном сайте? - person Howiecamp; 13.08.2018

Чтобы пропустить несколько кадров и погрузиться в контекст вызывающих оберток, настройте в App.config или в программе известный модификатор:

скипфреймов=1

Примеры: см. эту страницу для ${callsite:skipFrames=Integer} и эта страница для ${callsite-linenumber:skipFrames=Integer}

Я рекомендую вам использовать этот формат в вашей обертке:

${callsite:fileName=true:includeSourcePath=false:skipFrames=1}

Результат этой настройки будет следующим:

... {LicenseServer.LSCore.MainThreadFunction(LSCore.cs:220)} ...

person Sold Out    schedule 27.04.2016
comment
Не уверен, какая версия NLog является наименьшей, которая поддерживает функцию skipFrames=Integer. Нужно проверить их документацию. - person Sold Out; 27.04.2016
comment
См. эту ссылку для списка визуализаторов макета: github.com/nlog/nlog/wiki/Layout -Визуализаторы - person Sold Out; 28.04.2016
comment
Победитель-победитель куриного ужина, это отлично сработало для меня. - person kevinc; 09.05.2017
comment
На сегодняшний день это единственное жизнеспособное решение для универсального класса. Я пробовал принятый ответ безрезультатно, но это работает как шарм. - person Bozhidar Stoyneff; 14.04.2021

internal string GetCallingMethodName()
{
  string result = "unknown";
  StackTrace trace = new StackTrace(false);
  for (int i = 0; i < trace.FrameCount; i++)
  {
    StackFrame frame = trace.GetFrame(i);
    MethodBase method = frame.GetMethod();
    Type dt = method.DeclaringType;
    if (!typeof(ILogger).IsAssignableFrom(dt) && method.DeclaringType.Namespace != "DiagnosticsLibrary")
    {
      result = string.Concat(method.DeclaringType.FullName, ".", method.Name);
      break;
    }
  }
  return result;
}

Источник : http://slf.codeplex.com/discussions/210075

Я использовал размещенный выше код, чтобы просто извлечь имя вызывающего метода и передать его как часть параметра «сообщение» в макет. Это позволяет мне записать исходное имя метода, в котором была вызвана оболочка журнала, в файл журнала (а не имя класса оболочки журнала).

person Peter Bernier    schedule 21.11.2011
comment
Этот код не будет хорошо масштабироваться. Работа со StackFrame требует больших затрат с точки зрения производительности. - person Dave Black; 11.08.2017

Я уже некоторое время борюсь с этой проблемой.

Действительно важным было Callsite (FullyQualified Namespace) в лог-файлах.

Во-первых, я попытался получить нужный регистратор из Stacktrace:

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static NLog.Logger GetLogger()
    {
        var stackTrace = new StackTrace(false);
        StackFrame[] frames = stackTrace.GetFrames();
        if (null == frames) throw new ArgumentException("Stack frame array is null.");
        StackFrame stackFrame;
        switch (frames.Length)
        {
            case 0:
                throw new ArgumentException("Length of stack frames is 0.");
            case 1:
            case 2:
                stackFrame = frames[frames.Length - 1];
                break;
            default:
                stackFrame = stackTrace.GetFrame(2);
                break;
        }

        Type declaringType = stackFrame.GetMethod()
                                       .DeclaringType;

        return declaringType == null ? LogManager.GetCurrentClassLogger() :                 LogManager.GetLogger(declaringType.FullName);
    }

Но, к сожалению, трассировка стека с MEF очень длинная, и я не могу четко определить правильный вызывающий объект для запрашивающего ILogger.

Итак, вместо внедрения интерфейса ILogger с помощью внедрения конструктора я создал интерфейс ILogFactory, который можно внедрить с помощью внедрения конструктора, а затем вызвать метод Create на фабрике.

    public interface ILogFactory
    {
        #region Public Methods and Operators

        /// <summary>
        ///     Creates a logger with the Callsite of the given Type
        /// </summary>
        /// <example>
        ///     factory.Create(GetType());
        /// </example>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        ILogger Create(Type type);

        #endregion
    }

И реализовал это:

    using System;
    using System.ComponentModel.Composition;

    [Export(typeof(ILogFactory))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class LogFactory : ILogFactory
    {
        #region Public Methods and Operators

        public ILogger Create(Type type)
        {
            var logger = new Logger().CreateLogger(type);
            return logger;
        }

        #endregion
    }

С ILogger:

    public interface ILogger
    {
        #region Public Properties

        bool IsDebugEnabled { get; }

        bool IsErrorEnabled { get; }

        bool IsFatalEnabled { get; }

        bool IsInfoEnabled { get; }

        bool IsTraceEnabled { get; }

        bool IsWarnEnabled { get; }

        #endregion

        #region Public Methods and Operators

        void Debug(Exception exception);
        void Debug(string format, params object[] args);
        void Debug(Exception exception, string format, params object[] args);
        void Error(Exception exception);
        void Error(string format, params object[] args);
        void Error(Exception exception, string format, params object[] args);
        void Fatal(Exception exception);
        void Fatal(string format, params object[] args);
        void Fatal(Exception exception, string format, params object[] args);
        void Info(Exception exception);
        void Info(string format, params object[] args);
        void Info(Exception exception, string format, params object[] args);
        void Trace(Exception exception);
        void Trace(string format, params object[] args);
        void Trace(Exception exception, string format, params object[] args);
        void Warn(Exception exception);
        void Warn(string format, params object[] args);
        void Warn(Exception exception, string format, params object[] args);

        #endregion
    }

и реализация:

    using System;

      using NLog;
      using NLog.Config;

      /// <summary>
      ///     The logging service.
      /// </summary>
      public class Logger : NLog.Logger, ILogger
      {
          #region Fields

          private string _loggerName;

          #endregion

          #region Public Methods and Operators

          /// <summary>
          ///     The get logging service.
          /// </summary>
          /// <returns>
          ///     The <see cref="ILogger" />.
          /// </returns>
          public ILogger CreateLogger(Type type)
          {
              if (type == null) throw new ArgumentNullException("type");               

              _loggerName = type.FullName;

              var logger = (ILogger)LogManager.GetLogger(_loggerName, typeof(Logger));

              return logger;
          }

Чтобы использовать его... просто введите ILogFactory и вызовите метод Create в конструкторе импорта Mefed:

      [ImportingConstructor]
      public MyConstructor(          
        ILogFactory logFactory)
       {
        _logger = logFactory.Create(GetType());
        }

надеюсь это поможет

person Pascalsz    schedule 22.07.2013

В настоящее время более простой способ исправить сайт вызова — использовать LogManager.AddHiddenAssembly(Assembly)

e.g.

LogManager.AddHiddenAssembly(yourAssembly);

Это исправит сайт вызова, и не нужно будет выполнять обход стека вручную и т. д.

person Julian    schedule 23.06.2019
comment
Охватывает ли это всю сборку или также будет полезно, если Trace() вызывается из Func‹T›/делегата? - person Dan Chase; 15.07.2019
comment
Он фильтрует кадры стека из этой сборки. - person Julian; 15.07.2019

В качестве альтернативы вы можете отказаться от собственного решения из настройки NLog и получить файл | метод | информация о строке в вашем коде обертки:

using System.Diagnostics;
...
static private string GetCallsite()
{
  StackFrame sf = new StackTrace(2/*Skip two frames - dive to the callers context*/, true/*Yes I want the file info !*/).GetFrame(0);
  return "{" + sf.GetFileName() + " | " + sf.GetMethod().Name + "-" + sf.GetFileLineNumber() + "} ";
}

Затем вы просто вызываете свои статические методы и добавляете callsite перед сообщением:

LogManager.GetCurrentClassLogger().Trace(GetCallsite() + "My Trace Message.");
person Sold Out    schedule 27.04.2016
comment
ПРИМЕЧАНИЕ. Вам нужно пропустить 2 кадра, если вы выберете метод GetCallsite(), так как это будет еще один уровень стека выше вашего вызывающего метода в самой оболочке. Затем будьте осторожны, чтобы НЕ вызывать его с более низкого уровня. - person Sold Out; 27.04.2016
comment
Этот код не будет хорошо масштабироваться. Работа со StackFrame обходится дорого с точки зрения производительности. - person Dave Black; 11.08.2017

Ребята, после нескольких дней напряженной работы и поиска. Наконец, я просто использую один простой класс, созданный Nlog Wrapper, который может сохранить $ {callsite} и получить правильное имя регистратора при создании экземпляра Nlog Wrapper. Я поставлю код следующим образом с простым комментарием. Как видите, я использую Stacktrace, чтобы получить правильное имя регистратора. Используйте write и writewithex, чтобы зарегистрировать logevnet, чтобы сохранить callsite.

  public  class NlogWrapper 
    {  
        private  readonly NLog.Logger _logger; //NLog logger

    /// <summary>
    /// This is the construtor, which get the correct logger name when instance created  
    /// </summary>

    public NlogWrapper()
        {
        StackTrace trace = new StackTrace();

        if (trace.FrameCount > 1)
        {
            _logger = LogManager.GetLogger(trace.GetFrame(1).GetMethod().ReflectedType.FullName);
        }
        else //This would go back to the stated problem
        {
            _logger = LogManager.GetCurrentClassLogger();
        }
    }
    /// <summary>
    /// These two method are used to retain the ${callsite} for all the Nlog method  
    /// </summary>
    /// <param name="level">LogLevel.</param>
    ///  <param name="format">Passed message.</param>
    ///  <param name="ex">Exception.</param>
    private void Write(LogLevel level, string format, params object[] args)
    {
        LogEventInfo le = new LogEventInfo(level, _logger.Name, null, format, args);
        _logger.Log(typeof(NlogWrapper), le);
    }
    private void WriteWithEx(LogLevel level, string format,Exception ex, params object[] args)
    {
        LogEventInfo le = new LogEventInfo(level, _logger.Name, null, format, args);
        le.Exception = ex;
        _logger.Log(typeof(NlogWrapper), le);
    }


    #region  Methods
    /// <summary>
    /// This method writes the Debug information to trace file
    /// </summary>
    /// <param name="message">The message.</param>
    public  void Debug(String message)
        {
            if (!_logger.IsDebugEnabled) return;

        Write(LogLevel.Debug, message);
    }  

    public  void Debug(string message, Exception exception, params object[] args)
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Debug, message, exception);
    }

    /// <summary>
    /// This method writes the Information to trace file
    /// </summary>
    /// <param name="message">The message.</param>
    public  void Info(String message)
        {
            if (!_logger.IsInfoEnabled) return;
        Write(LogLevel.Info, message);
    }

    public  void Info(string message, Exception exception, params object[] args) 
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Info, message, exception);
    }
    /// <summary>
    /// This method writes the Warning information to trace file
    /// </summary>
    /// <param name="message">The message.</param>
    public  void Warn(String message)
        {
            if (!_logger.IsWarnEnabled) return;
          Write(LogLevel.Warn, message); 
        }

    public  void Warn(string message, Exception exception, params object[] args)
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Warn, message, exception);
    }

    /// <summary>
    /// This method writes the Error Information to trace file
    /// </summary>
    /// <param name="error">The error.</param>
    /// <param name="exception">The exception.</param>
    //   public static void Error( string message)
    //  {
    //    if (!_logger.IsErrorEnabled) return;
    //  _logger.Error(message);
    //}

    public  void Error(String message) 
    {
        if (!_logger.IsWarnEnabled) return;
        //_logger.Warn(message);
        Write(LogLevel.Error, message);
    }
    public void Error(string message, Exception exception, params object[] args)
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Error, message, exception);
    }  


    /// <summary>
    /// This method writes the Fatal exception information to trace target
    /// </summary>
    /// <param name="message">The message.</param>
    public void Fatal(String message)
        {
            if (!_logger.IsFatalEnabled) return;
         Write(LogLevel.Fatal, message);
    }

    public void Fatal(string message, Exception exception, params object[] args)
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Fatal, message, exception);
    }

    /// <summary>
    /// This method writes the trace information to trace target
    /// </summary>
    /// <param name="message">The message.</param>
    /// 
    public  void Trace(string message, Exception exception, params object[] args)  
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Trace, message, exception);
    }
    public  void Trace(String message)
        {
            if (!_logger.IsTraceEnabled) return;
            Write(LogLevel.Trace, message);
    }

        #endregion

    }
person Steve Tianqin Guo    schedule 21.09.2016
comment
Когда вы используете его в своем проекте. Просто выполните: NlogWrapper mylog= new NlogWrapper(); mylog.Error (это ошибка относительно:, например). Затем класс-оболочка позаботится об имени регистратора и информации о месте вызова. Когда вы используете ${logger} ${callsite}. Вы получите правильную информацию вместо информации о вызове класса logwrapper. - person Steve Tianqin Guo; 21.09.2016

Есть простой способ добиться этого. Просто добавьте эти атрибуты в сигнатуры методов оболочки журнала:

void Log(LogSeverity severity, string message, [CallerFilePath] string fileName = null, [CallerMemberName] string member = null, [CallerLineNumber] int? lineNumber = null);

и передать их в обернутые методы NLog.

См. https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callerfilepathattribute?view=netframework-4.7.2 для получения дополнительной информации об атрибутах System.Runtime.CompilerServices в .NET.

person Bo Christian Skjøtt    schedule 24.01.2019