首页 > 解决方案 > Stephen Cleary 关于异步的新日志记录模式问题

问题描述

在我测试过Stephen Cleary 的新日志模式后,我很喜欢它。但是,有一点我不明白:

不幸的是,这里的解决方案不适用于异步代码。这是因为异步会导致异常被捕获,然后在等待时重新抛出。因此,异常过滤器在等待点而不是最初引发异常的位置运行。

我想举一个例子,因为我的代码似乎对我来说很好用Handle(...)/hisTrue(...)和 no throw;

SubscribeAsync故意抛出异常。

public async Task StartAsync(CancellationToken cancellationToken)
{
    try
    {
        await SubscribeAsync().ConfigureAwait(false);
    }
    catch (Exception ex) when (Handle(() => _logger.LogError(ex, "Unexpected error.")))
    {
    }
}

public static class ExceptionFilterUtility
{
    public static bool Handle(Action action)
    {
        action();
        return true;
    }

    public static bool Propagate(Action action)
    {
        action();
        return false;
    }
}

我的堆栈跟踪是:

[2021-03-13 20:16:32 Error] ElonMuskBot.Core.TradeManagers.LiveTradeManager
Unexpected error.
ElonMuskBot.Core.Exceptions.RequestFailedException: Error while subscribing to the Spot candlestick update stream
   at ElonMuskBot.Core.Clients.SpotBotClient.SubscribeToCandleUpdatesAsync(String symbol, KlineInterval timeInterval, Action`1 onMessage) in E:\GitHub\elonmuskbot\src\ElonMuskBot.Core\Clients\SpotBotClient.cs:line 88
   at ElonMuskBot.Core.TradeManagers.LiveTradeManager.SubscribeAsync() in E:\GitHub\elonmuskbot\src\ElonMuskBot.Core\TradeManagers\LiveTradeManager.cs:line 54
   at ElonMuskBot.Core.TradeManagers.LiveTradeManager.StartAsync(CancellationToken cancellationToken) in E:\GitHub\elonmuskbot\src\ElonMuskBot.Core\TradeManagers\LiveTradeManager.cs:line 38

标签: c#

解决方案


我想举一个例子,因为我的代码似乎对我来说很好,使用 Handle(...)/his True(...) 并且没有 throw;。

该模式的重点是在throw. 无论哪种方式,异常处理本身都可以正常工作,但捕获的范围不同。

由于异常过滤器实际上是在throw. 因此异常记录在throw,包括所有存在的日志记录范围。

但是,它不适用于异步代码。异常将被很好地记录,但记录范围将不存在。这是因为异常过滤器在 的点执行await,而不是在throw.

因此,这是一个无法正确捕获异常日志记录范围的示例:

public async Task SubscribeAsync()
{
  using var _ = _logger.BeginScope("This will not be logged");
  await Task.Yield();
  throw new InvalidOperationException("This will be logged");
}

自从写了那篇博文以来,我开发了一个更好的解决方案,它同时适用于同步和异步异常,并将它作为一个库发布。我还没来得及写一篇新的博客文章(对不起!),但一旦我这样做了,我就会将旧的解决方案指向新的解决方案。

较新的解决方案仅适用于 .NET Core,因为它与ILogger. 示例用法:

// Don't forget to call IServiceCollection.AddExceptionLoggingScopes() in your startup.
public async Task StartAsync(CancellationToken cancellationToken)
{
  try
  {
    await SubscribeAsync().ConfigureAwait(false);
  }
  catch (Exception ex)
  {
    using (_logger.BeginCapturedExceptionLoggingScopes(ex))
      _logger.LogError(ex, "Unexpected error.");
  }
}

推荐阅读