.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
相关产品推荐
相关产品推荐

