文章目录
- 为什么要打日志?
- 日志分为哪几种
- (1) 调试开发日志
- (2) 用户行为日志
- (3)程序运行日志
- (4)记录系统或者机器的状态
- 运行日志可以反馈哪些信息
- (1) 通用信息
- (2) 进度信息
- (3) 异常信息
- (4) 接口间的日志(传递唯一标识)
- 每行日志都要带有uuid吗
- log.info 和String.format的对比
- 引入日志
- log.info日志的入参
- 双object和objects的区别
- 格式字符串(format)和直接字符串的区别
- 性能提升方法
- 通常哪些位置要加日志
- controller入参日志
- 异常日志
- 方法完成返回结果日志
- 加日志的注意点
- 无用的日志
- 还是无用的日志
- 再来一个无用的日志
- 没必要的日志
- 很大的日志可以考虑不加,例如base64String
- 返回报文里面也序列化入参
- 为什么要日志优化
- 日志中不要有特殊字符
- service中的日志和返回体
- 微服务中日志的不足
- 场景
- 解决方案
- 能够记下主要接口在哪个应用中
- requestId实现分布式的日志追踪
- 日志中的行号找不到代码
- 日志和返回体中写法不同
- 日志中使用中括号
- 日志的规范性
- 规范性2
- traceId的作用
- 批量数据要不要日志
- 日志中的低级错误
- 占位符和入参哪个少了会报错
- 日志和接口ResultJson的信息的区别:
- 日志会影响效率吗
- xshell日志工具
为什么要打日志?
日志是非常重要的,好的日志可以快速定位问题。
试想如果你一行日志也不打,服务器出现问题,是不是很麻爪。
日志分为哪几种
(1) 调试开发日志
目的是开发期调试程序使用,这种日志量比较大,且没有什么实质性的意义,只应该出现在开发期,而不应该在项目上线之后输出。
(2) 用户行为日志
这种类型的日志,记录用户的操作行为,用于大数据分析,比如监控、风控、推荐等等。这种日志,一般是给其他团队分析使用,而且可能是多个团队,因此一般会有一定的格式要求,开发者应该按照这个格式来记录,便于其他团队的使用。当然,要记录哪些行为、操作,一般也是约定好的,因此,开发者主要是执行的角色。这种日志,又叫事件上报,或埋点。
(3)程序运行日志
记录程序的运行状况,特别是非预期的行为、异常情况,这种日志,主要是给开发、维护人员使用。什么时候记录,记录什么内容,完全取决于开发人员,开发者具有高度自主性。本文讨论的主要也是指这种类型的日志,因为作为一个服务端开发、运维人员,程序运行日志往往是解决线上问题的救命稻草。
(4)记录系统或者机器的状态
比如网络请求、系统CPU、内存、IO使用情况等等,这种日志主要是给运维人员使用,生成各种更直观的展现形式,在系统出问题的时候报警。
运行日志可以反馈哪些信息
(1) 通用信息
时间,日志类型,所在类等 。 (日志框架一般都带了)
(2) 进度信息
比如一个服务有5个步骤,是否开始这个服务。执行到了哪步等。
(3) 异常信息
这里要 特别注意一点,如果记录了异常日志,不要继续抛出异常了。 否则可能多次记录日志,因为其他位置可能还会捕获并记录。
注: 该写error的日志,千万别写为info,因为error有个最重的功能就是传入e,会自动打印异常栈,而info必须显式的打印。
(4) 接口间的日志(传递唯一标识)
我在这台机器上调用服务,服务是在另外一台机器。所以需要传递唯一标识。可以传递一个uuid,便于找到问题所在。如代码:
这样我可能没有另一台服务的权限,但可以提供uuid给服务器的同事查问题。
每行日志都要带有uuid吗
每一行都带上更好,就是代码量稍多一些。
log.info 和String.format的对比
效果基本相同,log有些前缀信息。
引入日志
log.info日志的入参
可以使用格式字符串,用占用符替代参数。
Logger类定义的方法:
例子:
双object和objects的区别
开始我也纳闷,双object不就是objects的一个特殊形式么。
后来看了源码才发现,有点区别。 objects会创建一个objects数组。会增加消耗。
但是双object的2个对象是独立的,性能更高。 这也是把性能发挥到极致了。
格式字符串(format)和直接字符串的区别
1、格式字符串可以使用占位符(这谁都知道)
2、看源码说,format还有个优点,就是如果日志级别不支持,不会创建对象。
这里说的不是传入的object对象,而是logger.trace()对象。
例如,默认的级别是info,trace对象是不会创建的。可以还没想到如何验证。
性能提升方法
1、尽量使用格式字符串。(可以减少低级别的日志对象创建,但是对入参object无效)
2、能使用双object就不要用objects数组。(省略数组开销)
3、使用logger.isErrorEnabled() 。 尤其在入参比较大的时候,这种方法性能很高。
通常哪些位置要加日志
一般至少要说明以下信息:
发生了什么请求,入参是什么
成功了,返回值是
异常了,异常原因是
熟练使用日志还需要对业务非常熟悉才可以,否则都不知道该筛选什么日志。
controller入参日志
例如用户今天提交了个快递单,但是说他看不到。我在数据库查也没有。说明用户没有提交,或者是提交过程出错了。
如何排查呢:
1、那么先查info日志,看用户是否有提交的记录,如果没有日志,说明数据没有到后台。是不是前端出了问题。 经查,app端出了问题。
controller里面最好要2条日志,入参日志,结果日志。 调用的方法有日志,异常有日志,就实现了日志的全覆盖。
而且@RequestMapping 还可以设置name,很容易通过拦截器批量处理。
service层中的日志比较多样,这个可以手动掌握,更加的灵活。
异常日志
还是上述快递单的场景,info查到了入参日志,但是数据库没信息。
说明很有可能有异常了。 经常,后台空指针异常了。
方法完成返回结果日志
还是订单问题。 数据库没信息,info有出参日志,提示单据成功。 但是数据库查不到啊。继续翻日志,发现还有一条删除记录的日志。 说明用户提交了又删除了。所以查不到。
注: 如果是逻辑中远程调用的结果,最好也打上日志。这样好找问题。
加日志的注意点
加日志尽量全,又要尽可能少。
无用的日志
如下日志就是没用的。如:
看不懂了 ,转账1000元,谁转的啊。虽然有日志,但是和没有一样。
还是无用的日志
谁呀,什么期限。谁也看不懂。
正确的方式:
1、 未获取对应的税号666999的期限 这样大家都能看懂。
如果有的税号没有期限记录,那么没法打印税号。 也要提供线索。
2、票据123456未获取对应的税号期限 这样也是可查的。
再来一个无用的日志
这种日志在调试的时候,或许有点用。 但是到了生产上就屁用没有。
在海洋般的日志里,没有检索特征无异于大海捞针。
正确的例子:
没必要的日志
基础方法里面没必要写日志。
例如生成随机码的工具类,没有必要在这里加。而是应该加在调用它的controller里面。
很大的日志可以考虑不加,例如base64String
例如上传的图片要处理,转化为base64,要不要加日志?
不加,万一出问题怎么办。加日志,一行日志就上千行,其实可读性也很差。
这样大的日志其实可以考虑去掉。
返回报文里面也序列化入参
为了方便查询,返回报文里面也打印入参。
缺点: 相当于入参打印了2遍,如果入参报文也比较多,那么很影响性能。
一个uuid贯穿比打2次入参方便很多。
为什么要日志优化
通常不会有这个问题,因为开发者只怕日志少,不怕日志多。
但是,如果经历过一天10G、甚至40G日志的场景,就能体会到了。
日志太大,即使一个简单的命令,捞数据效率也很慢,更别提复杂场景的数据捞取。
什么,拆分日志,40G的日志文件,即使1000M一个文件,也要拆分40个,要是100M一个,那要400个。 是不是很崩溃。所以日志肯定不是越多越好。
日志中不要有特殊字符
单双引号,星号,右斜杠等最好都不要加。
另外还有:
不要有多个中划线,一个中划线是没问题的,多个就很尴尬。
例如日志为 “--------请求已开始” 。
那你 grep --------请求已开始 a.txt 看下。
service中的日志和返回体
需要日志么:
这个要看具体情况,主要是service的复杂度。如果一个service里面就一个dao,dao里面就一个mapper,那么没必要加。
如果service集成了很多的逻辑,作为一个单独的功能,那么是可以加的。
需要返回体么:
service也可大可小。 如果一个service,包含了一个较大的功能,具备独立的code,message等,那么是可以配备返回体的。
微服务中日志的不足
日志虽然可以查看问题。但是如果在微服务中,查看日志还是有点捉襟见肘。
一种笨方法是:远程调用服务之前可以加是哪个远程服务的,这样跟踪的时候不蒙圈。
场景
应用由10个模块组成,每个模块双节点。 出了问题,先从主应用日志找,再找具体某个模块。 登录服务器需要堡垒机,最少要开4个shell窗口。 相当头疼。
解决方案
方案一:
仅依靠看日志效率还是有点低,一天出几个问题,就够受的了。 所以最好加入监控机制。
方案二:
其实springcloud中的slueth组件就是专门解决分布式日志的跟踪的。
能够记下主要接口在哪个应用中
应用有哪些主要接口是要了如指掌的。 至少看到service要知道属于哪个应用。
还有流程的过程也要清楚,走了几个应用,都是什么方法。
否则看日志都不知道去哪个应用下看。
requestId实现分布式的日志追踪
微服务中,经常用到不只一个rpc服务。通过requestId可以贯穿整个流程。
requestId可以放在入参父类中,自动生成。
继续调用其他rpc的时候requestId也传递出去。接收方先判断是否有requestId,如果存在,就直接使用,不存在则生成。
日志中的行号找不到代码
其实一般是能找到的,这里需要注意下,日志的行号是输出日志的那一行,异常在哪一行是在堆栈信息那一行。
如下:
demo2:58 是logger.error这一行
demo2(ModelController.java:56) 是报错的行
日志和返回体中写法不同
日志中推荐使用占位符的写法。 因为看起来更优雅,修改起来也方便。
返回体中推荐使用+号拼接字符串,因为JsonResult不具备解析占位符的功能。
正确的写法:
错误的写法:
日志中使用中括号
其实就是一种写法,为了便于删选:
日志的规范性
要做到绝对规范挺难的,因为不同场景,日志要打印的格式也不同。
要一致性,一种思路是定义常量。但是都用常量的话,看起来反而不如文字清爽。
不用常量的话,就会有一个问题,日志描述越写花样越多,例如:
这里追求日志完全一模一样挺难的,但至少要有保证一点,能把事情描述清楚。
规范性2
例如controller中用"请求":
为了日志名称的一致性,@RequestMapping中可以设置下name属性,主要是为了复制起来方便。
service中用"服务"
主要就这2个了,dao里面一般不用日志的。
traceId的作用
1、多行不同的日志确定是同一个请求的。
2、跨服务之间日志的追踪。
批量数据要不要日志
有的时候考虑到日志量会非常大,便不打日志,这是不对的。因为万一某条数据有纠纷,什么都查不到,这官司怎么打? 所以宁可麻烦一点,批量数据也想办法加上日志。如果不是特别多,可以一起加,量很大的话,可以循环加。
日志是肯定要的。
日志中的低级错误
还有一种情况要坚决避免,就是日志写错的情况。
例如:
这种低级错误实际中确实有发生,要细心些,坚决避免。
占位符和入参哪个少了会报错
这个比较好测试,代码如下:
实测都不会报错。如果少了,打印不出来而已。
日志和接口ResultJson的信息的区别:
当然是有区别的,
1、日志要带上请求的名称,ResultJson不用。
2、日志要加上,返回报文、返回信息 这样的字段,ResultJson不用。
至于占位符,都是可以使用的,因为有MessageFormat工具。
日志会影响效率吗
一般来说,单条日志的耗时是1ms级别甚至更少。
所以一个请求,有二三十条日志也不碍事。
只要日志打印的不是特别夸张的对象就行。例如一个超大的对象,几万行的json等。
xshell日志工具
简单说就是将日志结果保存到自己电脑上。
右键 | 日志 | 启动 ,然后弹出一个保存文件的对话框,选个文件就是日志输出的文件,所有的操作及返回都会打印到这个文件里。
如果不用了,右键 | 日志 | 停止 即可。
缺点: 因为要写日志到文件,所以稍微影响点效率。