Как сохранить информацию о вызывающем абоненте при обертке 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);
}
Ответы
Ответ 1
Проблема заключается в том, что ваша обертка не обертывается правильно. Вот пример того, как правильно обернуть 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);
}
}
}
Ключ передает тип вашего логгера для вызова в журнал. Когда NLog пытается найти callsite, он поднимается до стека до первого метода вызова, тип объявления которого НЕ является типом, переданным в вызов 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);
}
Ответ 2
Чтобы пропустить несколько кадров и погрузиться в контекст вызывающих операторов, настройте в App.config или в программе известный модификатор:
skipFrames = 1
Примеры:
См. эта страница для ${callsite:skipFrames=Integer}
и эта страница для ${callsite-linenumber:skipFrames=Integer}
Я рекомендую использовать этот формат в вашей обертке:
${callsite:fileName=true:includeSourcePath=false:skipFrames=1}
Выход из этой настройки будет следующим:
... {LicenseServer.LSCore.MainThreadFunction(LSCore.cs: 220)}...
Ответ 3
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
Я использовал вышеописанный код, чтобы просто извлечь имя вызывающего метода и передать его как часть параметра "сообщение" в макет. Это позволяет мне иметь исходное имя метода, в котором была вызвана оболочка журнала, в файл журнала (а не имя класса обертки журнала).
Ответ 4
Я сейчас боюсь этой проблемы.
На самом деле, неудобным было 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);
}
Но, к сожалению, Stacktrace с MEF очень длинный, и я не могу четко идентифицировать правильного вызывающего абонента для Requester из ILogger.
Итак, вместо того, чтобы вводить интерфейс ILogger через инжекцию конструктора, я создал интерфейс ILogFactory, который может быть введен с помощью инжекции конструктора и вызвать метод Create на Factory
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 Import Constructor:
[ImportingConstructor]
public MyConstructor(
ILogFactory logFactory)
{
_logger = logFactory.Create(GetType());
}
надеюсь, что это поможет
Ответ 5
В качестве альтернативы вы можете избежать встроенного решения из NLog setup и получить файл | метод | line в вашем коде оберток:
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() + "} ";
}
Затем вы просто вызываете свои статические методы и добавляете колл-сайт до сообщения:
LogManager.GetCurrentClassLogger().Trace(GetCallsite() + "My Trace Message.");
Ответ 6
Guys
После нескольких дней напряженной работы и поиска. Наконец, я просто использую один простой класс, созданный 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
}
Ответ 7
Есть простой способ добиться этого. Просто добавьте эти атрибуты в ваши сигнатуры метода оболочки журнала:
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 attibutes в .NET.
Ответ 8
В настоящее время более простой подход к исправлению места вызова заключается в использовании LogManager.AddHiddenAssembly(Assembly)
например
LogManager.AddHiddenAssembly(yourAssembly);
Это исправит место вызова и не требует ручного обхода стека и т.д.