Java如何合理打印日志
日志:记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。
一、应用程序日志的概述
通常,Java程序员在开发项目时都是依赖Eclipse/IDEA等集成开发工具的Debug 调试功能来跟踪解决Bug,但项目发布到了测试、生产环境怎么办?你有可能会说可以使用远程调试,但实际并不能允许让你这么做。
所以,日志的作用就是在测试、生产环境没有 Debug 调试工具时开发和测试人员定位问题的手段。日志打得好,就能根据日志的轨迹快速定位并解决线上问题,反之,日志输出不好,不仅无法辅助定位问题反而可能会影响到程序的运行性能和稳定性。
很多介绍 AOP 的地方都采用日志来作为介绍,实际上日志要采用切面的话是极其不科学的!对于日志来说,只是在方法开始、结束、异常时输出一些什么,那是绝对不够的,这样的日志对于日志分析没有任何意义。
如果在方法的开始和结束整个日志,那方法中呢?如果方法中没有日志的话,那就完全失去了日志的意义!如果应用出现问题要查找由什么原因造成的,也没有什么作用。这样的日志还不如不用!
二、日志的作用
不管是使用何种编程语言,日志输出几乎无处不再。总结起来,日志大致有以下几种用途:
- 「问题追踪」:辅助排查和定位线上问题,优化程序运行性能。
- 「状态监控」:通过日志分析,可以监控系统的运行状态。
- 「安全审计」:审计主要体现在安全上,可以发现非授权的操作。
三、记录日记的时机
在看线上日志的时候,我们可曾陷入到日志泥潭?该出现的日志没有,无用的日志一大堆,或者需要的信息分散在各个角落,特别是遇到紧急的在线bug时,有效的日志被大量无意义的日志信息淹没,焦急且无奈地浪费大量精力查询日志。那什么是记录日志的合适时机呢?
总结几个需要写日志的点:
- 「编程语言提示异常」:如今各类主流的编程语言都包括异常机制,业务相关的流行框架有完整的异常模块。这类捕获的异常是系统告知开发人员需要加以关注的,是质量非常高的报错。应当适当记录日志,根据实际结合业务的情况使用warn或者error级别。
- 「业务流程预期不符」:除开平台以及编程语言异常之外,项目代码中结果与期望不符时也是日志场景之一,简单来说所有流程分支都可以加入考虑。取决于开发人员判断能否容忍情形发生。常见的合适场景包括外部参数不正确,数据处理问题导致返回码不在合理范围内等等。
- 「系统核心角色,组件关键动作」:系统中核心角色触发的业务动作是需要多加关注的,是衡量系统正常运行的重要指标,建议记录INFO级别日志,比如电商系统用户从登录到下单的整个流程;微服务各服务节点交互;核心数据表增删改;核心组件运行等等,如果日志频度高或者打印量特别大,可以提炼关键点INFO记录,其余酌情考虑DEBUG级别。
- 「系统初始化」:系统或者服务的启动参数。核心模块或者组件初始化过程中往往依赖一些关键配置,根据参数不同会提供不一样的服务。务必在这里记录INFO日志,打印出参数以及启动完成态服务表述。
四、日志打印最佳实践
1、日志变量定义
日志变量往往不变,最好定义成final static,变量名用大写。
private static final Logger log = LoggerFactory.getLogger({SimpleClassName}.getClass());
通常一个类只有一个 log
对象,如果有父类可以将 log
定义在父类中。
日志变量类型定义为门面接口(如 slf4j 的 Logger),实现类可以是 Log4j、Logback 等日志实现框架,不要把实现类定义为变量类型,否则日志切换不方便,也不符合抽象编程思想。
另外,推荐引入lombok的依赖,在类的头部加上@Slf4j
的注解,之后便可以在程序的任意位置使用log
变量打印日志信息了,使用起来更加简洁一点,在重构代码尤其是修改类名的时候无需改动原有代码。
2、参数占位符格式
使用参数化形式{}占位,[]进行参数隔离
String username = linhuaming; String password = 123456; log.info(参数 username:[{}],参数 password:[{}],username,password);
3、日志的基本格式
日志输出主要在文件中,应包括以下内容:
- 日志时间
- 日志级别主要使用
- 调用链标识(可选)
- 线程名称
- 日志记录器名称
- 日志内容
- 异常堆栈(不一定有)
4、日志文件
日志文件放置于固定的目录中,按照一定的模板进行命名,推荐的日志文件名称:
当前正在写入的日志文件名:<应用名>[-<功能名>].log 已经滚入历史的日志文件名:<应用名>[-<功能名>].log.<yyyy-MM-dd>
5、日志配置
根据不同的环境配置不同的日志输出方式:
- 本地调试可以将日志输出到控制台上
- 测试环境或者生产环境输出到文件中,每天产生一个文件,如果日志量庞大可以每个小时产生一个日志文件
- 生产环境中的文件输出,可以考虑使用异步文件输出,该种方式日志并不会马上刷新到文件中去,会产生日志延时,在停止应用时可能会导致一些还在内存中的日志未能及时刷新到文件中去而产生丢失,如果对于应用的要求并不是非常高的话,可暂不考虑异步日志
logback 日志工具可以在日志文件滚动后将前一文件进行压缩,以减少磁盘空间占用,若使用 logback 对于日志量庞大的应用建议开启该功能。
6、日志使用规范
1、输出Exceptions的全部Throwable信息。
void foo(){ try{ //do somehing }catch(Exception e){ log.error(e.getMessage());//错误示范 log.erroe(Bad Things,e.getMessage());//错误示范 log.error(Bad Things,e);//正确演示 } }
2、不允许记录日志后又抛出异常。
void foo() throws LogException{ try{ //do somehing }catch(Exception e){ log.error(Bad Things,e);//正确 throw new LogException(Bad Things,e); } }
3、不允许使用标准输出 Syste.out.println。
void foo(){ try{ //do somehing }catch(Exception e){ Syste.out.println(e.getMessage());//错误 System.error.println(e.getMessage());//错误 log.error(Bad Things,e);//正确 } }
4、不允许出现printStackTrace。
void foo(){ try{ //do somehing }catch(Exception e){ e.printStacktrace();//错误 log.error(Bad Things,e);//正确 } } public void printStackTrace() { printStackTrace(System.err); }
5、不要在大循环中打印日志。
for(int i=0; i<2000; i++){ log.info(XX); }
参考: