在 Serilog 中动态更改日志文件路径?

Dar*_*han 6 serilog

我具有动态更改日志文件路径的功能。但是,当我更改 Consul 中可配置的路径时,它会在两个位置(即旧路径和新路径)写入部分日志。更改日志文件路径应该可以在不重新启动任何服务的情况下进行。我们如何存档?

我们在日志文件中写入如下:

.WriteTo.File(logFolderFullPath + "\\" + applicationName + "_.txt",
                         LogEventLevel.Error, shared: true,
                         fileSizeLimitBytes: fileSizeLimitBytes, rollOnFileSizeLimit: true, rollingInterval: RollingInterval.Day,
                          outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] [{MachineName}] [{SourceContext}] {RequestId} {CorrelationId} {Message}{NewLine}{Exception}{properties}")
Run Code Online (Sandbox Code Playgroud)

logFolderFullPath是可配置的路径appsetting.json。当我们更改路径时,它会在新路径中创建日志文件,但同时也会继续写入旧路径文件。

所以我们希望它应该停止写入旧路径。

Mat*_*STP 7

Serilog FileSink 一旦设置路径就不允许修改。我仍然更喜欢使用 appsettings.json 来存储 serilog 配置,但我在使用它之前修改了配置。

我的 appsettings.json 如下所示:

...
        "WriteTo": [
            {
                "Name": "File",
                "Args": {
                    "path": "../logs/log-.txt",
                    "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact",
                    "rollingInterval": "Day",
                    "buffered": true
                }
            }
        ]
...
Run Code Online (Sandbox Code Playgroud)

在配置 Serilog 之前,我创建了一个扩展方法来覆盖 appsettings.json 中的配置。

public static class IHostBuilderExtensions
    {

        public static IHostBuilder ConfigureSerilog(this IHostBuilder hostBuilder, string appName)
        {
            return hostBuilder.ConfigureAppConfiguration((hostCtx, configBuilder) =>
             {
                 var config = configBuilder.Build();

                 var pid = Process.GetCurrentProcess().Id;
                 var logFilePath = $@"{MyLogFolder}\\{appName}_pid_{pid}_.txt";
                 var logFileNameWithPidPattern = $"{appName}_pid_{pid}_.txt";
                 const string serilogWriteConfigPattern = "Serilog:WriteTo:";
                 const string serilogFilePathConfigPattern = ":Args:path";

                 var serilogFoundKvpFilePathFromConfig = config
                     .AsEnumerable()
                     .FirstOrDefault(kvp =>
                         kvp.Key.Contains(serilogWriteConfigPattern, StringComparison.InvariantCultureIgnoreCase)
                         && kvp.Key.Contains(serilogFilePathConfigPattern, StringComparison.InvariantCultureIgnoreCase))
                     ;
                 var keyToReplace = serilogFoundKvpFilePathFromConfig.Key;
                 var overridenValue = serilogFoundKvpFilePathFromConfig.Value
                     .Replace("log-.txt", logFileNameWithPidPattern);

                 var serilogWriteToFilePathOverride = KeyValuePair.Create(keyToReplace, overridenValue);
                 configBuilder.AddInMemoryCollection(new[] { serilogWriteToFilePathOverride });
             })
            .UseSerilog((ctx, lc) =>
            {
                lc
                    // At this point, the config has been updated
                    // and the file name contains the Process Id:
                    // eg.: MyName_pid_15940_20220826.txt
                    .ReadFrom.Configuration(ctx.Configuration)
                    .WriteTo
                    .Console();
            });
        }
}
Run Code Online (Sandbox Code Playgroud)

我在 Program.cs 中使用它,如下所示:

...
 hostBuilder
  .ConfigureAppConfiguration((hostCtx, configBuilder) => { /* other config */ })
  .ConfigureSerilog(appName)
...
Run Code Online (Sandbox Code Playgroud)


Cai*_*ete 6

您可以尝试使用Serilog.Settings.Reloader,它可以在配置更改时在运行时交换记录器实例。


在运行时更改记录器属性的另一种常见方法是使用Serilog.Sinks.Map,这是一个根据日志事件的属性调度事件的接收器。

下面的示例使用调用的日志事件属性FileName来决定它将写入的日志文件的名称,因此每当此属性更改时,日志文件都会相应更改:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Map("FileName", "IDontKnow", (fileName, wt) => wt.File($"{fileName}.txt"))
    .CreateLogger();

Log.ForContext("FileName", "Alice").Information("Hey!"); // writes to Alice.txt
Log.ForContext("FileName", "Bob").Information("Hello!"); // writes to Bob.txt
Log.Information("Hi Again!"); // writes to IDontKnow.txt (default if property is missing)

Log.CloseAndFlush();
Run Code Online (Sandbox Code Playgroud)

在您的情况下,您希望根据配置的更改动态更改此属性名称。一种简单的方法是创建一个自定义丰富器,它可以根据您的配置设置更改上述属性的值。

您的自定义丰富器看起来像这样:

internal class LogFilePathEnricher : ILogEventEnricher
{
    private string _cachedLogFilePath;
    private LogEventProperty _cachedLogFilePathProperty;

    public const string LogFilePathPropertyName = "LogFilePath";

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var logFilePath = // Read path from your appsettings.json
        // Check for null, etc...

        LogEventProperty logFilePathProperty;

        if (logFilePath.Equals(_cachedLogFilePath))
        {
            // Path hasn't changed, so let's use the cached property
            logFilePathProperty = _cachedLogFilePathProperty;
        }
        else
        {
            // We've got a new path for the log. Let's create a new property
            // and cache it for future log events to use
            _cachedLogFilePath = logFilePath;

            _cachedLogFilePathProperty = logFilePathProperty =
                propertyFactory.CreateProperty(LogFilePathPropertyName, logFilePath);
        }

        logEvent.AddPropertyIfAbsent(logFilePathProperty);
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:如果您使用选项模式,而不是每次写入日志消息时检查配置,上面的示例丰富器可能会更有效。

使用可以根据配置动态设置属性的丰富器LogFilePath,您只需将日志记录管道配置为基于该属性进行映射即可。

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .Enrich.With<LogFileNameEnricher>()
    .WriteTo.Map(LogFileNameEnricher.LogFilePathPropertyName,
        (logFilePath, wt) => wt.File($"{logFilePath}"), sinkMapCountLimit: 1)
    .CreateLogger();

// ...

Log.CloseAndFlush();
Run Code Online (Sandbox Code Playgroud)