[英]How to use serilog in a referenced class library and use with CallerMember, CallerFilePath (not asp.net core)
我正在使用WPF应用程序,可以通过将它放置在App.xaml.cs中来成功设置serilog并访问其静态方法,
但是,我也想在项目中登录各种引用的类库。
由于循环依赖,由于无法从类库本身引用主应用程序,因此在这些类库中执行日志记录的最佳方法是什么?
public class LogManager : IMLogManager
{
#region Constructor
public LogManager()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.Enrich.FromLogContext()
.WriteTo.Console(LogEventLevel.Debug, OutputTemplate, theme: AnsiConsoleTheme.Code)
.WriteTo.Async(a => a.File("./log/log.log",
LogEventLevel.Debug,
OutputTemplate,
rollingInterval: RollingInterval.Day,
encoding: Encoding.UTF8,
buffered: true,
rollOnFileSizeLimit: true))
.CreateLogger();
}
#endregion
#region Private properties
/// <summary>
/// Template ghi log.
/// </summary>
public static string OutputTemplate =>
"[{Timestamp:dd-MM-yyyy HH:mm:ss.fff} {Level:u3}] source {SourceContext} ::: {Message:lj}{NewLine}{Exception}{NewLine}";
#endregion
#region Private methods
private static string FormatMessage(string message,
string memberName = "",
string filePath = "",
int lineNumber = 0)
=> $"in method [{Path.GetFileNameWithoutExtension(filePath)} > {memberName}]\r\n\tat {filePath}:{lineNumber}\r\n{message}";
#endregion
#region Private fields
#endregion
#region Implementation of IMLogManager
/// <summary>
/// Write app log.
/// </summary>
/// <param name="level"></param>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="memberName"></param>
/// <param name="filePath"></param>
/// <param name="lineNumber"></param>
public void WriteLog(LogEventLevel level,
string message,
Exception exception = null,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
switch (level)
{
case LogEventLevel.Verbose:
Log.Verbose(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Debug:
Log.Debug(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Information:
Log.Information(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Warning:
Log.Warning(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Error:
Log.Error(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Fatal:
Log.Fatal(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
default:
throw new ArgumentOutOfRangeException(nameof(level), level, null);
}
}
public void WriteLog<T>(LogEventLevel level,
string message,
Exception exception = null,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0) where T : class
{
var log = Log.ForContext<T>()
.ForContext("MemberName", memberName)
.ForContext("FilePath", filePath)
.ForContext("FileName", Path.GetFileNameWithoutExtension(filePath))
.ForContext("LineNumber", lineNumber);
switch (level)
{
case LogEventLevel.Verbose:
log.Verbose(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Debug:
log.Debug(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Information:
log.Information(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Warning:
log.Warning(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Error:
log.Error(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
case LogEventLevel.Fatal:
log.Fatal(exception, FormatMessage(message, memberName, filePath, lineNumber));
break;
default:
throw new ArgumentOutOfRangeException(nameof(level), level, null);
}
}
public void CloseAndFlush()
{
Log.CloseAndFlush();
}
#endregion
}
通常,需要通过Serilog公开日志的库仅依赖于Serilog
库,任何希望执行日志记录的类要么通过构造函数参数依赖ILogger
,要么使用静态Log
,例如
// Constructor
public MyClass(ILogger logger)
{
_logger = logger.ForContext(GetType());
}
// Constructor
public MyClass()
{
_logger = Log.ForContext(GetType());
}
由于您似乎希望将调用者信息添加到每条日志消息中,因此建议您将此功能提取到所有“参考类库”都可以使用的共享库中,例如,作为类似于以下内容的扩展方法:
public static ILogger WithCallerContext(
this ILogger logger,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
return logger.ForContext("MemberName", memberName)
.ForContext("FilePath", filePath)
.ForContext("FileName", Path.GetFileNameWithoutExtension(filePath))
.ForContext("LineNumber", lineNumber);
}
然后,您可以在库中记录如下:
_logger.WithCallerContext().Information("Hello, world!");
这种方法的另一个优点是,它将添加调用方上下文和记录消息的操作分开,因此可以使用ILogger.Information
等方法及其完整的消息模板支持,而不必始终通过自定义WriteLog
方法。 然后,您可以执行以下操作:
_logger.WithCallerContext().Information("Hello from {Name}!", name);
请注意,即使使用此方法不会将调用者上下文包含在Message
属性中,您仍然可以通过直接在输出模板中引用属性名称来将其包含在输出中,例如{FilePath}
, {LineNumber}
。
最后的提示是,在您的WriteLog
方法中(如果最终使用它们),而不是在各种日志事件级别之间切换并调用适当的Log.<Level>
方法,您可以简单地使用以下Log.Write
重载:
Log.Write(level, exception, FormatMessage(message, memberName, filePath, lineNumber));
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.