当前位置: 首页 > 科技观察

日志应该如何打印?

时间:2023-03-18 10:09:44 科技观察

【.com原稿】前言写代码的人都不会写日志,但是我们应该如何打印日志呢?有没有办法打印日志?针对这个问题,查了一下《阿里巴巴Java开发手册》,有8种日志协议。例如,不同功能的日志存储在不同的日志文件中,日志文件的命名形式为appName_logType_logName.log。挺好的,但是属于日志分类的问题,还是解决不了程序员如何系统的在代码中写日志的问题。下面看一个常见的打印日志的例子:log.info("开始执行业务逻辑---------------->{}",param);log.info("业务逻辑执行---------------->{}",param);log.info("业务逻辑执行结束-------------->{}",param);log.error("业务执行异常---------------->{}",param,e);这是怎么回事用这种日志打印?首先,在没有绑定事件的情况下执行什么业务逻辑?没有明确的事件,或者名称、分类,我更愿意称它为事件。我们在搜索日志的时候,一定要有一个主题。如果一个事件被添加到日志打印中,那么我们在搜索日志的时候,只需要输入关键字就可以得到该事件的所有日志。改进日志打印:log.info("{}|开始执行业务逻辑---------------->{}",EVENT_NAME,param);log.info("{}|业务逻辑执行---------------->{}",EVENT_NAME,param);log.info("{}|业务逻辑执行结束----------------->{}",EVENT_NAME,param);log.error("{}|业务执行异常---------------->{}",EVENT_NAME,param,e);其次,一直在生成未绑定主键的事件下的日志,当出现问题时,往往只会给你一个案例进行诊断。所以除了记录Events还需要记录主键,通过观察主键执行过程中产生了哪些日志来定位问题。日志打印改进:log.info("{}|ID={}|开始执行业务逻辑---------------->{}",EVENT_NAME,ID,param);log.info("{}|ID={}|业务逻辑执行---------------->{}",EVENT_NAME,ID,param);log.info("{}|ID={}|业务逻辑执行结束---------------->{}",EVENT_NAME,ID,param);log.error("{}|ID={}|业务执行异常---------------->{}",EVENT_NAME,ID,param,e);三、没有绑定请求事件有主键,但是在查询日志的过程中,发现主键产生了很多重复的日志,日志上下文不连贯。某个请求产生的连续日志,对于我们来说是非常不方便的。这时候就需要考虑并发Case了。改进日志打印://UUID可用于生成ReqId//finalStringReqId=UUID.randomUUID().toString();log.info("{}|ReqId={}|ID={}|开始执行业务逻辑--------------->{}",EVENT_NAME,ReqId,ID,param);log.info("{}|ReqId={}|ID={}|业务逻辑正在执行-------------->{}",EVENT_NAME,ReqId,ID,param);log.info("{}|ReqId={}|ID={}|结束业务逻辑的执行---------------->{}",EVENT_NAME,ReqId,ID,param);log.error("{}|ReqId={}|ID={}|业务执行异常---------------->{}",EVENT_NAME,ReqId,ID,param,e);四、不要绑定分词符打印日志时使用---这个定界符,无意义,不规范,分词难度很大。一定要将不变的文字描述和改变的参数分开打印出来,并用分词符分隔,因为不变的文字描述也可以作为关键字进行搜索。改进日志打印:log.info("{}|ReqId={}|ID={}|开始执行业务逻辑|parameter={}",EVENT_NAME,ReqId,ID,param);log.info("{}|ReqId={}|ID={}|业务逻辑执行|parameter={}",EVENT_NAME,ReqId,ID,param);log.info("{}|ReqId={}|ID={}|结束执行业务逻辑|parameter={}",EVENT_NAME,ReqId,ID,param);log.error("{}|ReqId={}|ID={}|业务执行异常|parameter={}",EVENT_NAME,ReqId,ID,参数,e);第五,错误日志需要输出异常信息。异常日志必须打印堆栈信息。使用e.printStackTrace()无法将异常堆栈输出到控制台,所以写入异常堆栈如果没有日志文件,则需要将异常对象写入最后一个参数。我相信每个人都明白这一点。另外,还需要将异常信息的toString()内容打印到同一行日志中。因为异常栈是换行的,对于一些单行的日志系统,比如阿里云sls,根本看不到异常信息,还得爬到服务器上去找日志栈。所以还是需要把e.toString()写进参数里,有些异常只能看e.toString()的内容来定位。为什么我要求使用e.toString()而不是e.getMessage()?因为如果是NullPointerException,e.getMessage()返回空。改进代码:log.error("{}|ReqId={}|ID={}|业务执行异常|parameter={}|e={}",EVENT_NAME,ReqId,ID,param,e.toString(),e);六、如果有循环,需要绑定循环主键在上面的例子中添加一个业务循环,然后看代码:log.info("{}|ReqId={}|ID={}|开始执行业务逻辑|parameter={}",EVENT_NAME,ReqId,ID,param);for(Keykey:KeyList){log.info("{}|ReqId={}|ID={}|业务逻辑执行|参数={}",EVENT_NAME,ReqId,ID,param);}log.info("{}|ReqId={}|ID={}|业务逻辑执行结束|param={}",EVENT_NAME,ReqId,ID,参数);这样生成的日志非常不方便定位具体的周期。如果循环体出现异常,不清楚是哪个Key引起的。所以,遇到业务逻辑在循环内的代码,应该打印出每次处理的Key。改进代码:log.info("{}|ReqId={}|ID={}|开始执行业务逻辑|parameter={}",EVENT_NAME,ReqId,ID,param);for(Keykey:KeyList){log.info("{}|ReqId={}|ID={}|Key={}|执行业务逻辑|parameter={}",EVENT_NAME,ReqId,ID,key,param);}log.info("{}|ReqId={}|ID={}|结束业务逻辑的执行|Parameters={}",EVENT_NAME,ReqId,ID,param);公式经过上面的分析,我们可以总结出日志打印公式:EVENT_NAME={}|ReqId={}|Key={}|childKey={}|doingsomething|result={}如果没有进程,ReqId可以省略,并且Key的个数不一定只有一个,还可以看到在日志中增加一列[start|end],表示业务流程的开始和结束。JSON日志前段时间写了一个JSON格式的日志,即每一行日志都是一个JSON字符串。上面提到的日志可以称为单行日志。例如:MaplogMap=newHashMap<>();try{logMap.put("EVENT_NAME","monitor");//....}finally{LogUtil.save(JSON.toJSONString(logMap)));}输出日志(为了便于查看我已经格式化):{"EVENT_NAME":"monitor","ReqId":"654321","ID":"123456","success":true,"param":{"name":"zs","age":14}}JSON日志自然是和请求流程绑定的,它最大的优势就是输出序列化的JSON字符串,非常方便离线对其进行各种操作。但是相比单行日志,代码嵌入性太强,需要通过try..finally代码块来捕获。首页:https://github.com/onblogPS:以前喜欢用json日志,现在更喜欢用单行日志【原创稿件,合作站点转载,转载请注明原作者和出处为.com]

最新推荐
猜你喜欢