之前星球的好友就在说,关于日志的问题,我们是怎么来进行使用和分析的,因为如果日志使用的好,那么我们在程序出现BUG的时候,能够快速的定位,然后找到原因,并且解决,如果使用不好,那么你将无从下手。下面我就就来说说日志的那点事!
之前很多人在介绍AOP的时候,通常AOP使用在哪些地方,脱口就是日志和事务,但是,实际上日志要采用切面的话是极其不科学的!对于日志来说,只是在方法开始、结束、异常时输出一些什么,那是绝对不够的,这样的日志对于日志分析没有任何意义。如果在方法的开始和结束整个日志,那方法中呢?如果方法中没有日志的话,那就完全失去了日志的意义!如果应用出现问题要查找由什么原因造成的,也没有什么作用。这样的日志还不如不用!
日志级别
- DEBUG
- INFO
- WARN
- ERROR
这是日志的四个最经常用的,还有四个是不怎么用到的,我就不再去介绍他们了,我们主要来说这四个日志级别。
DEBUG
DEBUG级别的主要输出调试性质的内容,该级别日志主要用于在开发、测试阶段输出。该级别的日志应尽可能地详尽,便于在开发、测试阶段出现问题或者异常时,对其进行分析。
INFO
INFO 级别的主要输出提示性质的内容,该级别日志主要用于生产环境的日志输出。该级别或更高级别的日志不要出现在循环中,可以在循环开始或者结束后输出循环的次数,以及一些其他重要的数据。
INFO 级别日志原则是在生产环境中,通过 INFO 和更高级别的日志,可以了解系统的运行状况,以及出现问题或者异常时,能快速地对问题进行定位,还原当时调用的上下文数据,能重现问题,那么INFO级别的日志主要是用于哪些方面呢?
1. 应用启动时所加载的配置参数值(比如:连接参数、线程池参数、超时时间等,以及一些与环境相关的配置,或者是整个配置参数)
2. 一些重要的依赖注入对象的类名
3. 方法(服务方法)的输入参数值、返回值,由于一些方法入参的值非常多,只在入口处输出一次就可以了,在服务方法内部或者调用非服务方法时就不需要再输出了
4. 方法中重要的部分,比如:从数据库中所获取较为重要的数据,以及调用第三方接口的输入参数值和接口返回值
这个INFO应该用来反馈系统的当前状态给最终用户的,所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。
从某种角度上说,INFO 输出的信息可以看作是软件产品的一部分(就像那些交互界面上的文字一样),所以需要谨慎对待,不可随便。
此输出级别常用语业务事件信息。例如某项业务处理完毕,或者业务处理过程中的一些信息。
此输出级别也常用于输出一些对系统有比较大的影响的需要被看到的message,例如数据库更新,系统发送了额外的请求等。
WARN
WARN 级别的主要输出警告性质的内容,这些内容是可以预知且是有规划的,比如,某个方法入参为空或者该参数的值不满足运行该方法的条件时。在 WARN 级别的时应输出较为详尽的信息,以便于事后对日志进行分析,不要直接写成:
log.warn( "name is null" );
因为我们除了输出警告的原因之外,还需要将其他参数内容都输出,以便于有更多的信息供为日志分析的参考。
我们可以写成这个样子的
log.warn( "[{}] name is null, ignore the method, arg0: {}, arg1: {}" );
ERROR
ERROR 级别主要针对于一些不可预知的信息,诸如:错误、异常等,比如,在 catch 块中抓获的网络通信、数据库连接等异常,若异常对系统的整个流程影响不大,可以使用 WARN 级别日志输出。在输出 ERROR 级别的日志时,尽量多地输出方法入参数、方法执行过程中产生的对象等数据,在带有错误、异常对象的数据时,需要将该对象一并输出.
这个时候也就是说,发生了必须马上处理的错误。此类错误出现以后可以允许程序继续运行,但必须马上修正,如果不修正,就会导致不能完成相应的业务。
基本的日志规范
其实说到这里,有些人会有一些疑问,日志还需要规范?是的,你没听错,日志需要规范,下面我们来说一下这个日志规范。
1. 在一个对象中通常只使用一个Logger对象,Logger应该是static final的,只有在少数需要在构造函数中传递logger的情况下才使用private final。
static final Logger_LOG = LoggerFactory.getLogger(Test.class)
2. 输出Exceptions的全部Throwable信息,因为logger.error(msg)和logger.error(msg,e.getMessage())这样的日志输出方法会丢失掉最重要的StackTrace信息。
try(){ ... }catch(Exception e){ Log.error("xxx",e); }
3. 不允许记录日志后又抛出异常,因为这样会多次记录日志,只允许记录一次日志。
try(){ ... }catch(Exception e){ Log.error("xxx",e); thorw new LogException("xxx",e); 这是错误的 }
4. 日志性能的考虑,如果代码为核心代码,执行频率非常高,则输出日志建议增加判断,尤其是低级别的输出<debug、info、warn>。
以上这四点其实就是日志输出的一些规范,其实还有,比如说不要出现printStackTrace这种等等的内容。
日志文件格式
日志文件格式其实这个大部分人都会明白,现在进行的就是.log,昨天的就是日期加.log,规范就是这样的
日志文件放置于固定的目录中,按照一定的模板进行命名,推荐的日志文件名称:
- 当前正在写入的日志文件名:<应用名>[-<功能名>].log
- 已经滚入历史的日志文件名:<应用名>[-<功能名>].log.<yyyy-MM-dd>
Java日志框架
Java的日志框架比较多,我们说几个比较常用的
- Log4j 或 Log4j 2
- Logback
- java.util.logging
- Slf4j
- Apache Commons Logging
Log4j是Apache的开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;用户也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,用户能够更加细致地控制日志的生成过程。这些可以通过一个配置文件(XML或Properties文件)来灵活地进行配置,而不需要修改程序代码。Log4j 2则是前任的一个升级,参考了Logback的许多特性;
Logback是由log4j创始人设计的又一个开源日记组件。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日记系统如log4j或JDK14 Logging;
java.util.logging 是JDK内置的日志接口和实现,功能比较简单;
SLF4J是为各种Logging API提供一个简单统一的接口),从而使用户能够在部署的时候配置自己希望的Logging API实现;
Apache Commons Logging (JCL)希望解决的问题和Slf4j类似。
选项太多了的后果就是选择困难症,我的看法是没有最好的,只有最合适的。在比较关注性能的地方,选择Logback或自己实现高性能Logging API可能更合适;在已经使用了Log4j的项目中,如果没有发现问题,继续使用可能是更合适的方式;我一般会在项目里选择使用Slf4j, 如果不想有依赖则使用java.util.logging或框架容器已经提供的日志接口。
日志优先级
优先级从高到低依次为: ERROR WARN INFO DEBUG
如果将log level设置在某一个级别上 那么比此级别优先级高的log都能打印出来例如 如果设置优先级为WARN 那么 ERROR WARN 2个级别的log能正常输出,而INFO DEBUG 级别的log则会被忽略。
所以选对合适的日志级别才是你应该做的,你学会了么?
我是懿,一个正在被打击还在努力前进的码农。欢迎大家关注我们的公众号,加入我们的知识星球,我们在知识星球中等着你的加入。