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

6

我拥有动态更改日志文件路径的功能。但是当我更改配置在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}")

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

因此,我们希望停止向旧路径写入。

2个回答

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();

在你的情况下,你想根据配置变化动态更改这个属性名称。一种简单的方法是创建一个自定义enricher,该enricher可以根据你的配置设置更改上述属性的值。
你的自定义enricher应该类似于以下方式:
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);
    }
}

注意:上面的示例增强器可以更有效地使用Options模式,而不是每次写日志消息时都要检查配置。
具有动态设置LogFilePath属性的增强器可以根据配置为您设置该属性,因此您只需配置日志管道以基于该属性进行映射。
Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .Enrich.With<LogFileNameEnricher>()
    .WriteTo.Map(LogFileNameEnricher.LogFilePathPropertyName,
        (logFilePath, wt) => wt.File($"{logFilePath}"), sinkMapCountLimit: 1)
    .CreateLogger();

// ...

Log.CloseAndFlush();

关于 .Enrich.With<LogFileNameEnricher>(),当我尝试实现时出现以下错误:CS1503 无法将 'SomeProgramNamespace.LogFileNameEnricher' 转换为 'Serilog.Core.ILogEventEnricher',你有什么想法吗? - George 2.0 Hope

5

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
                }
            }
        ]
...

我创建了一个扩展方法,用于在配置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();
            });
        }
}

我在Program.cs中这样使用它:

...
 hostBuilder
  .ConfigureAppConfiguration((hostCtx, configBuilder) => { /* other config */ })
  .ConfigureSerilog(appName)
...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接