【问题标题】:Stephen Cleary's new logging pattern question about asyncStephen Cleary 关于异步的新日志记录模式问题
【发布时间】:2021-03-15 21:54:23
【问题描述】:

我喜欢 Stephen Cleary 的新 logging pattern 测试。但是,有一些我不明白的地方:

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

我想举一个例子,因为我的代码似乎对我来说很好用 Handle(...)/his True(...) 而没有 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

【问题讨论】:

  • 你声称“没问题”的输出是......
  • @CaiusJard,我没明白你的意思。
  • 你的异常的堆栈跟踪是什么?
  • @CaiusJard,i.imgur.com/NXEQwVr.png,这是正确的。它就在那里。
  • 我从未见过 SO 强制执行字符限制,但鉴于您在屏幕截图中只显示了大约 6 行,您清楚地掌握了“演示所需的合理数据量是多少点”,所以在将文本编辑到问题中时,请随意做出相同的判断

标签: c#


【解决方案1】:

我想举一个例子,因为我的代码似乎对我来说很好,使用 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");
}

自从写了那篇博文以来,我开发了一个更好的解决方案,它适用于同步和异步异常以及released it as a library。我还没来得及写一篇新的博文(抱歉!),但我会尽快将旧的解决方案指向新的解决方案。

较新的解决方案仅适用于 .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.");
  }
}

【讨论】:

  • 感谢您的回答!我看到了替代方案:github.com/Tolyandre/serilog-throw-context-enricher。我实际上正在使用 Serilog。使用哪个更好?
  • 那么我肯定会使用 Serilog 的。因为您可以在您的启动中设置它,然后不必打扰BeginCapturedExceptionLoggingScopesBeginCapturedExceptionLoggingScopes 之所以存在,是因为 Microsoft 依赖注入库的功能不足以扩展记录器以自动执行此操作。 Serilog 库直接扩展了 Serilog 记录器,因此它没有相同的限制,因此它的 API 更易于使用(不需要BeginCapturedExceptionLoggingScopes)。
  • 非常感谢您的解释!
猜你喜欢
  • 2021-06-13
  • 1970-01-01
  • 1970-01-01
  • 2015-07-14
  • 1970-01-01
  • 1970-01-01
  • 2019-10-15
  • 1970-01-01
  • 2011-09-15
相关资源
最近更新 更多