游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

舒服!一个注解,搞定SpringBoot操作日志

  Springboot注解操作日志
  此组件解决的问题是:
  谁在什么时间对什么做了什么事
  本组件目前针对Springboot做了Autoconfig,如果是SpringMVC,也可自己在xml初始化bean使用方式基本使用maven依赖添加SDK依赖dependencygroupIdio。github。mouztgroupIdbizlogsdkartifactIdversion1。0。4versiondependency复制代码SpringBoot入口打开开关,添加EnableLogRecord注解
  tenant是代表租户的标识,一般一个服务或者一个业务下的多个服务都写死一个tenant就可以SpringBootApplication(excludeDataSourceAutoConfiguration。class)EnableTransactionManagementEnableLogRecord(tenantcom。mzt。test)publicclassMain{publicstaticvoidmain(String〔〕args){SpringApplication。run(Main。class,args);}}复制代码日志埋点1。普通的记录日志pefix:是拼接在bizNo上作为log的一个标识。避免bizNo都为整数ID的时候和其他的业务中的ID重复。比如订单ID、用户ID等bizNo:就是业务的ID,比如订单ID,我们查询的时候可以根据bizNo查询和它相关的操作日志success:方法调用成功后把success记录在日志的内容中SpEL表达式:其中用双大括号包围起来的(例如:{{order。purchaseName}})order。purchaseName是SpEL表达式。Spring中支持的它都支持的。比如调用静态方法,三目表达式。SpEL可以使用方法中的任何参数LogRecordAnnotation(success{{order。purchaseName}}下了一个订单,购买商品{{order。productName}},下单结果:{{ret}},prefixLogRecordType。ORDER,bizNo{{order。orderNo}})publicbooleancreateOrder(Orderorder){log。info(【创建订单】orderNo{},order。getOrderNo());dbinsertorderreturntrue;}复制代码
  此时会打印操作日志张三下了一个订单,购买商品超值优惠红烧肉套餐,下单结果:true2。期望记录失败的日志,如果抛出异常则记录fail的日志,没有抛出记录success的日志LogRecordAnnotation(fail创建订单失败,失败原因:{{errorMsg}},success{{order。purchaseName}}下了一个订单,购买商品{{order。productName}},下单结果:{{ret}},prefixLogRecordType。ORDER,bizNo{{order。orderNo}})publicbooleancreateOrder(Orderorder){log。info(【创建订单】orderNo{},order。getOrderNo());dbinsertorderreturntrue;}复制代码
  其中的errorMsg是取的方法抛出异常后的异常的errorMessage。3。日志支持种类
  比如一个订单的操作日志,有些操作日志是用户自己操作的,有些操作是系统运营人员做了修改产生的操作日志,我们系统不希望把运营的操作日志暴露给用户看到,
  但是运营期望可以看到用户的日志以及运营自己操作的日志,这些操作日志的bizNo都是订单号,所以为了扩展添加了类型字段,主要是为了对日志做分类,查询方便,支持更多的业务。LogRecordAnnotation(fail创建订单失败,失败原因:{{errorMsg}},categoryMANAGER,success{{order。purchaseName}}下了一个订单,购买商品{{order。productName}},下单结果:{{ret}},prefixLogRecordType。ORDER,bizNo{{order。orderNo}})publicbooleancreateOrder(Orderorder){log。info(【创建订单】orderNo{},order。getOrderNo());dbinsertorderreturntrue;}复制代码4。支持记录操作的详情或者额外信息
  如果一个操作修改了很多字段,但是success的日志模版里面防止过长不能把修改详情全部展示出来,这时候需要把修改的详情保存到detail字段,
  detail是一个String,需要自己序列化。这里的order。toString()是调用了Order的toString()方法。
  如果保存JSON,自己重写一下Order的toString()方法就可以。LogRecordAnnotation(fail创建订单失败,失败原因:{{errorMsg}},categoryMANAGERVIEW,detail{{order。toString()}},success{{order。purchaseName}}下了一个订单,购买商品{{order。productName}},下单结果:{{ret}},prefixLogRecordType。ORDER,bizNo{{order。orderNo}})publicbooleancreateOrder(Orderorder){log。info(【创建订单】orderNo{},order。getOrderNo());dbinsertorderreturntrue;}复制代码5。如何指定操作日志的操作人是什么?框架提供了两种方法第一种:手工在LogRecord的注解上指定。这种需要方法参数上有operatorLogRecordAnnotation(fail创建订单失败,失败原因:{{errorMsg}},categoryMANAGERVIEW,detail{{order。toString()}},operator{{currentUser}},success{{order。purchaseName}}下了一个订单,购买商品{{order。productName}},下单结果:{{ret}},prefixLogRecordType。ORDER,bizNo{{order。orderNo}})publicbooleancreateOrder(Orderorder,StringcurrentUser){log。info(【创建订单】orderNo{},order。getOrderNo());dbinsertorderreturntrue;}复制代码
  这种方法手工指定,需要方法参数上有operator参数,或者通过SpEL调用静态方法获取当前用户。第二种:通过默认实现类来自动的获取操作人,由于在大部分web应用中当前的用户都是保存在一个线程上下文中的,所以每个注解都加一个operator获取操作人显得有些重复劳动,所以提供了一个扩展接口来获取操作人
  框架提供了一个扩展接口,使用框架的业务可以implements这个接口自己实现获取当前用户的逻辑,
  对于使用Springboot的只需要实现IOperatorGetService接口,然后把这个Service作为一个单例放到Spring的上下文中。使用SpringMvc的就需要自己手工装配这些bean了。ConfigurationpublicclassLogRecordConfiguration{BeanpublicIOperatorGetServiceoperatorGetService(){return()Optional。of(OrgUserUtils。getCurrentUser())。map(anewOperatorDO(a。getMisId()))。orElseThrow(()newIllegalArgumentException(userisnull));}}也可以这么搞:ServicepublicclassDefaultOperatorGetServiceImplimplementsIOperatorGetService{OverridepublicOperatorDOgetUser(){OperatorDOoperatorDOnewOperatorDO();operatorDO。setOperatorId(SYSTEM);returnoperatorDO;}}复制代码6。日志文案调整
  对于更新等方法,方法的参数上大部分都是订单ID、或者产品ID等,
  比如下面的例子:日志记录的success内容是:更新了订单{{orderId}},更新内容为,这种对于运营或者产品来说难以理解,所以引入了自定义函数的功能。
  使用方法是在原来的变量的两个大括号之间加一个函数名称例如{ORDER{orderId}}其中ORDER是一个函数名称。只有一个函数名称是不够的,需要添加这个函数的定义和实现。可以看下面例子
  自定义的函数需要实现框架里面的IParseFunction的接口,需要实现两个方法:functionName()方法就返回注解上面的函数名;apply()函数参数是{ORDER{orderId}}中SpEL解析的orderId的值,这里是一个数字1223110,接下来只需要在实现的类中把ID转换为可读懂的字符串就可以了,
  一般为了方便排查问题需要把名称和ID都展示出来,例如:订单名称(ID)的形式。
  这里有个问题:加了自定义函数后,框架怎么能调用到呢?
  答:对于Springboot应用很简单,只需要把它暴露在Spring的上下文中就可以了,可以加上Spring的Component或者Service很方便。Springmvc应用需要自己装配Bean。没有使用自定义函数LogRecordAnnotation(success更新了订单{{orderId}},更新内容为。。。。,prefixLogRecordType。ORDER,bizNo{{order。orderNo}},detail{{order。toString()}})publicbooleanupdate(LongorderId,Orderorder){returnfalse;}使用了自定义函数,主要是在{{orderId}}的大括号中间加了functionNameLogRecordAnnotation(success更新了订单ORDER{orderId}},更新内容为。。。,prefixLogRecordType。ORDER,bizNo{{order。orderNo}},detail{{order。toString()}})publicbooleanupdate(LongorderId,Orderorder){returnfalse;}还需要加上函数的实现ComponentpublicclassOrderParseFunctionimplementsIParseFunction{ResourceLazy为了避免类加载顺序的问题最好为Lazy,没有问题也可以不加privateOrderQueryServiceorderQueryService;OverridepublicStringfunctionName(){函数名称为ORDERreturnORDER;}Override这里的value可以吧Order的JSON对象的传递过来,然后反解析拼接一个定制的操作ahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志a内容publicStringapply(Stringvalue){if(StringUtils。isEmpty(value)){returnvalue;}OrderorderorderQueryService。queryOrder(Long。parseLong(value));把订单产品名称加上便于理解,加上ID便于查问题returnorder。getProductName()。concat(()。concat(value)。concat());}}复制代码7。日志文案调整使用SpEL三目表达式LogRecordAnnotation(prefixLogRecordTypeConstant。CUSTOMATTRIBUTE,bizNo{{businessLineId}},success{{disable?停用:启用}}了自定义属性{ATTRIBUTE{attributeId}})publicCustomAttributeVOdisableAttribute(LongbusinessLineId,LongattributeId,booleandisable){returnxxx;}复制代码8。日志文案调整模版中使用方法参数之外的变量
  可以在方法中通过LogRecordContext。putVariable(variableName,Object)的方法添加变量,第一个对象为变量名称,后面为变量的对象,
  然后我们就可以使用SpEL使用这个变量了,例如:例子中的{{innerOrder。productName}}是在方法中设置的变量OverrideLogRecordAnnotation(success{{order。purchaseName}}下了一个订单,购买商品{{order。productName}},测试变量{{innerOrder。productName}},下单结果:{{ret}},prefixLogRecordType。ORDER,bizNo{{order。orderNo}})publicbooleancreateOrder(Orderorder){log。info(【创建订单】orderNo{},order。getOrderNo());dbinsertorderOrderorder1newOrder();order1。setProductName(内部变量测试);LogRecordContext。putVariable(innerOrder,order1);returntrue;}复制代码9。函数中使用LogRecordContext的变量
  使用LogRecordContext。putVariable(variableName,Object)添加的变量除了可以在注解的SpEL表达式上使用,还可以在自定义函数中使用,这种方式比较复杂,下面例子中示意了列表的变化,比如从〔A,B,C〕改到〔B,D〕那么日志显示:删除了A,增加了DLogRecord(success{DIFFLIST{文档地址}},bizNo{{id}},prefixREQUIREMENT)publicvoidupdateRequirementDocLink(StringcurrentMisId,Longid,ListStringdocLinks){RequirementDOrequirementDOgetRequirementDOById(id);LogRecordContext。putVariable(oldList,requirementDO。getDocLinks());LogRecordContext。putVariable(newList,docLinks);requirementModule。updateById(docLinks,RequirementUpdateDO。builder()。id(id)。docLinks(docLinks)。updater(currentMisId)。updateTime(newDate())。build());}ComponentpublicclassDiffListParseFunctionimplementsIParseFunction{OverridepublicStringfunctionName(){returnDIFFLIST;}SuppressWarnings(unchecked)OverridepublicStringapply(Stringvalue){if(StringUtils。isBlank(value)){returnvalue;}ListStringoldList(ListString)LogRecordContext。getVariable(oldList);ListStringnewList(ListString)LogRecordContext。getVariable(newList);oldListoldListnull?Lists。newArrayList():oldList;newListnewListnull?Lists。newArrayList():newList;SetStringdeletedSetsSets。difference(Sets。newHashSet(oldList),Sets。newHashSet(newList));SetStringaddSetsSets。difference(Sets。newHashSet(newList),Sets。newHashSet(oldList));StringBuilderstringBuildernewStringBuilder();if(CollectionUtils。isNotEmpty(addSets)){stringBuilder。append(新增了b)。append(value)。append(b:);for(Stringitem:addSets){stringBuilder。append(item)。append(,);}}if(CollectionUtils。isNotEmpty(deletedSets)){stringBuilder。append(删除了b)。append(value)。append(b:);for(Stringitem:deletedSets){stringBuilder。append(item)。append(,);}}returnStringUtils。isBlank(stringBuilder)?null:stringBuilder。substring(0,stringBuilder。length()1);}}复制代码框架的扩展点重写OperatorGetServiceImpl通过上下文获取用户的扩展,例子如下ServicepublicclassDefaultOperatorGetServiceImplimplementsIOperatorGetService{OverridepublicOperatorgetUser(){returnOptional。ofNullable(UserUtils。getUser())。map(anewOperator(a。getName(),a。getLogin()))。orElseThrow(()newIllegalArgumentException(userisnull));}}复制代码ILogRecordService保存查询日志的例子,使用者可以根据数据量保存到合适的存储介质上,比如保存在数据库或者ES。自己实现保存和删除就可以了
  也可以只实现查询的接口,毕竟已经保存在业务的存储上了,查询业务可以自己实现,不走ILogRecordService这个接口,毕竟产品经理会提一些千奇百怪的查询需求。ServicepublicclassDbLogRecordServiceImplimplementsILogRecordService{ResourceprivateLogRecordMapperlogRecordMapper;OverrideTransactional(propagationPropagation。REQUIRESNEW)publicvoidrecord(LogRecordlogRecord){log。info(【logRecord】log{},logRecord);LogRecordPOlogRecordPOLogRecordPO。toPo(logRecord);logRecordMapper。insert(logRecordPO);}OverridepublicListLogRecordqueryLog(StringbizKey,CollectionStringtypes){returnLists。newArrayList();}OverridepublicPageDOLogRecordqueryLogByBizNo(StringbizNo,CollectionStringtypes,PageRequestDOpageRequestDO){returnlogRecordMapper。selectByBizNoAndCategory(bizNo,types,pageRequestDO);}}复制代码IParseFunction自定义转换函数的接口,可以实现IParseFunction实现对LogRecord注解中使用的函数扩展
  例子:ComponentpublicclassUserParseFunctionimplementsIParseFunction{privatefinalSplittersplitterSplitter。on(,)。trimResults();ResourceLazyprivateUserQueryServiceuserQueryService;OverridepublicStringfunctionName(){returnUSER;}Override11,12返回11(小明),12(张三)publicStringapply(Stringvalue){if(StringUtils。isEmpty(value)){returnvalue;}ListStringuserIdsLists。newArrayList(splitter。split(value));ListUsermisDOListuserQueryService。getUserList(userIds);MapString,UseruserMapStreamUtil。extractMap(misDOList,User::getId);StringBuilderstringBuildernewStringBuilder();for(StringuserId:userIds){stringBuilder。append(userId);if(userMap。get(userId)!null){stringBuilder。append(()。append(userMap。get(userId)。getUsername())。append());}stringBuilder。append(,);}returnstringBuilder。toString()。replaceAll(,34;,);}}复制代码变量相关
  LogRecordAnnotation可以使用的变量出了参数也可以使用返回值ret变量,以及异常的错误信息errorMsg,也可以通过SpEL的T方式调用静态方法噢ChangeLogTODO
  注意点:
  整体日志拦截是在方法执行之后记录的,所以对于方法内部修改了方法参数之后,LogRecordAnnotation的注解上的SpEL对变量的取值是修改后的值哦源码
  https:github。commouztmztbizlog

如何提升野外帐篷宿营体验?不想错过奇妙的日出和晚霞,野外帐篷宿营是不错的选择,如何才能使帐篷宿营有更好的体验呢?以下谈谈几点体会:1。帐篷宿营地点选择。首先要选择较为空旷、干燥、平整、没有洞……为什么你的衣柜里总是缺少那一件衣服每到换季,无论刮风下雨,商场都人满为患。可见购物星人之庞大!十分惭愧的是,我也是购物星庞大家族中的一员。为什么每到换季就要买买买?去年的衣服都穿不了了么?对于这个问题,我和我的……曝英特尔DG1独显只应用于移动端,挑战英伟达MX系列IT之家5月11日消息根据外媒Tom39;sHardware的消息,美国阿贡国家实验室在PPT中介绍了英特尔即将推出的DG1独显,称这款独显将只用于移动端。如上图所示,英……英伟达公布TeslaA100计算卡6912CUDA核心,40IT之家5月14日消息今晚,英伟达发布了TeslaA100计算卡,搭载了GA100GPU,7nm工艺,采用了全新的安培架构。根据外媒VideoCardz的介绍,Tesla……世界最大GPU英伟达TeslaA100曝光,540亿个晶体管IT之家5月14日消息根据外媒VideoCardz的消息,他们获得了英伟达即将推出的TeslaA100SMX模块的图片,该模块搭载了GA100GPU和6个HBM2(e)显存。……英伟达发布新版驱动程序修复荒野大镖客2在4核CPU上崩溃BuIT之家11月20日消息根据TPU的报道,英伟达推出了GeForceHotfix驱动程序441。34,修复了《荒野大镖客2》在使用VulkanAPI时在4核和6核CPU上死机的……暑假亲子自驾游去哪儿好玩工作太辛苦,不如趁着假期跟随小编来一场说走就走的自驾游,放松一下紧张的心情。一。桂林桂林,阳朔风景还算不错,可是,想要看美景之前,还得做出十足的防坑准备。每一个小资……废钢涨涨涨黑暗过去就是黎明,沉寂了多时的废钢这几天终于又迎来了一波上涨行情。早些时候废钢跌得最惨的时候,废钢精炉料市场收购价跌到了1950元吨,而且还无人问津。时间不长,也就是国庆……英伟达GTX1660SUPER正式发布,性能较GTX1660IT之家10月29日消息根据英伟达官方的消息,GeForceGTX1660SUPER正式发布,其性能相较初代GTX1660提升最高可达20,而相较上一代GTX10606GB,性……消息称AMDRX5500系列显卡推迟到12月,性能接近RX5IT之家10月29日消息今天上午,博板堂的消息称,AMD近日推出的RX5500系列显卡将会推迟到12月份发售。据悉,10月初,AMD发布了新款桌面端显卡RX5500系列。……微星GTX1660Super现身天猫,售价1999元IT之家10月27日消息英伟达将于10月29日发布GTX1660Super显卡,现在,微星的天猫旗舰店已经将这款产品列了出来,售价定在1999元。根据详情页的介绍,微星G……雷神推出RGB笔记本支架,自带USBCHUB功能IT之家5月9日消息雷神推出了一款笔记本电脑支架,搭载了RGB氛围侧灯,自带HUB功能,售价199元。IT之家了解到,雷神RGB笔记本支架F80采用了铝合金材质,3mm铝……
再信贾跃亭一次?FF又续命成功拉来40亿元增资,股价一夜涨28月2日消息,法拉第未来(简称FF公司)大股东FFTopHoldingLLC与FF公司就可转换定期贷款融资正式签署非约束性投资意向书。据悉,本轮融资包括潜在金额最高达6亿……股市大底已探明,2023年将是新一轮牛市起点!(长文数据研究很多时候我们入场不好,容易买贵,造成亏损。我们试图通过对市场底部的判断,来加强持股信心。沪深300指数底部探讨,2023年有望开启新一轮长牛!1、股债利差到达历史底部区间……不停地补水,还是干燥脱皮,为什么用护肤品还会刺痛?你的皮肤有没碰到过,不停地补水,还是干燥脱皮,而且用护肤品还会刺痛?1。为什么会干燥起皮还刺痛。你以为你的脸是干燥脱皮,其实是受伤,表面上看,你的皮肤是这样的(图一……不用牛奶的吐司面包也可以很柔软,群友分享的这个配方太棒了我做的吐司面包大多都是放牛奶,或者放奶粉,总觉得有牛奶的配方做出来才能柔软美味,带有奶香味才更好吃。但是前段时间,群里一位朋友分享了一个配方,没有牛奶,没有奶粉,配料非常……中药知识一点通紫梢花的功效和作用有哪些?紫梢花,别名紫霄花、淡水海绵,为淡水海绵科动物脆弱骨针淡水海绵或刻盘淡水海绵,以干燥群体入药。秋、冬于河床或湖边拾取。生活于清流或湖沼中,常附生在石块、树枝或水草等物体上……纳微科技正式公开发售UniSilHP系列新产品,目前上市销售纳微科技4月9日公告,公司于4月正式公开发售全新单分散超大孔硅胶色谱填料UniSilHP系列新产品。UniSilHP系列新产品实现了超过1。0mLg的大孔容,使得该系列新产品更……法国主力后卫重伤,退出世界杯!卫冕冠军陷魔咒先后折损7王牌北京时间11月23日,法国足协官方宣布,球队的中卫卢卡斯埃尔南德斯右膝十字韧带断裂,无缘所有剩余比赛,提前告别本届世界杯。法国队全体员工,祝他早日康复。在今天凌晨进行的2……走过大西北,你才知道什么是锦绣中华(1)西北旅游最近几年还是比较火的,我们也蹭了一次热度,跑了一次甘肃青海环线,沿途的风光深深地震撼了我,让我对祖国锦绣河山的认识又上了一个高度。一路上,我们走过戈壁荒滩,大漠、盐湖、……北京房产证后,大衣哥新儿媳晒嫁妆,几十个礼盒,金银首饰数不清有钱人家和普通人家在经营生活的理念上是有区别的!对于普通人家来说,女孩子嫁过去后,想要丰厚的物质生活,需要靠自身努力,但有钱人家不同,长辈们更重视你的德行,不争不抢,顾大……罚50万元!个贷业务违反审慎经营规则,中关村银行遭通报6月13日消息,中国银保监会官网日前披露的一则行政处罚信息公开表显示,北京中关村银行股份有限公司(下称北京中关村银行)因个人贷款业务严重违反审慎经营规则,被北京银保监局责令改正……智慧宿舍,人脸识别为你保驾护航随着社会的不断发展,人们的生活水平也在不断提高,对于宿舍管理的要求也越来越高。然而,传统的宿舍管理方式,已经不能满足现代化管理需求,安全性、效率性以及管理水平亟待提升。针对这些……内鬼成堆,热火该如何应对阿德巴约真是个纯纯的贵物,上一轮欺负乔守信贼来劲异常凶猛,大帝带伤回归丫立马哑火。狐假虎威混到东决后更是逆大天被扒的连裤衩子都不剩,堂堂顶薪全明星内线,两场比赛凑一块总共才拿1……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网