如何按发生日志记录的类将 Serilog Logging 输出分开?

How do I separate Serilog Logging output by the class where logging happens?

提问人:NinjaPedroX 提问时间:11/14/2023 最后编辑:NinjaPedroX 更新时间:11/17/2023 访问量:59

问:

我是 C# 的业余爱好者,正在开发一个 .NET Framwork 4.7.2 WPF 应用程序,其主要目的是允许用户从类(所有这些代码都保存在程序中的“脚本”文件夹中)运行代码,该类将在嵌入式浏览器中执行一组特定任务。我正在使用 Serilog 进行日志记录,现在,我的程序在我的文件中设置,以将所有日志记录输出到“Logs”文件夹,如下所示:App.xaml.cs

Log.Logger = new LoggerConfiguration()
    .WriteTo.File("./Logs/Log - .log", rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:HH:mm:ss.fff} [{Level:u3}] {Message}{NewLine}{Exception}")
    .WriteTo.Debug()
    .WriteTo.Console()
    .MinimumLevel.Debug()
    .CreateLogger();

该程序为保存在“脚本”文件夹中的每个类生成一个数据文件夹,以存储输入/输出数据。这些类中的每一个也使用 Serilog,以便在脚本运行时记录各种内容。有没有办法让 Serilog 将这些脚本类中完成的任何日志记录保存到一个单独的日志文件中,然后该文件将位于每个相应类的数据文件夹中?这样做的目的是将脚本中代码的任何类似内容和任何错误保留在其自己的文件中,以便用户以后可以在程序中访问和研究它。Log.Information("Finished task #1...");.log

我已经研究了这个问题,以寻找一种基于类过滤日志记录的方法,但是,看起来我必须为我拥有的每个脚本手动输入此代码块:

.WriteTo.Logger(lc => lc
                .Filter.ByIncludingOnly(Matching.FromSource<MyClass>())
                .WriteTo.RollingFile("logs/DBStats-{Date}.log", outputTemplate: "{Message}{NewLine}"))

这些脚本类将根据用户的意愿不断添加和删除到程序中,所以我认为这行不通。我希望在任何脚本类中完成的任何日志记录都能自动写入该脚本的相应数据文件夹中。

我也研究过 Serilog 子记录器,但还没有找到使用它来解决这个问题的方法。

C# WPF 日志记录 Serilog

评论

0赞 NPras 11/15/2023
有没有办法识别所有的脚本类?(例如,它们是否都在同一个命名空间下?它们是否都继承自某个接口?如果是这样,您可能可以扫描它们并自动执行配置。Filter
0赞 NPras 11/15/2023
当然,请尝试 ContextRollingFile 接收器
0赞 NinjaPedroX 11/16/2023
@NPras 所有脚本都继承自“ScriptBase”类。您能为我指出如何实现此过滤器自动化的正确方向吗?关于,我的第一个代码块中的行不正是这样做的吗?ContextRollingFile.WriteTo.File("./Logs/Log - .log", rollingInterval: RollingInterval.Day,...
0赞 NPras 11/17/2023
ContextRollingFile可能更容易。它与您正在使用的水槽不同。它允许您在文件名/位置中使用日志(通常是您的类)。您如何在脚本类中使用记录器?如果使用依赖关系注入,则每个脚本记录器都将已使用上下文进行扩充。如果您使用的是静态记录器,则每个脚本都需要初始化自己的 ContextFileContextILogger logger = Lor.ForContext(this.GetType);
0赞 NPras 11/17/2023
PS.很方便,但也有一个非常胖的依赖树,可能会带来一大堆你不需要的库。我会给你举个例子,在没有它的情况下构建你自己的。ContextRollingFileFilter

答:

0赞 NPras 11/17/2023 #1

在配置记录器时,您可以做的是扫描所有脚本类,并为它们设置过滤器。

根据您的评论,我们假设您的课程如下所示:

public abstract class ScriptBase {
  protected ILogger logger;

  // Enrich the logger with SourceContext, in this case, the class type.
  // Child classes will be enriched with their own class names.
  public ScriptBase() { logger = Log.ForContext(this.GetType()); }
  
  public void Test(string s) {
    logger.Debug($"{s} debug");
    logger.Information($"{s} info");
    logger.Error($"{s} error");
  }
}
public class ScriptOne : ScriptBase { }
public class ScriptTwo : ScriptBase { }

然后,我们可以使用 SourceContext 来设置过滤器。
这将假定脚本类在启动时加载,并且未在运行时执行动态 DLL 加载。
Matching.FromSource()

using System;
using System.Linq;
using Serilog;
using Serilog.Events;
using Serilog.Filters;

class Program {
  // Include Context in the template, to make the `Everything` log easier to read.
  const string template = "{Timestamp:HH:mm:ss.fff} [{Level:u3}] {SourceContext}: {Message}{NewLine}{Exception}";
  const RollingInterval interval = RollingInterval.Day;
  const string logDir = "c:/temp/logs"; // or wherever

  static void Main() {
    // We'll build up the Logger Config in a few steps.
    var logConfig = new LoggerConfiguration()
      // Allow Debug and above globally. We can override this per sink later.
      .MinimumLevel.Debug()
      ;

    // Step 1. Find ALL classes inheriting from ScriptBase.
    var scriptTypes = AppDomain.CurrentDomain.GetAssemblies()
      .SelectMany(assembly => assembly.GetTypes())
      .Where(typ => typeof(ScriptBase).IsAssignableFrom(typ))
      ;

    // Step 2. For each of the classes found above, 
    // configure the directory using filters.
    foreach (var type in scriptTypes) {
      logConfig = logConfig
        .WriteTo.Logger(lc => lc
          // Filter by the class name
          .Filter.ByIncludingOnly(Matching.FromSource(type.FullName))
          // And use it as subdirectory
          .WriteTo.File($"{logDir}/{type.Name}/Log-.log",
            rollingInterval: interval, outputTemplate: template
          )
        );
    }

    // Step 3. The rest of the config.
    logConfig = logConfig
      // Global file sink for everything
      .WriteTo.File($"{logDir}/EverythingLog-.log",
        rollingInterval: interval, outputTemplate: template,
        // Let's only log Info for the global file
        restrictedToMinimumLevel: LogEventLevel.Information
      )
      // And maybe restrict the Console sink to Warnings/Errors
      .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Warning)
      ;

    // Use the config above to initialise the logger.
    Log.Logger = logConfig.CreateLogger();

    // Now let's test the different scripts.
    new ScriptOne().Test("Hello");
    new ScriptTwo().Test("World");
  }
}

注意:上面的代码是独立的。在项目中实现它们之前,请随时在新的控制台项目中使用它。