文章目录
- java日志总结系列1-日志规范
- 日志级别
- 日志内容
- 日志注意事项
java日志总结系列1-日志规范
日志是系统的重要组成部分,用于记载系统的执行记录、审计、排查问题、数据采集等。日志需要持久化,通常日志仅仅需要持久化到磁盘,或者存储到ES,有些场景也需要将日志存储到MySQL中,例如重要的请求日志、用户抽奖的执行记录、提现操作等。
本系列主要讲述java系统中存储到文件、ES中的日志。
日志级别
由高到低可分为error、warn、info、debug。
error:表示系统发生了错误,应该马上介入处理,此时已经影响到了系统正常运转。这样情况最好配置告警。例如
- 读写文件错误
- 网络中断
- 与第三方对接的异常
- 其他影响功能使用的异常(NPE/DB异常等)
warn:需要引起重视,但是不需要马上处理的问题。例如
- 有容错机制的异常
- 性能接近临界值
- 找不到配置文件,但是有默认值(参考第一条,许多框架也是这么做的)
- 业务异常
- 其他
info:记录用户/系统行为
- 对外提供的接口入口处:打印接口的唯一标识和简短描述,并且要将调用方传入的参数原样打印出来,这样当系统出现问题时,就能很容易的判断出是否是调用方出现了问题
- 系统操作:读写文件,定时任务
- 不符合业务逻辑预期:打印关键的参数,要能从这些参数中清楚地看出,谁的操作与预期不符,为什么与预期不符。
- 调用其它接口的前后:打印所调用接口的系统名称/接口名称和传入参数/响应参数,这样能方便做问题定界,通过这两条日志可以清楚地看出是否是所调用的系统出现了问题
- 系统模块的入口和出口处:重要方法级或模块级,记录它的输入与输出,方便定位
- 非预期执行:为程序在“有可能”执行到的地方打印日志
- switch case语句块中的default
- if…else if…else中很少出现的else情况
- try catch语句块中catch分支。
- 服务状态变化(尽可能记录线索):程序中重要的状态信息的变化应该记录下来,方便查问题时还原现场,推断程序运行过程
- 一些可能很耗时的业务处理:批处理,IO操作
debug:方便排查问题的信息,一般线上不允许存储该级别的日志
- 开发和测试人员都能看懂
- 不需要重现问题,直接就能定位问题
日志内容
必要前缀
- 时间:时区,毫秒
- 进程ID
- 线程ID,在多线程时非常重要
- 日志级别
- 模块,哪个类;如果是微服务,指明哪个服务ID或者名字
- host,机器ip/name
自定义标签
- traceId
- 上下文重要标签信息等
日志消息体
- 日志表述的内容
- filter:用于查找时方便,自定义。(比如可以给重要的日志编号,这样搜索日志会很方便,但是要注意编码要尽量不与其他数据冲突,比如123就是不好的编号)
- 会话信息(用户,登录账号,session),其他信息,比如状态信息(开始,中断,结束),版本号
- 案发现场上下文信息和异常堆栈信息
日志注意事项
- 不要打印敏感信息,比如密码、身份证号、银行卡号等等,避免敏感信息泄露
- 不要使用System.err 打印日志,这是同步的。线上高qps下容易造成阻塞,引发性能问题
- 不要打印大量的日志,一般一个服务一小时500M已经算比较多了。
- 使用logger.xxx()时使用参数化日志特性,不要进行字符串拼接。这样可以提高在关闭某一级别日志时系统的性能。因为如果使用字符串拼接,那么无论如何对象的toString()方法都会执行;而如果使用参数化打印,某一级别的日志不打印时,是不会执行toString()方法的。
public void info(String format, Object arg) {
if (logger.isLoggable(Level.INFO)) { //如果不打印info,直接就跳过了
FormattingTuple ft = MessageFormatter.format(format, arg);
log(SELF, Level.INFO, ft.getMessage(), ft.getThrowable());
}
}
- 打印异常时的做法
logger.error("filter. message. params = {}.",params.e)
。不要只打印e.getMessage(),也不要使用e.printStackTrace(),它是打印到System.err中的 - 不要在打印日志的时候出现异常。例如
logger.info("msg. id = {}".dto.getId())
.dto为null时会抛NPE - 使用日志门面框架,例如SLF4J,不要直接使用某一个具体日志类
- 一个项目底层只依赖一个实现,其他的日志类需要全部排除。