tnblog
首页
视频
资源
登录

.NetCore3.1及以上异常和响应中间件服务一条龙服务

2327人阅读 2024/8/30 20:29 总访问:5426 评论:0 收藏:0 手机
分类: .NET6

一、为什么要有这篇文章:

在.Net Core3.1及以上的开发操作中,我们通常对日志记录及响应记录,还有一些其他的事物性记录需要严苛的监控或者说本系统内部的数据流向追踪,异常查找很是繁琐。后来有了日志组件Nlog,Serilog,Log4Net等日志记录组件。使用起来也很方便,但是缺少对数据流向的追踪。

为了解决数据流向追踪,我们研究了下中间(Middleware),中间件的好处就是对代码无侵入,只用注入一次,则会全局使用,相对于拦截器来说更加方便轻量。

本文章以Serilog 配合中间件实现全流程的追踪,我这种菜鸟都能很快的去定位错误。之所以使用Serilog是因为它的自定义性和记录方式的多样性,很灵活。只需要配置简单的json文件就可以实现。

二、你需要准备什么?环境是什么样的?

众所周知,在使用别人的东西前得引入别人的包,这里我们需要用到的Nuget包包含以下:
1.Newtonsoft.Json 序列化数据参数用
2.Serilog.AspNetCore 这是配合.Net Core3.1及以上的Serilog主库,方便程序注入
3.Serilog.Sinks.File
4.Serilog.Expressions
5.Serilog.Sinks.Console
6.Serilog.Sinks.Elasticsearch 等等库。。

读到上面这个,有的同学或者朋友就说这么多库,白瞎了,不用了。其实也完全不必苦恼。可卸库是可以被动态加载的。期望自己用什么库什么样的日志写入方式,就可以依次精简,这是Serilog比较灵活的地方,一切基于配置。跑题了,这是异常请求响应中间件的一条龙服务的。为毛要介绍这些呢?废话,查日志不需要地点?他给你生啊。

三、开始着手,你需要了解哪些?
1.中间件是干啥的?这里就不介绍了,去看官网文档。
2.一切基于配置和注入,我们需要一个灵活的json配置文件去配置Serilog;写一个比较好用的请求响应异常中间件。就这么两件事
3.总结:把大象放进冰箱。冰箱的原理和大象的体积这个自己去查阅。

