From 781097f325ee4a293d7e4d07fd2e5cd393212703 Mon Sep 17 00:00:00 2001 From: wangsl Date: Sat, 8 May 2021 16:29:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96:=201.=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=85=A8=E7=AB=99api=20=E6=96=87=E6=9C=AC=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=AE=B0=E5=BD=95,=E5=90=8E=E6=9C=9F=E5=8F=AF=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=E5=88=B0ES?= =?UTF-8?q?=E7=AD=89=E6=90=9C=E7=B4=A2=E5=99=A8=E4=B8=AD=202.=E7=A6=81?= =?UTF-8?q?=E7=94=A8.net=E8=87=AA=E5=8A=A8=E6=A8=A1=E6=80=81=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=20,=20=E9=87=87=E7=94=A8=E7=B3=BB=E7=BB=9F=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E5=BC=82=E5=B8=B8=E6=8D=95=E8=8E=B7=E6=9C=BA=E5=88=B6?= =?UTF-8?q?,=E5=A2=9E=E5=8A=A0=E5=8F=8B=E5=A5=BD=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Infrastructure/Infrastructure.csproj | 1 + .../IApplicationBuilderExtension.cs | 20 +++ .../RequestResponseLoggingMiddleware.cs | 150 ++++++++++++++++++ OpenAuth.WebApi/Startup.cs | 25 ++- 4 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 Infrastructure/Middleware/IApplicationBuilderExtension.cs create mode 100644 Infrastructure/Middleware/RequestResponseLoggingMiddleware.cs diff --git a/Infrastructure/Infrastructure.csproj b/Infrastructure/Infrastructure.csproj index d7e43deb..8e5ffc46 100644 --- a/Infrastructure/Infrastructure.csproj +++ b/Infrastructure/Infrastructure.csproj @@ -12,6 +12,7 @@ + diff --git a/Infrastructure/Middleware/IApplicationBuilderExtension.cs b/Infrastructure/Middleware/IApplicationBuilderExtension.cs new file mode 100644 index 00000000..cc98115d --- /dev/null +++ b/Infrastructure/Middleware/IApplicationBuilderExtension.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Builder; + +namespace Infrastructure.Middleware +{ + /// + /// + /// + public static class ApplicationBuilderExtension + { + /// + /// 注入日志中间件 + /// + /// + /// + public static IApplicationBuilder UseLogMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/Infrastructure/Middleware/RequestResponseLoggingMiddleware.cs b/Infrastructure/Middleware/RequestResponseLoggingMiddleware.cs new file mode 100644 index 00000000..cdaa9163 --- /dev/null +++ b/Infrastructure/Middleware/RequestResponseLoggingMiddleware.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Infrastructure.Middleware +{ + /// + /// 请求与返回中间件 + /// + public class RequestResponseLoggingMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _log; + + /// + /// + /// + public RequestResponseLoggingMiddleware(RequestDelegate next, ILogger log) + { + _next = next; + _log = log; + } + + /// + /// + /// + /// + /// + public async Task Invoke(HttpContext context) + { + #region 这里可以加入正则验证context.Path。 过滤不需要记录日志的api + + var path = context.Request.Path.ToString().ToLower(); + + if (path.Contains("/index") || path.Contains("/check") || + path.Contains("/swagger") || path.Contains("/getsysdatas") || path.Contains("/load")) + { + await CatchNext(context); + return; + } + + #endregion + + // 启用耗时 日志记录 + var stopwatch = new Stopwatch(); + stopwatch.Start(); + var logData = new Dictionary(); + var request = context.Request; + logData.Add("request.url", request.Path.ToString()); + logData.Add("request.headers", + request.Headers.ToDictionary(x => x.Key, v => string.Join(";", v.Value.ToList()))); + logData.Add("request.method", request.Method); + logData.Add("request.executeStartTime", DateTimeOffset.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); + //追踪实别器 + logData.Add("traceIdentifier", context.TraceIdentifier); + // 获取请求body内容 + if (request.Method.ToLower().Equals("post")) + { + // 启用倒带功能,就可以让 Request.Body 可以再次读取 + request.EnableBuffering(); + // 文件上传 记录文件信息 + if (path.Contains("/upload")) + { + var content = string.Join(",", request.Form.Files.Select(item => item.FileName)); + logData.Add("request.body", $"收到上传文件:{content}"); + } + else + { + var sr = new StreamReader(request.Body, Encoding.UTF8); + //string content = sr.ReadToEnd(); //.Net Core 3.0 默认不再支持 + var content = sr.ReadToEndAsync().Result; + logData.Add("request.body", content); + request.Body.Position = 0; + } + } + else if (request.Method.ToLower().Equals("get")) + { + logData.Add("request.body", request.QueryString.Value); + } + + // 获取Response.Body内容 + var originalBodyStream = context.Response.Body; + using (var responseBody = new MemoryStream()) + { + context.Response.Body = responseBody; + await CatchNext(context); + if (!logData.ContainsKey("response.body")) + { + logData.Add("response.body", await GetResponse(context.Response)); + } + + logData.Add("response.executeEndTime", DateTimeOffset.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")); + logData.Add("response.statusCode", context.Response.StatusCode); + await responseBody.CopyToAsync(originalBodyStream); + } + + // 响应完成记录时间和存入日志 + context.Response.OnCompleted(() => + { + try + { + stopwatch.Stop(); + logData.Add("elapsedTime", stopwatch.ElapsedMilliseconds + "ms"); + var json = JsonHelper.Instance.Serialize(logData); + _log.LogInformation(json); + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + }); + } + + private async Task CatchNext(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + _log.LogError(ex, "系统错误日志,管道捕获"); + context.Response.StatusCode = 200; + context.Response.ContentType = "application/json; charset=utf-8"; + var result = new { code = 500, message = ex.Message ?? "系统错误,请稍后再试" }; + await context.Response.WriteAsync(JsonHelper.Instance.Serialize(result)); + } + } + + /// + /// 获取响应内容 + /// + /// + /// + private static async Task GetResponse(HttpResponse response) + { + response.Body.Seek(0, SeekOrigin.Begin); + var text = await new StreamReader(response.Body).ReadToEndAsync(); + response.Body.Seek(0, SeekOrigin.Begin); + return text; + } + } +} \ No newline at end of file diff --git a/OpenAuth.WebApi/Startup.cs b/OpenAuth.WebApi/Startup.cs index ea8d4a2f..79a73ac5 100644 --- a/OpenAuth.WebApi/Startup.cs +++ b/OpenAuth.WebApi/Startup.cs @@ -7,6 +7,7 @@ using Autofac; using IdentityServer4.AccessTokenValidation; using Infrastructure; using Infrastructure.Extensions.AutofacManager; +using Infrastructure.Middleware; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; @@ -110,14 +111,19 @@ namespace OpenAuth.WebApi } }); services.Configure(Configuration.GetSection("AppSetting")); - services.AddControllers(option => { option.Filters.Add(); }).AddNewtonsoftJson(options => - { - //忽略循环引用 - options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - //不使用驼峰样式的key - //options.SerializerSettings.ContractResolver = new DefaultContractResolver(); - options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; - }); + services.AddControllers(option => { option.Filters.Add(); }) + .ConfigureApiBehaviorOptions(options => + { + // 禁用自动模态验证 + options.SuppressModelStateInvalidFilter = true; + }).AddNewtonsoftJson(options => + { + //忽略循环引用 + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + //不使用驼峰样式的key + //options.SerializerSettings.ContractResolver = new DefaultContractResolver(); + options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; + }); services.AddMemoryCache(); services.AddCors(); // todo:如果正式 环境请用下面的方式限制随意访问跨域 @@ -198,6 +204,9 @@ namespace OpenAuth.WebApi app.UseRouting(); app.UseAuthentication(); + // 启用日志追踪记录和异常友好提示 + app.UseLogMiddleware(); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); //配置ServiceProvider -- Gitee