如何在引用的类库中使用 serilog 并与 CallerMember、CallerFilePath(不是 asp.net 核心)一起使用

min*_*oft 2 .net c# serilog

我正在开发一个 WPF 应用程序,可以成功地设置 serilog 并通过将它放在 App.xaml.cs 中来访问它的静态方法,

但是,我还想登录项目中各种引用的类库。

由于循环依赖,我无法从类库本身引用主应用程序,因此在这些类库中执行日志记录的最佳方法是什么?

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
    }
Run Code Online (Sandbox Code Playgroud)

rob*_*ker 5

通常,需要通过 Serilog 公开日志记录的Serilog库将仅依赖于该库,并且任何希望执行日志记录的类要么ILogger通过构造函数参数依赖于 a要么将使用 static Log,例如

// Constructor
public MyClass(ILogger logger)
{
    _logger = logger.ForContext(GetType());
}
Run Code Online (Sandbox Code Playgroud)
// Constructor
public MyClass()
{
    _logger = Log.ForContext(GetType());
}
Run Code Online (Sandbox Code Playgroud)

由于您似乎想将调用者信息添加到每条日志消息中,我建议将此功能提取到所有“引用类库”都可以使用的共享库中,例如作为扩展方法,如下所示:

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);
}
Run Code Online (Sandbox Code Playgroud)

然后在您的库中,您可以按如下方式登录:

_logger.WithCallerContext().Information("Hello, world!");
Run Code Online (Sandbox Code Playgroud)

这种方法的另一个巨大优势是它将添加调用者上下文和记录消息的操作分开,因此您可以使用ILogger.Information具有完整消息模板支持的etc. 方法,而不必总是通过自定义WriteLog方法。然后你可以做这样的事情:

_logger.WithCallerContext().Information("Hello from {Name}!", name);
Run Code Online (Sandbox Code Playgroud)

请注意,即使Message使用这种方法调用者上下文不会包含在属性中,您仍然可以通过直接在输出模板中引用属性名称将其包含在输出中,例如{FilePath}, {LineNumber}

最后的提示是,在您的WriteLog方法中(如果您最终使用它们),而不是在各种日志事件级别之间切换并调用适当的Log.<Level>方法,您可以简单地使用以下Log.Write重载:

Log.Write(level, exception, FormatMessage(message, memberName, filePath, lineNumber));
Run Code Online (Sandbox Code Playgroud)