【发布时间】:2021-05-22 06:35:26
【问题描述】:
目前我正在设计一个记录器服务来记录由HttpClient 制作的HttpRequests。
这个记录器服务是Singleton,我想在里面有scoped 上下文。
这是我的记录器:
public Logger
{
public LogContext Context { get; set; }
public void LogRequest(HttpRequestLog log)
{
context.AddLog(log);
}
}
我在DelegatingHandler 中使用记录器:
private readonly Logger logger;
public LoggingDelegatingHandler(Logger logger)
{
this.logger = logger;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await base.SendAsync(request, cancellationToken);
this.logger.LogRequest(new HttpRequestog());
}
然后,当我使用HttpClient 发出一些请求时,我希望获得此特定呼叫的日志:
private void InvokeApi()
{
var logContext = new LogContext();
this.Logger.LogContext = logContext;
var httpClient = httpClientFactory.CreateClient(CustomClientName);
await httpClient.GetAsync($"http://localhost:11111");
var httpRequestLogs = logContext.Logs;
}
问题是,它可以工作,但它不是线程安全的。如果我有InvokeApi 的并行执行,context 将不正确。
如何正确地为每次执行附加上下文?
我正在像这样注册 HttpClient:
services.AddSingleton<Logger>();
services.AddHttpClient(CentaurusHttpClient)
.ConfigurePrimaryHttpMessageHandler((c) => new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
})
.AddHttpMessageHandler(sp => new LoggingDelegatingHandler(sp.GetRequiredService<Logger>()));
我正在使用这个测试这段代码:
public void Test_Parallel_Logging()
{
Random random = new Random();
Action test = async () =>
{
await RunScopedAsync(async scope =>
{
IServiceProvider sp = scope.ServiceProvider;
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
using (var context = new HttpRequestContext(sp.GetRequiredService<Logger>()))
{
try
{
var httpClient = httpClientFactory.CreateClient();
await httpClient.GetAsync($"http://localhost:{random.Next(11111, 55555)}");
}
catch (HttpRequestException ex)
{
Output.WriteLine("count: " + context?.Logs?.Count);
}
}
});
};
Parallel.Invoke(new Action[] { test, test, test });
}
【问题讨论】:
-
这是什么类型的应用程序? web、windows 窗体、控制台等。听起来您几乎需要在单例记录器周围使用瞬态或请求范围的包装器,并且上下文将在那里。如果是非 Web 应用程序,则可以使用某种形式的线程本地存储。然后包装器可以在日志调用期间将上下文传递给实际的记录器。
-
您正在调用异步方法,因此需要等待它的调用,同时使您的方法异步。阅读有关异步的更多信息:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
-
这是一个网络项目。但将使用
Hangfire服务器运行后台服务。 -
作为快速修复,我会尝试长期使用
AsyncLocal- 我会考虑重构 Logger 并根据请求或其他方法注入它(可能使用现有的日志库之一) -
@Zokka 是的,您创建的示例是正确的,我忘记了这个。我编辑了问题并添加了我的单元测试。