You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core中使用Refit时如何正确记录HTTP请求?

如何正确记录Refit发送的原始HTTP请求

问题场景

使用Refit调用API获取access token时,POST请求返回400状态码,需要查看发送给服务器的原始请求内容排查问题。尝试集成HttpLoggingHandler时遇到两个错误:

  • 未注册Handler时,提示No service for type ...HttpLoggingHandler has been registered
  • 注册为Transient后,提示The 'InnerHandler' property must be null. 'DelegatingHandler' instances provided to 'HttpMessageHandlerBuilder' must not be reused or cached
  • 微软官方HTTP日志方案无输出

解决方案

方案1:自定义日志DelegatingHandler(推荐)

核心问题是DelegatingHandler不能被复用,因此不能直接从DI容器获取已注册的实例,需每次创建新实例。

1. 实现自定义日志Handler

public class HttpLoggingHandler : DelegatingHandler
{
    private readonly ILogger<HttpLoggingHandler> _logger;

    public HttpLoggingHandler(ILogger<HttpLoggingHandler> logger)
    {
        _logger = logger;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 记录请求详情
        _logger.LogDebug("请求方法: {Method}, 请求地址: {Uri}", request.Method, request.RequestUri);
        if (request.Content != null)
        {
            var content = await request.Content.ReadAsStringAsync(cancellationToken);
            _logger.LogDebug("请求内容: {Content}", content);
        }

        // 发送请求并记录响应
        var response = await base.SendAsync(request, cancellationToken);
        _logger.LogDebug("响应状态码: {StatusCode}", response.StatusCode);
        if (response.Content != null)
        {
            var content = await response.Content.ReadAsStringAsync(cancellationToken);
            _logger.LogDebug("响应内容: {Content}", content);
        }

        return response;
    }
}

2. 集成到Refit客户端

在注册Refit客户端时,通过工厂委托创建Handler实例,避免复用:

builder.Services.AddRefitClient<IVismaConnect>()
    .ConfigureHttpClient(config =>
    {
        config.BaseAddress = new Uri("https://ENDPOINT");
        config.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "SOMECREDENTIALSTHATEVENTUALLYWILLBEREADFROMACONFIGFILE");
        config.DefaultRequestHeaders.TryAddWithoutValidation("content-type", "application/x-www-form-urlencoded");
    })
    .AddHttpMessageHandler(sp => new HttpLoggingHandler(sp.GetRequiredService<ILogger<HttpLoggingHandler>>()));

方案2:使用HttpClient内置日志

无需自定义Handler,通过配置日志级别开启HttpClient的详细日志:

1. 修改appsettings.json配置

添加日志级别设置:

{
  "Logging": {
    "LogLevel": {
      "System.Net.Http.HttpClient": "Debug",
      "System.Net.Http.HttpClient.IVismaConnect.LogicalHandler": "Debug",
      "System.Net.Http.HttpClient.IVismaConnect.ClientHandler": "Debug"
    }
  }
}

2. 注册Refit客户端

无需额外添加Handler,直接注册即可:

builder.Services.AddRefitClient<IVismaConnect>()
    .ConfigureHttpClient(config =>
    {
        config.BaseAddress = new Uri("https://ENDPOINT");
        config.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "SOMECREDENTIALSTHATEVENTUALLYWILLBEREADFROMACONFIGFILE");
        config.DefaultRequestHeaders.TryAddWithoutValidation("content-type", "application/x-www-form-urlencoded");
    });

方案3:Refit内置日志

通过RefitSettings启用内置日志:

builder.Services.AddRefitClient<IVismaConnect>(new RefitSettings
{
    LoggerFactory = builder.Logging.Services.BuildServiceProvider().GetRequiredService<ILoggerFactory>()
})
.ConfigureHttpClient(config =>
{
    // HttpClient配置
});

修改后的完整Program.cs示例

var builder = WebApplication.CreateBuilder(args);

// 配置日志提供者
builder.Logging.ClearProviders();
builder.Logging.AddConfiguration(builder.Configuration)
               .AddConsole()
               .AddDebug()
               .AddSimpleConsole();

// 官方HTTP日志(针对入站请求,可选)
builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
});

// 注册Refit客户端+自定义日志Handler
builder.Services.AddRefitClient<IVismaConnect>()
    .ConfigureHttpClient(config =>
    {
        config.BaseAddress = new Uri("https://ENDPOINT");
        config.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "SOMECREDENTIALSTHATEVENTUALLYWILLBEREADFROMACONFIGFILE");
        config.DefaultRequestHeaders.TryAddWithoutValidation("content-type", "application/x-www-form-urlencoded");
    })
    .AddHttpMessageHandler(sp => new HttpLoggingHandler(sp.GetRequiredService<ILogger<HttpLoggingHandler>>()));

// 配置Configuration
builder.Configuration
    .SetBasePath(Environment.CurrentDirectory)
    .AddJsonFile("appsettings.json", true)
    .AddUserSecrets(Assembly.GetExecutingAssembly(), true)
    .AddEnvironmentVariables();

ConfigurationManager configuration = builder.Configuration;
C.Instance.SetConfiguration(configuration);

IWebHostEnvironment environment = builder.Environment;

// CORS配置
builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      builder =>
                      {
                          builder.WithOrigins("*").AllowAnyMethod().AllowAnyHeader();
                      });
});

builder.Services.AddControllersWithViews();

var app = builder.Build();

// 请求管道配置
if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
}

app.UseHttpLogging();

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] = new string[] { "My Response Header Value" };
    await next();
});

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");

app.MapFallbackToFile("index.html");

app.Run();

内容的提问来源于stack exchange,提问作者Aileron79

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 18:14:55