ASP .NET Core 中的请求-响应日志记录

参考源码:https://download.youkuaiyun.com/download/hefeng_aspnet/90084914 

        记录 ASP .NET Core http 请求和响应是几乎每个 .NET 开发人员迟早都会面临的常见任务。长期以来,开发团队选择的最流行的方法似乎是编写自定义中间件。但是,既然 .NET 6 我们有一个Microsoft.AspNetCore.HttpLogging内置库。所以,亲爱的 .NET 海狸们,让我们看看微软给我们的日志吧!并且找到更好的。

我们的人工智能生成的吉祥物带有原木 

设置日志记录

我们将从 .NET 提供的最简单的设置开始,运行以下控制台命令 

dotnet new web 

        检查 http 请求的每个部分的日志记录非常重要,例如请求主体、查询字符串、错误和路径参数。因此默认Hello World端点不行。让我们创建一个具有 blackjack 查询和路由参数的方: 

app.MapPost("/parties/{partyId}/guests", (string partyId, [FromQuery] bool? loungeAccess, Guest visitor) => {
    if (loungeAccess == true && !visitor.Vip) 
        throw new NotEnoughLevelException();

    return new Ticket(
        PartyId: partyId,
        Receiver: visitor.Name,
        LoungeAccess: loungeAccess ?? false,
        Code: Guid.NewGuid().ToString()
    );
});

app.Run();

public record Guest(string Name, bool Vip);
public record Ticket(string PartyId, string Receiver, bool LoungeAccess, string Code);
public class NotEnoughLevelException : Exception;

        默认情况下,NotEnoughLevelExceptionASP .NET Core 将粗略地中断请求并返回InternalServerError。让我们改为返回一个错误对象。实现它的最简单方法可能是使用Nist.Errorsnuget 包。要安装它,我们将使用以下命令:

dotnet add package Nist.Errors

并将我们的异常映射到相应的错误:

app.UseErrorBody<Error>(ex => ex switch {
    NotEnoughLevelException _ => new (HttpStatusCode.BadRequest, "NotEnoughLevel"),
    _ => new (HttpStatusCode.InternalServerError, "Unknown")
}, showException: false);

现在,让我们开始记录请求和响应。最简单的启用方法包括 4 个步骤:

1. 注册http日志服务

builder.Services.AddHttpLogging(o => {});

2.附加http日志记录中间件

app.UseHttpLogging();

3. 删除appsettings.Development.json以尽量减少配置开销

4. 由于默认情况下的日志级别Microsoft.AspNetCore是Warning为 http 日志中间件指定专用的日志级别appsettings.json

"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

完成所有这些更改后,我们的完整版本Program.cs将如下所示:

using Microsoft.AspNetCore.Mvc;
using Nist.Errors;
using System.Net;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(o => {});

var app = builder.Build();

app.UseHttpLogging();

app.UseErrorBody<Error>(ex => ex switch {
    NotEnoughLevelException _ => new (HttpStatusCode.BadRequest, "NotEnoughLevel"),
    _ => new (HttpStatusCode.InternalServerError, "Unknown")
}, showException: false);

app.MapPost("/parties/{partyId}/guests", (string partyId, [FromQuery] bool? loungeAccess, Guest visitor) => {
    if (loungeAccess == true && !visitor.Vip) 
        throw new NotEnoughLevelException();

    return new Ticket(
        PartyId: partyId,
        Receiver: visitor.Name,
        LoungeAccess: loungeAccess ?? false,
        Code: Guid.NewGuid().ToString()
    );
});

app.Run();

public record Guest(string Name, bool Vip);
public record Ticket(string PartyId, string Receiver, bool LoungeAccess, string Code);
public class NotEnoughLevelException : Exception;


让我们测试一下,从一个正常请求开始:

POST http://localhost:5244/parties/new-year/guests?loungeAccess=true

{
    "name": "Paul",
    "vip" : true
}

以下是我们得到的日志:

现在让我们讨论一个“糟糕的”请求。

POST http://localhost:5244/parties/halloween/guests?loungeAccess=true

{
    "name": "John",
    "vip" : false
}

我们将获得:

配置日志记录
        如您所见,尽管我们记录了很多内容,但我们无法真正说出有关请求的很多信息。我们可能会找出哪个端点被击中以及我们的响应是否成功(如果我们使用 http 状态代码),但仅此而已。此外,在高负载环境中,匹配请求和响应可能是一个挑战,因为它们是分开记录的。所以让我们CombineLogs看看我们可以记录什么。以下是代码:

builder.Services.AddHttpLogging(o => {
    o.CombineLogs = true;

    o.LoggingFields = HttpLoggingFields.All | HttpLoggingFields.RequestQuery;
});

HttpLoggingFields.All有点撒谎,日志中也明确指出:“HttpRequest.QueryString 不包含在该标志中,因为它可能包含私人信息”。因此我们需要手动附加 RequestQuery

这就是我们现在得到的:

        这样就好多了,现在我们可以看到到底收到了什么、响应了什么,以及处理花了多少时间。然而,日志仍然让人感到不知所措——我们记录了很多标头,但似乎没有任何价值。让我们只留下我们需要的东西:

builder.Services.AddHttpLogging(o => {
    o.CombineLogs = true;

    o.LoggingFields = HttpLoggingFields.RequestQuery
        | HttpLoggingFields.RequestMethod
        | HttpLoggingFields.RequestPath
        | HttpLoggingFields.RequestBody
        | HttpLoggingFields.ResponseStatusCode
        | HttpLoggingFields.ResponseBody
        | HttpLoggingFields.Duration;
});

现在,我们将以更为紧凑的格式获得几乎相同数量的有用信息: 

使其变得更好
然而,我们现有的技术仍然存在一些问题:

        1、请注意,由于我们有路径变量,因此partyId字段的值path对于单个端点来说并不相同。这将阻止我们通过特定端点获取分析数据。

        2、请求响应日志与发生异常的日志之间没有任何联系。这会使查找失败响应的确切异常变得复杂。特别是当异常无法识别时。

        3、由于多种原因,日志仍然占用大量空间。首先,我们有无用的不可移除字段(RequestBodyStatus, PathBase)。其次,我们的格式非常“宽”,总共使用 4 行来表示方法、路径和查询字符串。

        幸运的是,我们有一个 nuget 包可以弥补内置库的盲点。我们来看看吧。首先,我们需要安装它:

dotnet add package Nist.Logging

然后只需更改三行:

using Nist.Logs;

// remove builder.Services.AddHttpLogging

app.UseHttpIOLogging(); // instead of app.UseHttpLogging();

我们得到了最少但功能齐全的日志:

        尽管仍然记录发生的异常,但我们不再需要依赖它,因为我们在日志中间件ExceptionHandlerMiddleware中记录了异常。您甚至可以ExceptionHandlerMiddleware在"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "None"appsettings.json

回顾
最后,让我们看一下我们能够获得的最佳请求 - 响应日志: 

        尽管Microsoft.AspNetCore.NET 6 提供了广泛的开箱即用的 http 日志记录功能,但仍缺少一些功能。因此,我们使用了Nist.Logsnuget 包。下表总结了库的功能列表:

| Feature                   | Microsoft.AspNetCore.HttpLogging        | Nist.Logs |
|--------------------         |--------------------------------------|-----------     |
| Full Uri                    | ❌ - Only Path and Query Separately     | ✅         |
| Minimalism              | ❌ - Few unremovable redundant fields | ✅         |
| Exception attached | ❌                                                            | ✅         |
| Endpoint id              | ❌                                                            | ✅         |
| Single Line              | ✅                                                            | ✅         |
| Http Method            | ✅                                                            | ✅         |
| Request Body         | ✅                                                            | ✅         |
| Response Body      | ✅                                                            | ✅         |
| Response Code      | ✅                                                            | ✅         |
| Request Duration    | ✅                                                            | ✅         |

以下是最终版本,供快速参考Program.cs:

using Microsoft.AspNetCore.Mvc;
using Nist.Logs;
using Nist.Errors;
using System.Net;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpIOLogging();
app.UseErrorBody<Error>(ex => ex switch {
    NotEnoughLevelException _ => new (HttpStatusCode.BadRequest, "NotEnoughLevel"),
    _ => new (HttpStatusCode.InternalServerError, "Unknown")
}, showException: false);
app.MapPost("/parties/{partyId}/guests", (string partyId, [FromQuery] bool? loungeAccess, Guest visitor) => {
    if (loungeAccess == true && !visitor.Vip) 
        throw new NotEnoughLevelException();
    return new Ticket(
        PartyId: partyId,
        Receiver: visitor.Name,
        LoungeAccess: loungeAccess ?? false,
        Code: Guid.NewGuid().ToString()
    );
});
app.Run();
public record Guest(string Name, bool Vip);
public record Ticket(string PartyId, string Receiver, bool LoungeAccess, string Code);
public class NotEnoughLevelException : Exception;

项目代码:https://download.youkuaiyun.com/download/hefeng_aspnet/90084914 

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

csdn_aspnet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值