旁白音:本文是不定时更新的.net core,当前主线任务的Nuxt+VueAdmin教程的 nuxt.js 之 tibug项目已上线,你们能够玩一玩:http://123.206.33.109:7090,具体的部署教程会在下周发表。css
哈喽你们周五好呀,今天是一个不定时更新的文章,是很简单的一篇文章,你们应该都能看懂,虽然很简单,可是我感受很实用,主要包含了两个内容,一个是对AOP编程的进一步的理解(其中还有和过滤器比较),第二个就是一个简单的小插件——记录接口的调用时间调用状况,也就是很简单的性能记录,这个时候你确定不要和 Metricss+influxdb+grafana 做比较了,它们功能虽然很大,可是用起来笨重,我们这种入门级别的小项目暂时先不用这个了,说到这里,昨天有小伙伴留言,说要不要在项目中增长消息队列 ReditMQ ,我正在考虑中,看看如何使用,好啦废话很少说,先来几个小问题,热热身:html
一、在平时开发的时候,你们是如何记录当前 <接口/方法> 的调用时间的?// 手动写起止时间相减 git
二、如何对异常进行处理的,这里有 <api层的异常>,以及 <service 层的异常> 的?// 加 try catch github
三、如何快速的找到当前接口的错误信息?// 查看日志记录redis
你们先带着这几个问题本身想想,是否是和个人绿色解决方案一致,要是有更好的办法也帮忙提给我,不胜感激!sql
不过!今天确定不会用这些解决方案的,今天玩儿一个新花样,应该也会有人玩儿过,别着急,我们往下看。数据库
先来个实现图,预热下,之后调试接口,性能+错误信息一目了然:编程
时间是很快,我也已经从第一个专题,写到了第三个专题,还记得当时第一次写AOP的时候《框架之十 || AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存》,不少不少的小伙伴不是很明白,也不知道应用场景到底在哪里,彻底不了解落地几何,如今也能在群里,时不时看到有小伙伴用到AOP编程,感受很开心,至少帮到了一些人了,这就是最大的欣慰!那我们在Blog.core 项目中,到底如何运用了 AOP 呢?json
这一块相信已经有小伙伴知道,而且用到了,我也是在不少地方使用到了,好比在基于策略的权限认证文章中《JWT完美实现权限与接口的动态分配》,经过了对角色模块的切面缓存,很好的实现了快速受权的目的,不只代码整洁,并且功能也实现了:api
// 将最新的角色和接口列表更新 var data = await _roleModulePermissionServices.GetRoleModule(); var list = (from item in data where item.IsDeleted == false orderby item.Id select new PermissionItem { Url = item.Module?.LinkUrl, Role = item.Role?.Name, }).ToList();
// 经过AOP缓存获取角色模块信息 [Caching(AbsoluteExpiration = 10)] public async Task<List<RoleModulePermission>> GetRoleModule() { var roleModulePermissions = await dal.Query(a => a.IsDeleted == false); //....... return roleModulePermissions; }
另外还有在当前第三系列教程中,获取Bug信息的时候,也用到了切面缓存(因此,若是你用了文章开头的 tibug 系统,提交了一个 bug,可是没有马上显示出来,就是这个缘由,10分钟缓存):
综上所言,通过屡次使用,我我的表示真的是一个神器,在彻底不露痕迹的状况下,实现了缓存,是否是很好用?
这个时候你会问,单独为了缓存的话,AOP不是很透彻,那还有其余的用处么?请往下看。前提是上边的这种基于AOP的缓存你要看懂了,先在脑子里回顾下总体流程。
上边我们说了缓存,我我的感受还有一个很大的用户就是切面日志,这个具体的内容之前已经说过了,这里就很少说了,想了解原理和详细说明,请看《AOP面向切面编程浅解析:简单日志记录 + 服务切面缓存》,这里只是复习下流程:
public void Intercept(IInvocation invocation) { //记录被拦截方法信息的日志信息 var dataIntercept = $"{DateTime.Now} " + $"当前执行方法:{ invocation.Method.Name} "; //在被拦截的方法执行完毕后 继续执行当前方法,注意是被拦截的是异步的 invocation.Proceed(); dataIntercept += ($"方法执行完毕,返回结果:{invocation.ReturnValue}"); #region 输出到日志文件 #endregion }
只须要咱们 ServiceConfigure 中开启服务之后,就能够在指定文件中,看到每次的切面接口调用状况,注意这里是 service 层的,不是 controller 的日志,这个时候就是 AOP 和过滤器 Filter 的区别了:
一、Filter过滤器是基于当前Http请求的,也就是接口层面的,颗粒度比较大;
二、而AOP是基于服务切面的,是 Service 层的请求,颗粒度比较小;
那既然AOP能记录调用日志,能捕获异常么,上次群里有一个小伙伴问到了,这里就点名表扬了,挺棒的,能本身思考,那如何捕获呢?
在平时的开发中,咱们常常会遇到各类 Bug ,在 controller 里的错误就不说了,编译的时候基本都能看出来,可是不少 service 层的错误,真是难找,好比我故意写的这个bug,一个不重要的方法:
public void NoImportantMethod() { int a = 1; int b = 0; int c = a / b; }
这个错误是如何捕获的,你们还记得么,就是咱们用全局变量异常过滤器 Filter 捕获的《三十五║ 完美实现全局异常日志记录》:
咱们平时可能会在 api 中使用这样的service层方法,而后下边也会有其余的一些方法,由于这个方法不重要,好比仅仅是阅读数量+1,那咱们就不能让当前接口崩了
public async Task<MessageModel<Response>> Get() { var data = new MessageModel<Response>(); // 一个不重要的方法 _advertisementServices.NoImportantMethod(); // 核心功能:说爱你 Love love = New Love(); love.SayLoveU(); return data; }
这个咱们咱们会在 NoImportantMethod() 这里报异常,直接崩溃出去,你感受这样的设计合理么?咱们不能由于一个不重要的动做就不说核心的 我爱你 了吧😂,因此咱们通常就会使用 try catch 的方法,把咱们认为会出错的地方包括住,好比这样:
public async Task<MessageModel<Response>> Get() { var data = new MessageModel<Response>(); try{ // 一个不重要的方法 _advertisementServices.NoImportantMethod(); }catch{} // 核心功能:说爱你 Love love = New Love(); love.SayLoveU(); return data; }
这样虽然问题解决了,可是总感受很难受,太丑了,会致使咱们全部的接口都写满了这个 try try try,哦no!那咋办么,别慌,还记得咱们的AOP记录日志么,既然能记录日志,照样能记录异常!
没错就是这里:
try { invocation.Proceed(); } catch (Exception e) {//执行的 service 中,捕获异常 dataIntercept += ($"方法执行中出现异常:{e.Message + e.InnerException}"); }
咱们看看效果:
而后很顺利的捕获到了异常,而且主程序也走了下去,咱们就能够找到咱们的日志文件,而后看看错误:
20190118 11:40:50 当前执行方法:ReturnExp 参数是: 方法执行中出现异常:Attempted to divide by zero.方法执行完毕,返回结果:
到这里!咱们的文章开头提问的第二个方法(如何对异常进行处理的)已经顺利解决!我如今在公司的项目中就是用的这种切面方法,来进行处理的,把那些不重要的方法给走下去,虽然可能会出现,提交了订单,可是一直提交不上的问题,可是至少保证不会让页面崩了的尴尬状况。
到了这里不知道你是否满意了呢,别慌,刚刚咱们是如何来查看错误的?是经过找日志是吧,而后还须要对应的时间呀,接口方法呀啥的,若是日志信息不少,那茫茫bug,如何找到你心仪的那一个呢,这样,文章开头的第三问就出来了,这里先不说第三个,先说下第一个问题(如何记录当前 <接口/方法> 的调用时间)。请往下看。
A:咱们平时在开发接口的时候,总想看看一个接口到底执行了多久,甚至想看看某一个service 方法执行了多长时间,这个时候,我之前都是经过 断点调试的方法,来看执行时间,好比这样:
(这里说下我用的 Sqlsugar 的批量添加,100条,0.4s,速度还行,由于我中间还有一个if操做,因此时间应该在0.3左右,给凯旋兄打给广告😀)
B:或者直接就是写个起止时间方法,来判断下,好比这样(图片来自群管理DX):
可是咱们有那么多的接口,那么多的方法,总不能都这么 Debug 调试吧,也不能都写个方法吧,
C:咱们可使用AOP来进行每次方法的时间记录
就算是用上边的 AOP 写了一个,仍是得查看日志不是,并且也只能是service层的,那api层还得写过滤器了,这个时候,有一个插件能够帮咱们省去查看日志的诟病,就是它——MiniProfiler,这个具体的功能呢,我就不说了,你们自行百度便可,就是一个小插件,我这里就说下如何使用:
使用方法很简单,首先咱们须要引入nuget包:
Install-Package MiniProfiler.AspNetCore.Mvc
而后,在startup.cs 中配置服务ConfigureServices:
services.AddMiniProfiler(options => { options.RouteBasePath = "/profiler";//注意这个路径要和下边 index.html 脚本配置中的一致, (options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(10); }
最后,调用下中间件便可:
app.UseMiniProfiler();
当前这个时候还不能使用,咱们还须要在 Swagger 中进行配置,以便它能在 swagger 中使用。
上边咱们在配置中已经启动了服务,接下来就须要设置如何在 swagger 中展现了,这个时候咱们就须要自定义咱们的swagger主页了,之前咱们是用的默认的index.html,如今我们须要自定义一个:
从官网 Github 上,下载最新的 index.html:https://github.com/swagger-api/swagger-ui/blob/master/dist/index.html(官方的应该是这个,和个人有一丢丢不同,应该也能够用)
而后添加到项目中,我是放到了根路径了:
这个时候记得要把这个文件设置成嵌入资源的类型:
接下来,在 Index.html 文件中,增长配置脚本(我是在顶部写的,Head应该也能够,其余的没实验):
<script async="async" id="mini-profiler" src="/profiler/includes.min.js?v=4.0.138+gcc91adf599" data-version="4.0.138+gcc91adf599" data-path="/profiler/" data-current-id="4ec7c742-49d4-4eaf-8281-3c1e0efa748a" data-ids="" data-position="Left" data-authorized="true" data-max-traces="15" data-toggle-shortcut="Alt+P" data-trivial-milliseconds="2.0" data-ignored-duplicate-execute-types="Open,OpenAsync,Close,CloseAsync"> </script> <!-- HTML for static distribution bundle build --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> ........ ........
具体的参数请自行研究吧,基本都能看懂,好比版本,/profiler的路径,position的位置显示(我是左边),authorized的是否权限,max-traces最多显示多少条(15)等等。
而后咱们修改下中间件去调用咱们这个 index.html 页面:
app.UseSwaggerUI(c => { typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => { c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}"); });
// 将swagger首页,设置成咱们自定义的页面,记得这个字符串的写法:解决方案名.index.html c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.index.html"); });
最后,记得必定要配置了使用静态资源文件的中间件:
app.UseStaticFiles();
好啦,如今已经配置好了 MiniProfiler 和 Swagger了,最后就是如何使用了:
这里我在BlogController 的Get方法中,使用:
public async Task<object> Get(int id, int page = 1, string bcategory = "技术博文") {
......
// 你能够用这种包括的形式 using (MiniProfiler.Current.Step("开始加载数据:")) { if (redisCacheManager.Get<object>("Redis.Blog") != null) { // 也能够直接这么写 MiniProfiler.Current.Step("从Redis服务器中加载数据:"); blogArticleList = redisCacheManager.Get<List<BlogArticle>>("Redis.Blog"); } else { MiniProfiler.Current.Step("从MSSQL服务器中加载数据:"); blogArticleList = await blogArticleServices.Query(a => a.bcategory == bcategory); redisCacheManager.Set("Redis.Blog", blogArticleList, TimeSpan.FromHours(2)); } } ...... }
最后的效果就是这样的(这里说明下:我有时候在F5启动项目,而后第一次点击某一个接口的时候,当前接口分析报告会没有,再点击这个接口,报告就行了,状况之后也不会出现了,若是你也遇到了不要慌,属于正常问题):
是否是感受挺好的!这样的时间都有了,而后还记得上边配置的 /profiler 么,咱们点击 share 就能看到了,这里不细说了,你们本身玩一玩。如今有一个问题就是,我总不能每个 api 接口都这么写吧,多麻烦呀!机智如你,这个时候 AOP 日志记录又派上用场了!
很简单,只须要在 AOP 日志记录中,配置 MiniProfiler 便可:
try { // 就是这里!! MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> "); invocation.Proceed(); } catch (Exception e) { //执行的 service 中,捕获异常 dataIntercept += ($"方法执行中出现异常:{e.Message + e.InnerException}"); }
这样就在任何的接口中,都能看到这个调用信息了!
是否是如今更加体会到了 AOP 的好处!
就这样,我们文章开头的第一个问题就这样解决啦!目前是第1、第二两个问题都解决了,就剩下第三个问题了,若是查看错误日志呢,目前咱们仍是在日志里查看的,别慌,虽然 MiniProfiler没有这个功能,可是我本身奇思妙想了一个,请往下看。
操做很简单,功能颇有效,动图以下,具体的看个人在线地址 http://123.206.33.109:8081:
若是想要开启这个 SQL AOP的功能,请参考个人源代码,主要是经过 SqlSugar 的 相关功能来实现:
Blog.Core.Repository -> DbContext.cs -> _db.Aop.OnLogExecuting
_db.Aop.OnLogExecuting = (sql, pars) => //SQL执行中事件 { Parallel.For(0, 1, e => { MiniProfiler.Current.CustomTiming("SQL:", GetParas(pars) + "【SQL语句】:" + sql); LogLock.OutSql2Log("SqlLog", new string[] { GetParas(pars), "【SQL语句】:" + sql }); }); };
刚刚上边我们也说到了,可使用 AOP 进行异常的捕获记录,只不过须要在 log 日志文件中,查看。而 miniprofiler 的功能居然正好是时间的分析展现,那咱们可不能够融合下二者呢!
很简单!咱们只须要在 AOP 的切面异常catch中,还有 全局Filter 异常中,将错误信息融入到 MiniProfiler便可:
// 这个是AOP中 try { MiniProfiler.Current.Step($"执行Service方法:{invocation.Method.Name}() -> "); invocation.Proceed(); } catch (Exception e) { //执行的 service 中,收录异常 MiniProfiler.Current.CustomTiming("Errors:", e.Message); //执行的 service 中,捕获异常 dataIntercept += ($"方法执行中出现异常:{e.Message + e.InnerException}"); } //这个是全局异常处理中 public void OnException(ExceptionContext context) { //...................不重要内容............... MiniProfiler.Current.CustomTiming("Errors:", json.Message); //采用log4net 进行错误日志记录 _loggerHelper.Error(json.Message, WriteLog(json.Message, context.Exception)); }
这个时候,咱们查看下效果,还记得上边我们说到的那个栗子么,就是为了一个不重要的方法,而不能说出核心功能 “我爱你” 的那个栗子,我们如今再看看效果:
是否是很方便!不只能查看出全部的接口调用时间记录,还能查看错误信息!这对于咱们平时开发仍是颇有帮助的!
文章开头的第三个问题(如何快速的找到当前接口的错误信息)也完美解决!
好啦,今天的讲解就到这里了,主要说了分析了AOP切面编程的大做用,而后讲解了 MiniProfiler 插件的使用,同时对异常的处理,极大的帮助开发者定位错误已经信息处理。
最后给你们说下,关于Miniprofilter 还有其余的情景:
一、能够针对 EF 对数据库sql执行的性能进行监控(由于本项目是Sqlsugar,因此就不针对性展现了,想实验的能够自行测试)。
Client solution
, 添加 Dbcontext、Models、Repository
Nuget
安装packageMiniProfiler.EntityFrameworkCore
Startup
类的方法ConfigureServices
添加代码:services.AddMiniProfiler().AddEntityFramework();
Controller
的 Index
方法中添加对 repository
方法的调用二、不须要必定在swagger中使用,其余的也行,好比mvc,只不过我感受swagger更舒服。
https://github.com/anjoy8/Blog.Core
https://gitee.com/laozhangIsPhi/Blog.Core
-- ♥ -- ♥ -- ♥ -- ♥ -- ♥ -- ♥ --