# Java日志规范
## 前言
一个在生产环境里运行的程序如果没有日志是很让维护者提心吊胆的,有太多杂乱又无意义的日志也是令人伤神。程序出现问题时候,从日志里如果发现不了问题可能的原因是很令人受挫的。本文想讨论的是如何在Java程序里写好日志。
一般来说日志分为两种:业务日志和异常日志,使用日志我们希望能达到以下目标:
1. 对程序运行情况的记录和监控;
2. 在必要时可详细了解程序内部的运行状态;
3. 对系统性能的影响尽量小
## Java日志框架
Java的日志框架太多了。。。
1. [**Log4j**](http://logging.apache.org/) 或 [**Log4j 2**](http://logging.apache.org/log4j/2.x/) - Apache的开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;用户也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,用户能够更加细致地控制日志的生成过程。这些可以通过一个配置文件(XML或Properties文件)来灵活地进行配置,而不需要修改程序代码。Log4j 2则是前任的一个升级,参考了Logback的许多特性;
2. [**Logback**](http://logback.qos.ch/) - Logback是由log4j创始人设计的又一个开源日记组件。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日记系统如log4j或JDK14 Logging;
3. [**java.util.logging**](http://docs.oracle.com/javase/6/docs/api/java/util/logging/package-summary.html) - JDK内置的日志接口和实现,功能比较简;
4. [**Slf4j**](http://www.slf4j.org/) - SLF4J是为各种Logging API提供一个简单统一的接口),从而使用户能够在部署的时候配置自己希望的Logging API实现;
5. [**Apache Commons Logging**](http://commons.apache.org/proper/commons-logging/) - Apache Commons Logging (JCL)希望解决的问题和Slf4j类似。
选项太多了的后果就是选择困难症,我的看法是没有最好的,只有最合适的。在比较关注性能的地方,选择Logback或自己实现高性能Logging API可能更合适;在已经使用了Log4j的项目中,如果没有发现问题,继续使用可能是更合适的方式;我一般会在项目里选择使用Slf4j, 如果不想有依赖则使用java.util.logging或框架容器已经提供的日志接口。
## 3.Java日志最佳实践
### 定义日志变量
日志变量往往不变,最好定义成**final static**,变量名用**大写**。
### 日志分级
Java的日志框架一般会提供以下日志级别,缺省打开info级别,也就是debug,trace级别的日志在生产环境不会输出,在开发和测试环境可以通过不同的日志配置文件打开debug级别。
1. **fatal** - 严重的,造成服务中断的错误;
2. **error** - 其他错误运行期错误;
3. **warn** - 警告信息,如程序调用了一个即将作废的接口,接口的不当使用,运行状态不是期望的但仍可继续处理等;
4. **info** - 有意义的事件信息,如程序启动,关闭事件,收到请求事件等;
5. **debug** - 调试信息,可记录详细的业务处理到哪一步了,以及当前的变量状态;
6. **trace** - 更详细的跟踪信息;
在程序里要合理使用日志分级:
~~~
1 LOGGER.debug("entering getting content");
2 String content =CacheManager.getCachedContent();
3 if(content == null){
4
5 //使用warn,因为程序还可以继续执行,但类似警告太多可能说明缓存服务不可用了,值得注意
6 LOGGER.warn("Got empty content from cache,need perform database lookup");
7
8 Connection conn = ConnectionFactory.getConnection();
9 if (conn=null) {
10 LOGGER.error("Can't get database connection, failed to return content");//尽量提供详细的信息,知道错误的原因,而不能简单的写logger.error("failed")
11 }else{
12 try{
13 content = conn.query(...);
14 }catch ( IOException e ){
15 //异常要记录错误堆栈
16 LOGGER.error("Failed to perform database lookup", e );
17 }finally{
18 ConnectionFactory.releaseConnection(conn);
19 }
20 }
21 }
22 //调试的时候可以知道方法的返回了
23 LOGGER.debug("returning content: "+ content);
24 return content;
~~~
### 基本的Logger编码规范
1. 在一个对象中通常只使用**一个**Logger对象,Logger应该是**static final**的,只有在少数需要在构造函数中传递logger的情况下才使用private final。
~~~
static final logger_LOG=loggerFactory.getLogger(Main.class);
~~~
2. 输出Exceptions的**全部**Throwable信息,因为logger.error(msg)和logger.error(msg,e.getMessage())这样的日志输出方法会丢失掉最重要的StackTrace信息。
~~~
void foo(){
try {
// do something...
}catch ( Exception e ){
_LOG.error(e.getMessage()); // 错误
_LOG.error("Bad things : ",e.getMessage()); // 错误
_LOG.error("Bad things : ",e); // 正确
}
}
~~~
3. **不允许**记录日志后又抛出异常,因为这样会多次记录日志,**只允许**记录一次日志。
~~~
void foo() throws LogException{
try{
// do something...
}catch ( Exception e ){
_LOG.error("Bad things : ", e);
throw new LogException("Bad things : ",e);
}
}
~~~
4. **不允许**出现System print(包括System.out.println和System.error.println)语句。
~~~
void foo() {
try{
// do something...
}catch( Exception e ){
System.out.println(e.getMessage()); // 错误
System.err.println(e.getMessage()); // 错误
_LOG.error("Bad things : ",e ); // 正确
}
}
~~~
5. **不允许**出现printStackTrace。
~~~
void foo() {
try {
// do something...
}catch ( Exception e ) {
e.printStackTrace(); // 错误
_LOG.error("Bad things : ", e ); //正确
}
}
~~~
6. 日志性能的考虑,如果代码为核心代码,执行频率非常高,则输出日志建议增加判断,尤其是低级别的输出。
debug日志太多后可能会影响性能,有一种改进方法是:
~~~
if (LOGGER.isDebugEnabled ()) {
LOGGER.debug("returning content: "+ content);
}
~~~
但更好的方法是Slf4j提供的[最佳实践](http://www.slf4j.org/faq.html#logging_performance):
~~~
LOGGER.debug("returning content: {}", content);
~~~
一方面可以减少参数构造的开销,另一方面也不用多写两行代码。
7. **有意义**的日志
通常情况下在程序日志里记录一些比较有意义的状态数据:程序启动,退出的时间点;程序运行消耗时间;耗时程序的执行进度;重要变量的状态变化。
除此之外,在公共的日志里**规避**打印程序的调试或者提示信息。
- java
- 设计模式
- 设计模式总览
- 设计原则
- 工厂方法模式
- 抽象工厂模式
- 单例模式
- 建造者模式
- 原型模式
- 适配器模式
- 装饰者模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代子模式
- 责任链模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
- 附录
- JVM相关
- JVM内存结构
- Java虚拟机的内存组成以及堆内存介绍
- Java堆和栈
- 附录-数据结构的堆栈和内存分配的堆区栈区的区别
- Java内存之Java 堆
- Java内存之虚拟机和内存区域概述
- Java 内存之方法区和运行时常量池
- Java 内存之直接内存(堆外内存)
- JAVA内存模型
- Java内存模型介绍
- 内存模型如何解决缓存一致性问题
- 深入理解Java内存模型——基础
- 深入理解Java内存模型——重排序
- 深入理解Java内存模型——顺序一致性
- 深入理解Java内存模型——volatile
- 深入理解Java内存模型——锁
- 深入理解Java内存模型——final
- 深入理解Java内存模型——总结
- 内存可见性
- JAVA对象模型
- JVM内存结构 VS Java内存模型 VS Java对象模型
- Java的对象模型
- Java的对象头
- HotSpot虚拟机
- HotSpot虚拟机对象探秘
- 深入分析Java的编译原理
- Java虚拟机的锁优化技术
- 对象和数组并不是都在堆上分配内存的
- 垃圾回收
- JVM内存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用实例介绍
- JVM内存回收理论与实现(对象存活的判定)
- JVM参数及调优
- CMS GC日志分析
- JVM实用参数(一)JVM类型以及编译器模式
- JVM实用参数(二)参数分类和即时(JIT)编译器诊断
- JVM实用参数(三)打印所有XX参数及值
- JVM实用参数(四)内存调优
- JVM实用参数(五)新生代垃圾回收
- JVM实用参数(六) 吞吐量收集器
- JVM实用参数(七)CMS收集器
- JVM实用参数(八)GC日志
- Java性能调优原则
- JVM 优化经验总结
- 面试题整理
- 面试题1
- java日志规约
- Spring安全
- OAtuth2.0简介
- Spring Session 简介(一)
- Spring Session 简介(二)
- Spring Session 简介(三)
- Spring Security 简介(一)
- Spring Security 简介(二)
- Spring Security 简介(三)
- Spring Security 简介(四)
- Spring Security 简介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security对比
- Shiro简介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux