当前位置: 首页 > 后端技术 > Java

请记住,这种记录错误的方式既快速又准确!

时间:2023-04-01 18:46:47 Java

概述在日常工作中,程序员经常需要处理各种线上故障。如果业务代码没有打印日志或者日志没有打印好,会大大增加定位问题的难度,导致bug难以解决。时间长了。对于那些影响比较大的BUG,分秒必争的处理时间。如果处理慢几秒,GMV可能会下降很多。一个程序员好不好,其中一个判断维度就是:处理线上问题是否快速准确,而日志是帮助我们快速定位问题的绝佳手段。下面分享一下笔者平时在业务系统中保存日志的一些方法和习惯,希望对大家有所帮助。请统一日志格式。最好有统一的日志格式,方便查看和定位问题,便于统计。我一般喜欢定义一个LogObject对象,它定义了日志的各个字段。例如:importcom.fasterxml.jackson.annotation.JsonInclude;importcom.fasterxml.jackson.annotation.JsonInclude.Include;importcom.fasterxml.jackson.annotation.JsonProperty;publicclassLogObject{@JsonProperty(index=1)privateString事件名称;@JsonProperty(index=2)privateStringtraceId;@JsonProperty(index=3)私有字符串消息;@JsonProperty(index=4)privatelongcostTime;@JsonProperty(index=6)privateIntegeruserId;@JsonProperty(index=7)privateObjectothers;@JsonProperty(index=8)私有对象请求;@JsonProperty(index=9)私有对象响应;publicStringgetEventName(){返回事件名称;}publicLogObjectsetEventName(StringeventName){this.eventName=eventName;归还这个;}publicObjectgetRequest(){返回请求;}publicLogObjectsetRequest(Objectrequest){this.request=request;返回这;}publicObjectgetResponse(){返回响应;}publicLogObjectsetResponse(Objectresponse){this.response=response;归还这个;}publicStringgetMsg(){返回消息;}publicLogObjectsetMsg(Stringmsg){this.msg=msg;归还这个;}publiclonggetCostTime(){returncostTime;}publicLogObjectsetCostTime(longcostTime){this.costTime=costTime;归还这个;}publicIntegergetUserId(){返回userId;}publicLogObjectsetUserId(IntegeruserId){this.userId=userId;归还这个;}publicObjectgetOthers(){返回其他人;}publicLogObjectsetOthers(Objectothers){this.others=others;归还这个;}publicStringgetTraceId(){返回traceId;}publicLogObjectsetTraceId(StringtraceId){this.traceId=traceId;关于转动这个;}traceId:调用链ideventName:事件名称,一般为业务方法名userId:C端用户idmsg:结果消息costTime:接口响应时间request:接口请求入口response:接口返回值others:其他业务参数使用链式,方便设置字段的值:longendTime=System.currentTimeMillis();LogObjectlogObject=newLogObject();logObject.setEventName(methodName).setMsg(msg).setTraceId(traceId).setUserId(backendId).setRequest(liveRoomPushOrderReqDto).setResponse(响应).setCostTime((endTime-beginTime));LOGGER.info(JSON.toJSONString(logObject));当然最好封装一个工具类,比如:LogTemplate,作为一个统一另外入口可以使用JsonProperty注解来指定字段顺序,比如通过index=1,把eventName放在在前面。@JsonProperty(index=1)privateStringeventName;把request和response放在一起把request和returnvalue放在同一个log中,这样的好处是非常方便查看contextlog。如果打印成两块,那块返回值可能会冲到最后,又得重新grep操作,影响效率。具体日志如下:{"eventName":"createOrder","traceId":"createOrder_1574923602015","msg":"success","costTime":317,"request":{"uId":111111111,"skuList":[{"skuId":22222222,"buyNum":1,"buyPrice":8800,}]},"response":{"code":0,"message":"操作成功","data":{"bigOrderId":"BIG2019","m2LOrderIds":{"MID2019":{"22222222":"LIT2019"}}}}}为了形成单行,有两个选项,一个是比较low的,在代码中使用trycatchfinally,例如:@PostMapping(value="/createOrder")publicJsonResultcreateOrder(@RequestBodyObjectrequest)throwsException{StringmethodName="/createOrder";整数backendId=null;Stringmsg="成功";longbeginTime=System.currentTimeMillis();StringtraceId="createOrder_"+beginTime;JsonResult响应=null;尝试{OrderCreateRsporderCreateRsp=orderOperateService.createOrder(request,traceId);response=JsonResult.success(orderCreateRsp);}catch(Exceptione){msg=e.getMessage();LOGGER.error(methodName+",userId:"+backendId+",request:"+JsonHelper.toJson(request),e);thrownewBizException(0,"下单失败");}最后{longendTime=System.currentTimeMillis();LogObjectlogObject=newLogObject();logObject.setEventName(methodName).setMsg(msg).setTraceId(traceId).setUserId(backendId).setRequest(request).setResponse(response).setCostTime((endTime-beginTime));记录器.info(JSON.toJSONString(logObject));}返回响应;}这个方案有个缺点,就是每个业务方法都要处理日志。更好的方案是使用aop加线程本地统一拦截请求,将返回值和请求参数串在一起。这个网络上有很多解决办法,这里就不细说了。对于性能要求比较高的应用,推荐第一种方案,因为使用aop会有一定的性能损失。像我之前在唯品会参与的产品聚合服务,我用的是第一种方案。毕竟,每秒必须处理数百万个请求。另外附上学习资源:在Java高级视频资源日志中添加traceId。如果应用中已经使用了统一的调用链监控方案,并且可以根据调用链id查询接口状态,则无需在代码中手动添加traceId。如果应用还没有接入调用链系统,建议添加traceId,尤其是需要调用中台各种微服务接口的聚合服务。比如聚合层的下单业务,需要调用的微服务有:订单失败,是哪一个?如果微服务接口失效,就更难发现了。下面以小程序端调用聚合层下单接口为例进行演示://MarketingSystem{"eventName":"pms/getInfo","traceId":"createOrder_1575270928956","msg":“成功”,“costTime”:2,“userId”:1111111111,“请求”:{“userId”:1111111111,“skuList”:[{“skuId”:2222,“skuPrice”:65900,“buyNum”:1,"activityType":0,"activityId":0,}],},"response":{"result":1,"msg":"success","data":{"realPayFee":100,}}}//订单系统{"eventName":"orderservice/createOrder","traceId":"createOrder_1575270928956","msg":"success","costTime":29,"userId":null,"request":{"skuList":[{"skuId":2222,"buyNum":1,"buyPrice":65900,}],},"response":{"result":"200","msg":"调用成功",“数据”:{“bigOrderId”:“BIG2019”,“m2LOrderIds”:{“MID2019”:{“88258135”:“LIT2019”}}}}//支付系统{"eventName":"payservice/pay","traceId":"createOrder_1575270928956","msg":"success","costTime":301,"request":{"orderId":"BIG2019","paySubject":"Test","totalFee":65900,},"response":{"requestId":"test","code":0,"message":"操作成功","data":{"payId":123,"orderId":"BIG2019","tradeType":"JSAPI","perpayId":"test","nonceStr":"test","appId":"test","signType":"MD5","sign":"test","timeStamp":"1575270929"}}}可以看到聚合层需要调用营销、订单、支付应用的接口。在调用过程中,使用traceId是createOrder_1575270928956的字符串,所以我们只需要grep这个traceId就可以找出所有相关的调用和上下文如何生成traceId,一个简单的方法是使用System.currentTimeMillis()加上业务接口名,如:longbeginTime=System.currentTimeMillis();StringtraceId="createOrder_"+beginTime;添加traceId会侵入业务方法中,例如:publicvoidcreateOrder(Objectobj){longbeginTime=System.currentTimeMillis();StringtraceId="createOrder_"+beginTime;pmsService.getInfo(obj,traceId);orderService.createOrder(obj,traceId);payService.getPrepayId(obj,traceId);}像pmsService这样的内部服务方法都需要添加一个traceId字段。目前,我认为还可以。如果觉得被入侵了,也可以考虑线程本地方法。在处理请求时,为当前线程存储traceId,然后在业务方法中从当前线程中取出,避免接口方法中的traceId满天飞。来源:blog.csdn.net/linsongbin1/article/details/90349661