四、怎么做?
1.配置Serilog的文件
2.注入Serilog及配置文件
3.写一个中间件。就这么简单。
五、看图说话
① 注入必要小组件(Serilog、Middleware)

  1. ②配置json文件
  2. ![](https://img.tnblog.net/arcimg/17764029413/97b77ca1a75f452a974accd87f016d66.jpeg)
  3. 写一个中间件
  1. using DemoApp.Exceptions;
  2. using Microsoft.AspNetCore.Http.Extensions;
  3. using Newtonsoft.Json;
  4. using System.Text;
  5. namespace DemoApp.Middlewares
  6. {
  7. /// <summary>
  8. /// 全局异常处理中间件
  9. /// </summary>
  10. public class ExceptionHandlingMiddleware
  11. {
  12. private readonly IHttpContextAccessor _httpContextAccessor;
  13. private readonly ILogger<ExceptionHandlingMiddleware> _logger;
  14. private readonly RequestDelegate _next;
  15. /// <summary>
  16. /// 构造函数
  17. /// </summary>
  18. /// <param name="next"></param>
  19. /// <param name="logger"></param>
  20. public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger, IHttpContextAccessor httpContextAccessor)
  21. {
  22. _next = next;
  23. _logger = logger;
  24. _httpContextAccessor = httpContextAccessor;
  25. }
  26. public async Task Invoke(HttpContext context)
  27. {
  28. var flag = Guid.NewGuid().ToString("n");
  29. var request = context.Request;
  30. var response = context.Response;
  31. //确保请求body可以被重复读取
  32. request.EnableBuffering();
  33. //记录请求标记
  34. request.Headers["demoapp-request-flag"] = flag;
  35. //记录请求日志
  36. await LogRequest(request, flag);
  37. //获取Response.Body内容
  38. var originalBodyStream = response.Body;
  39. var resBodyMS = new MemoryStream();
  40. response.Body = resBodyMS;
  41. try
  42. {
  43. await _next(context);
  44. }
  45. catch (Exception e)
  46. {
  47. await HandleExceptionAsync(request, response, e, flag);
  48. }
  49. finally
  50. {
  51. //记录响应日志
  52. await LogResponse(response, flag);
  53. await resBodyMS.CopyToAsync(originalBodyStream);
  54. await resBodyMS.DisposeAsync();
  55. }
  56. }
  57. /// <summary>
  58. /// 处理异常信息
  59. /// </summary>
  60. /// <param name="request"></param>
  61. /// <param name="response"></param>
  62. /// <param name="e"></param>
  63. /// <param name="flag"></param>
  64. /// <returns></returns>
  65. private async Task HandleExceptionAsync(HttpRequest request, HttpResponse response, Exception e, string flag)
  66. {
  67. response.StatusCode = 200;
  68. var returnMsg = "服务开了个小差,请稍后再试";
  69. if (e is CustomException)
  70. {
  71. returnMsg = e.Message;
  72. }
  73. var result = new { code = 500, status = false, message = returnMsg, uuid = flag };
  74. var resBodyJson = JsonConvert.SerializeObject(result);
  75. var url = request.GetDisplayUrl();
  76. try
  77. {
  78. var contentType = request.ContentType?.ToLower() ?? string.Empty;
  79. if (contentType.StartsWith("application/json"))
  80. {
  81. request.Body.Position = 0;
  82. var stream = new StreamReader(request.Body);
  83. var body = await stream.ReadToEndAsync();
  84. request.Body.Position = 0;
  85. _logger.LogError($"[{flag}] 系统发生异常。{Environment.NewLine}Error Message:{e.Message}{Environment.NewLine}Path:{url}{Environment.NewLine}Body:{body}{Environment.NewLine}StackTrace:{e.StackTrace}");
  86. }
  87. else
  88. {
  89. _logger.LogError($"[{flag}] 系统发生异常。{Environment.NewLine}Error Message:{e.Message}{Environment.NewLine}Path:{url}{Environment.NewLine}StackTrace:{e.StackTrace}");
  90. }
  91. }
  92. catch (Exception)
  93. {
  94. _logger.LogError($"[{flag}] 系统发生异常。{Environment.NewLine}Error Message:{e.Message}{Environment.NewLine}Path:{url}{Environment.NewLine}StackTrace:{e.StackTrace}");
  95. }
  96. var resBodyByte = Encoding.Default.GetBytes(resBodyJson);
  97. response.ContentType = "application/json; charset=utf-8";
  98. await response.Body.WriteAsync(resBodyByte.AsMemory(0, resBodyByte.Length));
  99. }
  100. /// <summary>
  101. /// 请求日志
  102. /// </summary>
  103. /// <param name="request"></param>
  104. /// <param name="flag"></param>
  105. /// <returns></returns>
  106. private async Task LogRequest(HttpRequest request, string flag)
  107. {
  108. var ipaddress = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.MapToIPv4().ToString();
  109. var contentType = request.ContentType?.ToLower() ?? string.Empty;
  110. if (contentType.StartsWith("application/json"))
  111. {
  112. request.Body.Position = 0;
  113. var reader = new StreamReader(request.Body, Encoding.UTF8);
  114. var body = await reader.ReadToEndAsync();
  115. request.Body.Position = 0;
  116. _logger.LogInformation($"[{flag}] 请求内容:{ipaddress} [{request.Method}] {request.GetDisplayUrl()}{Environment.NewLine}Body:{body}");
  117. }
  118. else
  119. {
  120. _logger.LogInformation($"[{flag}] 请求内容:{ipaddress} [{request.Method}] {request.GetDisplayUrl()}");
  121. }
  122. }
  123. /// <summary>
  124. /// 响应日志
  125. /// </summary>
  126. /// <param name="response"></param>
  127. /// <param name="flag"></param>
  128. /// <returns></returns>
  129. private async Task LogResponse(HttpResponse response, string flag)
  130. {
  131. response.Body.Position = 0;
  132. var resBody = await new StreamReader(response.Body).ReadToEndAsync();
  133. //重置请求体流的位置,以便下一个中间件可以读取它
  134. response.Body.Position = 0;
  135. _logger.LogInformation($"[{flag}] 响应内容:{response.StatusCode}{Environment.NewLine}Body:{resBody}");
  136. }
  137. }
  138. /// <summary>
  139. /// 中间件注入方法
  140. /// </summary>
  141. public static class ExceptionHandlingExtensions
  142. {
  143. public static IApplicationBuilder UseExceptionHandling(this IApplicationBuilder builder)
  144. {
  145. return builder.UseMiddleware<ExceptionHandlingMiddleware>();
  146. }
  147. }
  148. }
  1. 以上基本上完成了请求响应异常中间件一条龙服务。?Serilog json配置可以去他们官网看我认为比较方便的方式还是写入es和文本文件。ES本地化部署了可以减少很多运维上的毛病

