|
|
|
@ -50,6 +50,8 @@
|
|
|
|
|
"#r \"nuget:System.Net.Http.Json\"\n",
|
|
|
|
|
"#r \"nuget:Microsoft.Extensions.Http\"\n",
|
|
|
|
|
"#r \"nuget:Microsoft.Extensions.DependencyInjection\"\n",
|
|
|
|
|
"#r \"nuget:Microsoft.Extensions.Logging\" \n",
|
|
|
|
|
"#r \"nuget:Microsoft.Extensions.Logging.Console\"\n",
|
|
|
|
|
"#r \"nuget:Polly\"\n",
|
|
|
|
|
"#r \"nuget:Microsoft.Extensions.Http.Polly\"\n",
|
|
|
|
|
"#r \"nuget:Refit\" \n",
|
|
|
|
@ -69,11 +71,17 @@
|
|
|
|
|
"global using System.Threading.Tasks;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"global using System.Net.Http;\n",
|
|
|
|
|
"global using System.Net.Http.Headers;\n",
|
|
|
|
|
"global using System.Net.Http.Json;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"global using Microsoft.Extensions.DependencyInjection;\n",
|
|
|
|
|
"global using Microsoft.Extensions.DependencyInjection.Extensions;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"global using Microsoft.Extensions.Logging;\n",
|
|
|
|
|
"global using Microsoft.Extensions.Logging.Console;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"global using Microsoft.Extensions.Http.Logging;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"global using Polly;\n",
|
|
|
|
|
"global using Polly.NoOp;\n",
|
|
|
|
|
"global using Polly.Simmy;\n",
|
|
|
|
@ -634,7 +642,7 @@
|
|
|
|
|
" {\n",
|
|
|
|
|
" await pipleLine.ExecuteAsync(async (inneerToken)=>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" var response = await client.GetAsync(\"api/Polly8/Retry_Exception\",inneerToken);\n",
|
|
|
|
|
" var response = await client.GetAsync(\"api/Polly8/RetryException\",inneerToken);\n",
|
|
|
|
|
" response.EnsureSuccessStatusCode();\n",
|
|
|
|
|
" });\n",
|
|
|
|
|
" }\n",
|
|
|
|
@ -859,6 +867,12 @@
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
"metadata": {
|
|
|
|
|
"dotnet_interactive": {
|
|
|
|
|
"language": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"polyglot_notebook": {
|
|
|
|
|
"kernelName": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"vscode": {
|
|
|
|
|
"languageId": "polyglot-notebook"
|
|
|
|
|
}
|
|
|
|
@ -1073,6 +1087,12 @@
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
"metadata": {
|
|
|
|
|
"dotnet_interactive": {
|
|
|
|
|
"language": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"polyglot_notebook": {
|
|
|
|
|
"kernelName": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"vscode": {
|
|
|
|
|
"languageId": "polyglot-notebook"
|
|
|
|
|
}
|
|
|
|
@ -1154,6 +1174,12 @@
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
"metadata": {
|
|
|
|
|
"dotnet_interactive": {
|
|
|
|
|
"language": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"polyglot_notebook": {
|
|
|
|
|
"kernelName": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"vscode": {
|
|
|
|
|
"languageId": "polyglot-notebook"
|
|
|
|
|
}
|
|
|
|
@ -1309,23 +1335,19 @@
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": 150,
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
"metadata": {
|
|
|
|
|
"dotnet_interactive": {
|
|
|
|
|
"language": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"polyglot_notebook": {
|
|
|
|
|
"kernelName": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"vscode": {
|
|
|
|
|
"languageId": "polyglot-notebook"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"outputs": [
|
|
|
|
|
{
|
|
|
|
|
"name": "stdout",
|
|
|
|
|
"output_type": "stream",
|
|
|
|
|
"text": [
|
|
|
|
|
"LoggerDelegatingHandler -> SendAsync -> Before\n",
|
|
|
|
|
"LoggerDelegatingHandler -> SendAsync -> After\n",
|
|
|
|
|
"Pong\n"
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"outputs": [],
|
|
|
|
|
"source": [
|
|
|
|
|
"//管道配置\n",
|
|
|
|
|
"\n",
|
|
|
|
@ -1377,8 +1399,8 @@
|
|
|
|
|
" handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;\n",
|
|
|
|
|
" })\n",
|
|
|
|
|
" //使用前先在AddTransient范围注册\n",
|
|
|
|
|
" //.AddHttpMessageHandler<LoggerDelegatingHandler>()\n",
|
|
|
|
|
" .AddHttpMessageHandler<LoggerDelegatingHandler>();\n",
|
|
|
|
|
" .AddHttpMessageHandler<LoggerDelegatingHandler>()\n",
|
|
|
|
|
" ;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
|
|
|
|
|
"\n",
|
|
|
|
@ -1399,6 +1421,326 @@
|
|
|
|
|
"### 日志配置"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"默认日志配置,需要先引用 `Microsoft.Extensions.Logging` 和 `Microsoft.Extensions.Logging.Console` 包,进行通用日志配置!"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
"metadata": {
|
|
|
|
|
"dotnet_interactive": {
|
|
|
|
|
"language": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"polyglot_notebook": {
|
|
|
|
|
"kernelName": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"vscode": {
|
|
|
|
|
"languageId": "polyglot-notebook"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"outputs": [],
|
|
|
|
|
"source": [
|
|
|
|
|
"//通用日志\n",
|
|
|
|
|
"{\n",
|
|
|
|
|
" ILoggerFactory loggerFactory = LoggerFactory.Create(buider =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" buider.AddConsole();\n",
|
|
|
|
|
" });\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" ILogger logger = loggerFactory.CreateLogger(\"logger\");\n",
|
|
|
|
|
" logger.LogInformation(\"直接使用的通用日志!\");\n",
|
|
|
|
|
"}\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"//IoC中使用\n",
|
|
|
|
|
"{\n",
|
|
|
|
|
" var services = new ServiceCollection();\n",
|
|
|
|
|
" services.AddLogging(config =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" config.SetMinimumLevel(LogLevel.Information);\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" config.AddConsole();\n",
|
|
|
|
|
" //config.AddSimpleConsole();\n",
|
|
|
|
|
" //config.AddSystemdConsole();\n",
|
|
|
|
|
" });\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" var serviceProvider = services.BuildServiceProvider();\n",
|
|
|
|
|
" var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();\n",
|
|
|
|
|
" var logger = loggerFactory.CreateLogger(\"logger\");\n",
|
|
|
|
|
" logger.LogInformation(\"IoC中使用日志!\");\n",
|
|
|
|
|
" logger.LogError(\"IoC中的错误日志!\");\n",
|
|
|
|
|
"}"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"#### 配置默认日志"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
"metadata": {
|
|
|
|
|
"dotnet_interactive": {
|
|
|
|
|
"language": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"polyglot_notebook": {
|
|
|
|
|
"kernelName": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"vscode": {
|
|
|
|
|
"languageId": "polyglot-notebook"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"outputs": [],
|
|
|
|
|
"source": [
|
|
|
|
|
"//配置默认日志(必须有常规日志及级别设置,否则不起使用)\n",
|
|
|
|
|
"{\n",
|
|
|
|
|
" var services = new ServiceCollection();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" // 1、配置通用日志\n",
|
|
|
|
|
" services.AddLogging(config =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" //日志级别\n",
|
|
|
|
|
" config.SetMinimumLevel(LogLevel.Trace);\n",
|
|
|
|
|
" //config.SetMinimumLevel(LogLevel.Information);\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" //日志载体\n",
|
|
|
|
|
" config.AddConsole();\n",
|
|
|
|
|
" //config.AddDebug();\n",
|
|
|
|
|
" //config.AddJsonConsole();\n",
|
|
|
|
|
" //config.AddSimpleConsole();\n",
|
|
|
|
|
" //config.AddSystemdConsole();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" });\n",
|
|
|
|
|
" services\n",
|
|
|
|
|
" .ConfigureHttpClientDefaults(options =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" //2、配置通用日志\n",
|
|
|
|
|
" options.AddDefaultLogger();\n",
|
|
|
|
|
" })\n",
|
|
|
|
|
" .AddHttpClient<HttpClient>(String.Empty,c =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" c.BaseAddress = new Uri(webApiBaseUrl);\n",
|
|
|
|
|
" c.DefaultRequestHeaders.Add(\"Authorization\", \"Bearer a.b.c\");\n",
|
|
|
|
|
" })\n",
|
|
|
|
|
" //2、或者单独配置此命名客户端日志\n",
|
|
|
|
|
" .AddDefaultLogger()\n",
|
|
|
|
|
" ;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
|
|
|
|
|
" var client = factory.CreateClient(String.Empty);\n",
|
|
|
|
|
" var response = await client.GetAsync(\"api/hello/index\");\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" response.EnsureSuccessStatusCode();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" var content = await response.Content.ReadAsStringAsync();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" Console.WriteLine(content);\n",
|
|
|
|
|
"}"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"#### 配置自定义日志"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"[博客](https://www.cnblogs.com/MingsonZheng/p/18013332) 可以参考"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
"metadata": {
|
|
|
|
|
"dotnet_interactive": {
|
|
|
|
|
"language": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"polyglot_notebook": {
|
|
|
|
|
"kernelName": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"vscode": {
|
|
|
|
|
"languageId": "polyglot-notebook"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"outputs": [],
|
|
|
|
|
"source": [
|
|
|
|
|
"/* 添加自定义日志记录\n",
|
|
|
|
|
" 1、可以指定当 HttpClient 启动请求、接收响应或引发异常时记录的内容和方式。可以同时添加多个自定义记录器(控制台、ETW 记录器),或“包装”和“不包装”记录器。由于其附加性质,可能需要事先显式删除默认的“旧”日志记录。\n",
|
|
|
|
|
" 要添加自定义日志记录,您需要实现 IHttpClientLogger 接口,然后使用 AddLogger 将自定义记录器添加到客户端。请注意,日志记录实现不应引发任何异常,否则可能会中断请求执行\n",
|
|
|
|
|
" 2、请求上下文对象\n",
|
|
|
|
|
" 上下文对象可用于将 LogRequestStart 调用与相应的 LogRequestStop 调用相匹配,以将数据从一个调用传递到另一个调用。 Context 对象由 LogRequestStart 生成,然后传递回 LogRequestStop。这可以是属性包或保存必要数据的任何其他对象。\n",
|
|
|
|
|
" 如果不需要上下文对象,实现可以从 LogRequestStart 返回 null。\n",
|
|
|
|
|
" 3、避免从内容流中读取\n",
|
|
|
|
|
" 例如,如果您打算阅读和记录请求和响应内容,请注意,它可能会对最终用户体验产生不利的副作用并导致错误。例如,请求内容可能在发送之前被消耗,或者巨大的响应内容可能最终被缓冲在内存中。此外,在 .NET 7 之前,访问标头不是线程安全的,可能会导致错误和意外行为。\n",
|
|
|
|
|
" 4、谨慎使用异步日志记录\n",
|
|
|
|
|
" 我们期望同步 IHttpClientLogger 接口适用于绝大多数自定义日志记录用例。出于性能原因,建议不要在日志记录中使用异步。但是,如果严格要求日志记录中的异步访问,您可以实现异步版本 IHttpClientAsyncLogger。它派生自 IHttpClientLogger,因此可以使用相同的 AddLogger API 进行注册。\n",
|
|
|
|
|
" 请注意,在这种情况下,还应该实现日志记录方法的同步对应项,特别是如果该实现是面向 .NET Standard 或 .NET 5+ 的库的一部分。同步对应项是从同步 HttpClient.Send 方法调用的;即使 .NET Standard 表面不包含它们,.NET Standard 库也可以在 .NET 5+ 应用程序中使用,因此最终用户可以访问同步 HttpClient.Send 方法。\n",
|
|
|
|
|
" 5、包装和不包装记录仪:\n",
|
|
|
|
|
" 当您添加记录器时,您可以显式设置wrapHandlersPipeline参数来指定记录器是否将被包装。默认不包装。\n",
|
|
|
|
|
" 在将重试处理程序添加到管道的情况下(例如 Polly 或某些重试的自定义实现),包装和不包装管道之间的区别最为显着。\n",
|
|
|
|
|
"*/\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"// 创建一个简单的控制台日志类\n",
|
|
|
|
|
"public class SimpleConsoleLogger : IHttpClientLogger\n",
|
|
|
|
|
"{\n",
|
|
|
|
|
" public object? LogRequestStart(HttpRequestMessage request)\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" return null;\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" public void LogRequestStop(object? ctx, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed)\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" Console.WriteLine($\"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - {(int)response.StatusCode} {response.StatusCode} in {elapsed.TotalMilliseconds}ms\");\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" public void LogRequestFailed(object? ctx, HttpRequestMessage request, HttpResponseMessage? response, Exception e, TimeSpan elapsed)\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" Console.WriteLine($\"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - Exception {e.GetType().FullName}: {e.Message}\");\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"}\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"//使用\n",
|
|
|
|
|
"{\n",
|
|
|
|
|
" var services = new ServiceCollection();\n",
|
|
|
|
|
" //1、先注册日志类\n",
|
|
|
|
|
" services.AddSingleton<SimpleConsoleLogger>();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" services\n",
|
|
|
|
|
" // 全局配置\n",
|
|
|
|
|
" .ConfigureHttpClientDefaults(options =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" })\n",
|
|
|
|
|
" // 配置到HttpClient\n",
|
|
|
|
|
" .AddHttpClient<HttpClient>(String.Empty,c =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" c.BaseAddress = new Uri(webApiBaseUrl);\n",
|
|
|
|
|
" })\n",
|
|
|
|
|
" //可选:取消默认日志记录\n",
|
|
|
|
|
" .RemoveAllLoggers()\n",
|
|
|
|
|
" //2、配置到HttpClient\n",
|
|
|
|
|
" .AddLogger<SimpleConsoleLogger>()\n",
|
|
|
|
|
" ;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
|
|
|
|
|
" var client = factory.CreateClient(String.Empty);\n",
|
|
|
|
|
" var response = await client.GetAsync(\"api/hello/index\");\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" response.EnsureSuccessStatusCode();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" var content = await response.Content.ReadAsStringAsync();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" Console.WriteLine($\"API 影响内容:{content}\");\n",
|
|
|
|
|
"}\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"// 使用上下文的日志类\n",
|
|
|
|
|
"public class RequestIdLogger : IHttpClientLogger\n",
|
|
|
|
|
"{\n",
|
|
|
|
|
" private readonly ILogger _log;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" public RequestIdLogger(ILogger<RequestIdLogger> log)\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" _log = log;\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" private static readonly Action<ILogger, Guid, string?, Exception?> _requestStart = LoggerMessage.Define<Guid, string?>\n",
|
|
|
|
|
" (\n",
|
|
|
|
|
" LogLevel.Information,\n",
|
|
|
|
|
" EventIds.RequestStart,\n",
|
|
|
|
|
" \"Request Id={RequestId} ({Host}) started\"\n",
|
|
|
|
|
" );\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" private static readonly Action<ILogger, Guid, double, Exception?> _requestStop = LoggerMessage.Define<Guid, double>\n",
|
|
|
|
|
" (\n",
|
|
|
|
|
" LogLevel.Information,\n",
|
|
|
|
|
" EventIds.RequestStop,\n",
|
|
|
|
|
" \"Request Id={RequestId} succeeded in {elapsed}ms\"\n",
|
|
|
|
|
" );\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" private static readonly Action<ILogger, Guid, Exception?> _requestFailed = LoggerMessage.Define<Guid>\n",
|
|
|
|
|
" (\n",
|
|
|
|
|
" LogLevel.Error,\n",
|
|
|
|
|
" EventIds.RequestFailed,\n",
|
|
|
|
|
" \"Request Id={RequestId} FAILED\"\n",
|
|
|
|
|
" );\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" public object? LogRequestStart(HttpRequestMessage request)\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" var ctx = new Context(Guid.NewGuid());\n",
|
|
|
|
|
" _requestStart(_log, ctx.RequestId, request.RequestUri?.Host, null);\n",
|
|
|
|
|
" return ctx;\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" public void LogRequestStop(object? ctx, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed)\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" _requestStop(_log, ((Context)ctx!).RequestId, elapsed.TotalMilliseconds, null);\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" public void LogRequestFailed(object? ctx, HttpRequestMessage request, HttpResponseMessage? response, Exception e, TimeSpan elapsed)\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" _requestFailed(_log, ((Context)ctx!).RequestId, null);\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" public static class EventIds\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" public static readonly EventId RequestStart = new(1, \"RequestStart\");\n",
|
|
|
|
|
" public static readonly EventId RequestStop = new(2, \"RequestStop\");\n",
|
|
|
|
|
" public static readonly EventId RequestFailed = new(3, \"RequestFailed\");\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" record Context(Guid RequestId);\n",
|
|
|
|
|
"}\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"//使用\n",
|
|
|
|
|
"{\n",
|
|
|
|
|
" var services = new ServiceCollection();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" services.AddLogging(config =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" config.SetMinimumLevel(LogLevel.Trace);\n",
|
|
|
|
|
" config.AddConsole();\n",
|
|
|
|
|
" });\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" //1、先注册日志类\n",
|
|
|
|
|
" services.AddSingleton<RequestIdLogger>();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" services\n",
|
|
|
|
|
" // 全局配置\n",
|
|
|
|
|
" .ConfigureHttpClientDefaults(options =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" })\n",
|
|
|
|
|
" // 配置到HttpClient\n",
|
|
|
|
|
" .AddHttpClient<HttpClient>(String.Empty,c =>\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" c.BaseAddress = new Uri(webApiBaseUrl);\n",
|
|
|
|
|
" })\n",
|
|
|
|
|
" //可选:取消默认日志记录\n",
|
|
|
|
|
" .RemoveAllLoggers()\n",
|
|
|
|
|
" //2、配置到HttpClient\n",
|
|
|
|
|
" .AddLogger<RequestIdLogger>()\n",
|
|
|
|
|
" ;\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
|
|
|
|
|
" var client = factory.CreateClient(String.Empty);\n",
|
|
|
|
|
" var response = await client.GetAsync(\"api/hello/get\");\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" response.EnsureSuccessStatusCode();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" var content = await response.Content.ReadAsStringAsync();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" Console.WriteLine($\"API 影响内容:{content}\");\n",
|
|
|
|
|
"}"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
@ -1406,6 +1748,149 @@
|
|
|
|
|
"## 7 工厂 + Polly V8"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"### 基础应用"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"工厂可以和Polly(v8)一起使用:\n",
|
|
|
|
|
"1. 引用 Polly v8 和 Microsoft.Extensions.Http.Polly 包\n",
|
|
|
|
|
"2. 配置命名客户端\n",
|
|
|
|
|
"3. 使用 AddTransientHttpErrorPolicy 配置策略"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
"metadata": {
|
|
|
|
|
"dotnet_interactive": {
|
|
|
|
|
"language": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"polyglot_notebook": {
|
|
|
|
|
"kernelName": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"vscode": {
|
|
|
|
|
"languageId": "polyglot-notebook"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"outputs": [],
|
|
|
|
|
"source": [
|
|
|
|
|
"//便捷应用:AddTransientHttpErrorPolicy() 方法,添加常用瞬时错误重试策略\n",
|
|
|
|
|
"{\n",
|
|
|
|
|
" var services = new ServiceCollection();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" services.AddHttpClient(\"ClientA\")\n",
|
|
|
|
|
" .ConfigureHttpClient(client => \n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
|
|
|
|
|
" })\n",
|
|
|
|
|
" .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync\n",
|
|
|
|
|
" (\n",
|
|
|
|
|
" new[]\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" TimeSpan.FromSeconds(1),\n",
|
|
|
|
|
" TimeSpan.FromSeconds(2),\n",
|
|
|
|
|
" TimeSpan.FromSeconds(4)\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
" ))\n",
|
|
|
|
|
" .AddTransientHttpErrorPolicy(builder => builder.Fallback<string>());\n",
|
|
|
|
|
" \n",
|
|
|
|
|
" var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
|
|
|
|
|
" var clientA = factory.CreateClient(\"ClientA\");\n",
|
|
|
|
|
" var response = await clientA.GetAsync(\"/api/polly8/RandomException\");\n",
|
|
|
|
|
" response.EnsureSuccessStatusCode();\n",
|
|
|
|
|
" var content = await response.Content.ReadAsStringAsync();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" Console.WriteLine($\"响应内容:{content}\");\n",
|
|
|
|
|
"}"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"### 使用通过传统 Polly 语法配置的任何策略"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
"metadata": {
|
|
|
|
|
"dotnet_interactive": {
|
|
|
|
|
"language": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"polyglot_notebook": {
|
|
|
|
|
"kernelName": "csharp"
|
|
|
|
|
},
|
|
|
|
|
"vscode": {
|
|
|
|
|
"languageId": "polyglot-notebook"
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"outputs": [],
|
|
|
|
|
"source": [
|
|
|
|
|
"{\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" var policy = Policy.Handle<Exception>()\n",
|
|
|
|
|
" .WaitAndRetryAsync\n",
|
|
|
|
|
" (\n",
|
|
|
|
|
" new[]\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" TimeSpan.FromSeconds(1),\n",
|
|
|
|
|
" TimeSpan.FromSeconds(2),\n",
|
|
|
|
|
" TimeSpan.FromSeconds(4)\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
" );\n",
|
|
|
|
|
" var services = new ServiceCollection();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" services.AddHttpClient(\"ClientA\")\n",
|
|
|
|
|
" .ConfigureHttpClient(client => \n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
|
|
|
|
|
" })\n",
|
|
|
|
|
" .AddTransientHttpErrorPolicy\n",
|
|
|
|
|
" (\n",
|
|
|
|
|
" builder => builder.WaitAndRetryAsync\n",
|
|
|
|
|
" (\n",
|
|
|
|
|
" new[]\n",
|
|
|
|
|
" {\n",
|
|
|
|
|
" TimeSpan.FromSeconds(1),\n",
|
|
|
|
|
" TimeSpan.FromSeconds(2),\n",
|
|
|
|
|
" TimeSpan.FromSeconds(4)\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
" )\n",
|
|
|
|
|
" )\n",
|
|
|
|
|
" .AddPolicyHandler(policy);\n",
|
|
|
|
|
" \n",
|
|
|
|
|
" var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
|
|
|
|
|
" var clientA = factory.CreateClient(\"ClientA\");\n",
|
|
|
|
|
" var response = await clientA.GetAsync(\"/api/polly8/RandomException\");\n",
|
|
|
|
|
" response.EnsureSuccessStatusCode();\n",
|
|
|
|
|
" var content = await response.Content.ReadAsStringAsync();\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" Console.WriteLine($\"响应内容:{content}\");\n",
|
|
|
|
|
"}"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"### 应用多个策略"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"### 动态选择策略"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"metadata": {},
|
|
|
|
|