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

不借助HttpRequestMessage,如何将ILogger<T>传入Polly重试策略

解决方法:将调用站点的ILogger传递给Polly重试委托(针对GetStreamAsync等无HttpRequestMessage参数的HttpClient方法)

由于GetStreamAsync、GetStringAsync这类HttpClient便捷方法内部封装了HttpRequestMessage的创建,但未暴露传入Polly Context的入口,我们可以通过以下两种实用方案来传递ILogger:

方案一:手动构造HttpRequestMessage,复用SendAsync传递Polly Context

直接模拟GetStreamAsync的内部逻辑,手动创建请求并传入携带ILogger的Polly Context:

调用站点代码(控制器中)

private readonly HttpClient _httpClient;
private readonly ILogger<MyController> _logger;

public MyController(HttpClient httpClient, ILogger<MyController> logger)
{
    _httpClient = httpClient;
    _logger = logger;
}

public async Task<IActionResult> FetchDataStream()
{
    // 1. 构造请求
    var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data-stream");
    
    // 2. 创建Polly Context并存入ILogger
    var pollyContext = new Context();
    pollyContext["CallSiteLogger"] = _logger;
    
    // 3. 携带Context调用SendAsync
    var response = await _httpClient.SendAsync(request, pollyContext);
    response.EnsureSuccessStatusCode();
    
    // 4. 获取Stream(和GetStreamAsync逻辑一致)
    var stream = await response.Content.ReadAsStreamAsync();
    // 后续处理stream...
    return File(stream, "application/octet-stream");
}

重试策略定义

从Polly Context中取出Logger并记录重试日志:

var retryPolicy = Policy<HttpResponseMessage>
    .HandleResult(resp => !resp.IsSuccessStatusCode)
    .WaitAndRetryAsync(3, 
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
        onRetry: (result, waitTime, retryCount, context) =>
        {
            // 安全取出ILogger
            if (context.TryGetValue("CallSiteLogger", out var loggerObj) && loggerObj is ILogger logger)
            {
                logger.LogWarning("请求失败(状态码:{StatusCode}),第{RetryCount}次重试,等待{WaitSeconds:F1}秒",
                    result.Result.StatusCode, retryCount, waitTime.TotalSeconds);
            }
        });

方案二:扩展HttpClient,添加带Polly Context的便捷方法重载

如果需要频繁使用GetStreamAsync这类方法,可以封装扩展方法,避免重复构造HttpRequestMessage:

扩展方法定义

public static class HttpClientPollyExtensions
{
    // 对应GetStreamAsync(string requestUri)的重载
    public static async Task<Stream> GetStreamAsync(this HttpClient client, string requestUri, Context pollyContext, CancellationToken cancellationToken = default)
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
        var response = await client.SendAsync(request, pollyContext, cancellationToken);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStreamAsync(cancellationToken);
    }

    // 对应GetStreamAsync(Uri requestUri)的重载
    public static async Task<Stream> GetStreamAsync(this HttpClient client, Uri requestUri, Context pollyContext, CancellationToken cancellationToken = default)
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
        var response = await client.SendAsync(request, pollyContext, cancellationToken);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStreamAsync(cancellationToken);
    }
}

调用站点代码

public async Task<IActionResult> FetchDataStream()
{
    var pollyContext = new Context();
    pollyContext["CallSiteLogger"] = _logger;
    
    // 直接调用扩展方法
    var stream = await _httpClient.GetStreamAsync("https://api.example.com/data-stream", pollyContext);
    // 后续处理stream...
    return File(stream, "application/octet-stream");
}

优化建议:类型安全的Context键

为避免字符串键拼写错误,可定义静态类统一管理Context键:

public static class PollyContextKeys
{
    public const string CallSiteLogger = nameof(CallSiteLogger);
}

使用时直接替换为PollyContextKeys.CallSiteLogger即可。


内容的提问来源于stack exchange,提问作者Dr. Strangelove

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 11:26:12