六、最兴奋的时候到了,看看…结果

  1. 为什么说是一条龙服务请看图,返回结果的uuid可以看到它的到生命周期,从一而终
  2. ![](https://img.tnblog.net/arcimg/17764029413/3a90278d7f63424aac22a192e51e72c3.jpeg)
  3. ![](https://img.tnblog.net/arcimg/17764029413/ac670569d1244aaf9e5b03efde52b067.jpeg)

以上是所有日志记录,我还是觉得es更方便,这样就可以一步到位看到所有的生命周期了。

代码里面有私藏,可能少了个类,是个自定义的异常

自定义异常类,写义务异常也可以这样做

  1. /// <summary>
  2. /// 自定义业务异常
  3. /// </summary>
  4. public class CustomException : Exception
  5. {
  6. public CustomException(string message) : base(message)
  7. {
  8. }
  9. public CustomException(string message, Exception ex) : base(message, ex)
  10. {
  11. }
  12. }

好了废话结束,只是教会你们怎么用。并没有告诉你为啥要这么用

评价

net core 使用 EF Code First

下面这些内容很老了看这篇:https://www.tnblog.net/aojiancc2/article/details/5365 项目使用多层,把数据库访问...

.net mvc分部页,.net core分部页

.net分部页的三种方式第一种:@Html.Partial(&quot;_分部页&quot;)第二种:@{ Html.RenderPartial(&quot;分部页&quot;);}...

StackExchange.Redis操作redis(net core支持)

官方git开源地址https://github.com/StackExchange/StackExchange.Redis官方文档在docs里边都是官方的文档通过nuget命令下...

.net core 使用session

tip:net core 2.2后可以直接启用session了,不用在自己添加一次session依赖,本身就添加了使用nuget添加引用Microsoft.AspN...

通俗易懂,什么是.net?什么是.net Framework?什么是.net core?

朋友圈@蓝羽 看到一篇文章写的太详细太通俗了,搬过来细细看完,保证你对.NET有个新的认识理解原文地址:https://www.cnblo...

asp.net core2.0 依赖注入 AddTransient与AddScoped的区别

asp.net core主要提供了三种依赖注入的方式其中AddTransient与AddSingleton比较好区别AddTransient瞬时模式:每次都获取一...

.net core 使用 Kestrel

Kestrel介绍 Kestrel是一个基于libuv的跨平台web服务器 在.net core项目中就可以不一定要发布在iis下面了Kestrel体验可以使...

net core中使用cookie

net core中可以使用传统的cookie也可以使用加密的cookieNET CORE中使用传统cookie设置:HttpContext.Response.Cookies.Appe...

net core项目结构简单分析

一:wwwrootwwwroot用于存放网站的静态资源,例如css,js,图片与相关的前端插件等lib主要是第三方的插件,例如微软默认引用...

net core使用EF之DB First

一.新建一个.net core的MVC项目新建好项目后,不能像以前一样直接在新建项中添加ef了,需要用命令在添加ef的依赖二.使用Nug...

.net core使用requestresponse下载文件下载excel等

使用request获取内容net core中request没有直接的索引方法,需要点里边的Query,或者formstringbase64=Request.Form[&quot;f...

iframe自适应高度与配合net core使用

去掉iframe边框frameborder=&quot;0&quot;去掉滚动条scrolling=&quot;no&quot;iframe 自适应高度如果内容是固定的,那么就...

net core启动报错Unable to configure HTTPS endpoint. No server certificate was specified

这是因为net core2.1默认使用的https,如果使用Kestrel web服务器的话没有安装证书就会报这个错其实仔细看他的错误提示,其...

net core中使用url编码与解码操作

net core中暂时还没有以前asp.net与mvc中的server对象。获取url的编码与解码操作不能使用以前的server对象来获取。使用的是...

下载net core

官方下载地址:https://dotnet.microsoft.com/download 进来之后就可以看到最新的下载版本可以直接点击下载,也可以下载其...
感谢挫折
排名
158
文章
3
粉丝
0
评论
1
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术