ASP.NET Core 2中的日志配置更改

时间:2018-08-28 09:01:39

标签: c# asp.net-core asp.net-core-2.1

我要在更改配置时记录日志。

I do thisProgram.csStartup.cs中:

ChangeToken.OnChange(
  () => configuration.GetReloadToken(),
  state => logger.Information("Configuration reloaded"),
  (object)null
);

但是我收到了两次变更报告,因此需要将其删除。 The advice is to do this

ChangeToken.OnChange(
  () => configuration.GetReloadToken(),
  state => { Thread.Sleep(2000); logger.Information("Configuration reloaded"); },
  (object)null
);

我在这里使用2000是因为不确定什么值合理。

我发现有时候我仍然可以检测到多个变更,相隔2000毫秒。因此,反跳对我不起作用,只会在报告的更改之间造成延迟。如果我设置一个很高的值,那么我只会得到一份报告,但这并不理想(并且掩盖了问题)。

所以我想知道:

  • 这真的是在反跳,还是只是对报告的更改进行排队?
  • 我使用1000到5000之间的值来表示成功。别人在用什么?
  • 睡眠是否已发布到服务器的主线程?我希望不会!

2 个答案:

答案 0 :(得分:1)

讨论 here 的多个更改检测问题(以及多个存储库中的至少十几个其他问题)是他们拒绝使用内置机制解决的问题。

MS 文档使用 file hashing 方法,但我认为去抖动更好。

我的解决方案使用 async(避免 async-in-sync 可能会意外炸毁某些东西)和一个 hosted service 来消除变化检测。

Debouncer.cs

public sealed class Debouncer : IDisposable {

  public Debouncer(TimeSpan? delay) => _delay = delay ?? TimeSpan.FromSeconds(2);

  private readonly TimeSpan _delay;
  private CancellationTokenSource? previousCancellationToken = null;

  public async Task Debounce(Action action) {
    _ = action ?? throw new ArgumentNullException(nameof(action));
    Cancel();
    previousCancellationToken = new CancellationTokenSource();
    try {
      await Task.Delay(_delay, previousCancellationToken.Token);
      await Task.Run(action, previousCancellationToken.Token);
    }
    catch (TaskCanceledException) { }    // can swallow exception as nothing more to do if task cancelled
  }

  public void Cancel() {
    if (previousCancellationToken != null) {
      previousCancellationToken.Cancel();
      previousCancellationToken.Dispose();
    }
  }

  public void Dispose() => Cancel();

}

ConfigWatcher.cs

public sealed class ConfigWatcher : IHostedService, IDisposable {

  public ConfigWatcher(IServiceScopeFactory scopeFactory, ILogger<ConfigWatcher> logger) {
    _scopeFactory = scopeFactory;
    _logger = logger;
  }

  private readonly IServiceScopeFactory _scopeFactory;
  private readonly ILogger<ConfigWatcher> _logger;

  private readonly Debouncer _debouncer = new(TimeSpan.FromSeconds(2));

  private void OnConfigurationReloaded() {
    _logger.LogInformation("Configuration reloaded");
    // ... can do more stuff here, e.g. validate config
  }

  public Task StartAsync(CancellationToken cancellationToken) {
    ChangeToken.OnChange(
      () => {                                                 // resolve config from scope rather than ctor injection, in case it changes (this hosted service is a singleton)
        using var scope = _scopeFactory.CreateScope();
        var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
        return configuration.GetReloadToken();
      },
      async () => await _debouncer.Debounce(OnConfigurationReloaded)
    );
    return Task.CompletedTask;
  }

  public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;

  public void Dispose() => _debouncer.Dispose();

}

Startup.cs

services.AddHostedService<ConfigWatcher>();        // registered as singleton

答案 1 :(得分:0)

希望其他人可以回答您的问题,但是我确实遇到了这个问题并找到了Gist by cocowalla。 cocowalla提供的代码会反跳,而不仅仅是等待。它为我成功地删除了更改回调。 Cocowalla还包含一个扩展方法,因此您只需在OnChange上调用IConfiguration

这里是一个示例:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    public static async Task Main(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
            .Build();

        configuration.OnChange(() => Console.WriteLine("configuration changed"));

        while (true)
        {
            await Task.Delay(1000);
        }
    }
}

public class Debouncer : IDisposable
{
    private readonly CancellationTokenSource cts = new CancellationTokenSource();
    private readonly TimeSpan waitTime;
    private int counter;

    public Debouncer(TimeSpan? waitTime = null)
    {
        this.waitTime = waitTime ?? TimeSpan.FromSeconds(3);
    }

    public void Debouce(Action action)
    {
        var current = Interlocked.Increment(ref this.counter);

        Task.Delay(this.waitTime).ContinueWith(task =>
        {
                // Is this the last task that was queued?
                if (current == this.counter && !this.cts.IsCancellationRequested)
                action();

            task.Dispose();
        }, this.cts.Token);
    }

    public void Dispose()
    {
        this.cts.Cancel();
    }
}

public static class IConfigurationExtensions
{
    /// <summary>
    /// Perform an action when configuration changes. Note this requires config sources to be added with
    /// `reloadOnChange` enabled
    /// </summary>
    /// <param name="config">Configuration to watch for changes</param>
    /// <param name="action">Action to perform when <paramref name="config"/> is changed</param>
    public static void OnChange(this IConfiguration config, Action action)
    {
        // IConfiguration's change detection is based on FileSystemWatcher, which will fire multiple change
        // events for each change - Microsoft's code is buggy in that it doesn't bother to debounce/dedupe
        // https://github.com/aspnet/AspNetCore/issues/2542
        var debouncer = new Debouncer(TimeSpan.FromSeconds(3));

        ChangeToken.OnChange<object>(config.GetReloadToken, _ => debouncer.Debouce(action), null);
    }
}

在示例中,反跳延迟为3秒,对于我的json小文件,反跳延迟会在大约230毫秒左右停止重复数据消除。