Kestrel 中的诊断

作者:Sourabh Shirhatti

本文提供从 Kestrel 收集诊断以帮助解决问题的指南。 涵盖的主题包括:

  • 日志记录:写入 .NET Core 日志记录的结​​构化日志。 应用框架使用 ILogger 来编写日志,用户使用它在应用中进行用户自己的日志记录。
  • 指标:一段时间间隔内数据度量值的表示形式,例如每秒请求数。 指标是使用 EventCounter 发出的,可以使用 dotnet-counters 命令行工具或 Application Insights 进行观察。
  • DiagnosticSource:DiagnosticSource 是一种用于记录生产时间的机制,具有丰富的数据有效负载可供进程使用。 与日志记录不同,后者假设数据将离开进程并且需要可序列化的数据,DiagnosticSource 可以很好地处理复杂数据。

Logging

与 ASP.NET Core 中的大多数组件一样,Kestrel 使用 Microsoft.Extensions.Logging 发出日志信息。 Kestrel 使用多个类别,你可以选择要侦听的日志。

日志记录类别名称 日志记录事件
Microsoft.AspNetCore.Server.Kestrel ApplicationError, ConnectionHeadResponseBodyWrite, ApplicationNeverCompleted, RequestBodyStart, RequestBodyDone, RequestBodyNotEntirelyRead, RequestBodyDrainTimedOut, ResponseMinimumDataRateNotSatisfied, InvalidResponseHeaderRemoved, HeartbeatSlow
Microsoft.AspNetCore.Server.Kestrel.BadRequests ConnectionBadRequest, RequestProcessingError, RequestBodyMinimumDataRateNotSatisfied
Microsoft.AspNetCore.Server.Kestrel.Connections ConnectionAccepted, ConnectionStart, ConnectionStop, ConnectionPause, ConnectionResume, ConnectionKeepAlive, ConnectionRejected, ConnectionDisconnect, NotAllConnectionsClosedGracefully, NotAllConnectionsAborted, ApplicationAbortedConnection
Microsoft.AspNetCore.Server.Kestrel.Http2 Http2ConnectionError, Http2ConnectionClosing, Http2ConnectionClosed, Http2StreamError, Http2StreamResetAbort, HPackDecodingError, HPackEncodingError, Http2FrameReceived, Http2FrameSending, Http2MaxConcurrentStreamsReached
Microsoft.AspNetCore.Server.Kestrel.Http3 Http3ConnectionError, Http3ConnectionClosing, Http3ConnectionClosed, Http3StreamAbort, Http3FrameReceived, Http3FrameSending

连接日志记录

Kestrel 还支持为字节级别的通信发出 Debug 级别日志,并可根据每个终结点启用该功能。 若要启用连接日志记录,请参阅为 Kestrel 配置终结点

指标

指标是一段时间间隔内数据度量值的表示形式,例如每秒请求数。 使用指标数据可以在高级别观察应用的状态。 Kestrel 指标是使用 EventCounter 发出的。

注意

connections-per-secondtls-handshakes-per-second 计数器命名不正确。 计数器:

  • 并不总是包含新建连接数或每秒 TLS 握手数
  • 显示上次更新间隔内新建连接数或 TLS 握手数,正如事件使用者通过 filterPayloadKestrelEventSource 中的 EventCounterIntervalSec 参数请求的那样。

建议这些计数器的使用者按照 DisplayRateTimeScale 为一秒的情况来缩放指标值。

名称 显示名称 说明
connections-per-second 连接率 每个更新间隔的新传入连接数
total-connections 总连接数 总连接数
tls-handshakes-per-second TLS 握手率 每个更新间隔的新 TLS 握手数
total-tls-handshakes TLS 握手总数 TLS 握手总数
current-tls-handshakes 当前 TLS 握手数 正在进行的 TLS 握手数
failed-tls-handshakes 失败的 TLS 握手数 失败的 TLS 握手总数
current-connections 当前连接数 总连接数,包括空闲连接数
connection-queue-length 连接队列长度 排队到线程池的总连接数。 在处于稳定状态的正常系统中,此数字应始终接近零
request-queue-length 请求队列长度 排队到线程池的请求总数。 在处于稳定状态的正常系统中,此数字应始终接近零。 此指标与 IIS/Http.Sys 请求队列不同,不能进行比较
current-upgraded-requests 当前升级请求 (Websocket) 活动的 WebSocket 请求数

DiagnosticSource

Kestrel 为服务器层上被拒绝的 HTTP 请求发出 DiagnosticSource 事件,例如格式错误的请求和协议冲突。 因此,这些请求决不会进入 ASP.NET Core 的宿主层。

Kestrel 使用 Microsoft.AspNetCore.Server.Kestrel.BadRequest 事件名称和 IFeatureCollection 作为对象有效负载发出这些事件。 可以通过访问功能集合上的 IBadRequestExceptionFeature 来检索基础异常。

解决这些事件的过程分为两个步骤。 必须创建 DiagnosticListener 观察程序:

class BadRequestEventListener : IObserver<KeyValuePair<string, object>>, IDisposable
{
    private readonly IDisposable _subscription;
    private readonly Action<IBadRequestExceptionFeature> _callback;

    public BadRequestEventListener(DiagnosticListener diagnosticListener, Action<IBadRequestExceptionFeature> callback)
    {
        _subscription = diagnosticListener.Subscribe(this!, IsEnabled);
        _callback = callback;
    }
    private static readonly Predicate<string> IsEnabled = (provider) => provider switch
    {
        "Microsoft.AspNetCore.Server.Kestrel.BadRequest" => true,
        _ => false
    };
    public void OnNext(KeyValuePair<string, object> pair)
    {
        if (pair.Value is IFeatureCollection featureCollection)
        {
            var badRequestFeature = featureCollection.Get<IBadRequestExceptionFeature>();

            if (badRequestFeature is not null)
            {
                _callback(badRequestFeature);
            }
        }
    }
    public void OnError(Exception error) { }
    public void OnCompleted() { }
    public virtual void Dispose() => _subscription.Dispose();
}

通过观察程序订阅 ASP.NET Core DiagnosticListener。 在此示例中,我们将创建一个记录基础异常的回调。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var diagnosticSource = app.Services.GetRequiredService<DiagnosticListener>();
using var badRequestListener = new BadRequestEventListener(diagnosticSource, (badRequestExceptionFeature) =>
{
    app.Logger.LogError(badRequestExceptionFeature.Error, "Bad request received");
});
app.MapGet("/", () => "Hello world");
app.Run();

附加了调试器的行为

将调试器附加到 Kestrel 进程时,不会强制实施某些超时和速率限制。 有关详细信息,请参阅附加了调试器的行为