1、简介
项目开发中,记录错误日志有以下好处:
- 方便调试
- 便于发现系统运行过程中的错误
- 存储业务数据,便于后期分析
在java中,记录日志有很多种方式:
- 自己实现
自己写类,将日志数据,以io操作方式,写数据到文本文件、数据库中。
- 使用log4j
log4j可以将日志输出到console窗口、文本文件、数据库等,功能强大!
- 使用slfj
slfj也是一个很强大的功能,slfj旨在一统天下,提供了logging.jar 和 log4j的接口,可以通过slfj来调用log4j,也可以调用jdk的logging。
最先出现的是 Apache 开源社区的 Log4j,这个日志确实是应用最广泛的日志工具,成为了 Java 日志的事实上的标准。
然而,当时 Java 的开发主体 Sun 公司认为自己才是正统,在 JDK 1.4 中增加了 JUL(在 java.util.logging 包下)日志实现,企图对抗 Log4j,但是却造成了 Java 目前开发者记录日志局面的混乱,迄今为止仍饱受诟病。
当然也有其他日志工具的出现,基本都是各自为政,这些日志系统互相没有关联。
为了搞定这个日常开发中比较棘手的问题,Apache 开源社区提供了一个日志框架作为日志的抽象,叫 commons-logging,也被称为 JCL(Java Common Logging),JCL 对各种日志接口进行抽象,抽象出一个接口层,对每个日志实现都进行适配,这样这些提供给别人的库都直接使用抽象层即可,确实出色地完成了兼容主流的日志实现(Log4j、JUL、Simplelog 等),较好的解决了上述问题,基本一统江湖,就连顶顶大名的 Spring 也是依赖了 JCL。
但是美好的日子并不长,作为元老级日志 Log4j 的作者(Ceki Gülcü),他觉得 JCL 不够优秀,所以他再度出山,搞出了一套更优雅的日志框架 SLF4J(这个也是抽象层),即简单日志门面(Simple Logging Facade for Java),并为 SLF4J 实现了一个亲儿子——logback,确实更加优雅了。
最后,Ceki Gülcü 觉得还是得照顾下自己的 “大儿子”——Log4j,又把 Log4j 进行了改造,就是所谓的 Log4j2,同时支持 JCL 及 SLF4J。
SLF4J 的出现,又使 Java 日志体系变得混乱起来。
日志库 Log4j、JUL、Logback 是互相不兼容的,没有共同的 Interface,因此 commons-logging、SLF4J 通过适配器模式,抽象出来一个共同的接口,然后根据使用的具体日志框架来实现日志。
java common logging 和 SLF4J 都是日志的接口,供用户使用,而没有提供实现,Log4j、JUL、Logback 等才是日志的真正实现。
当我们调用日志接口时,接口会自动寻找恰当的实现,返回一个合适的实例给我们服务,这些过程都是透明化的,用户不需要进行任何操作。
工具 | 官方网站 |
---|---|
Log4j | http://logging.apache.org/log4j/1.2 |
JCL | http://commons.apache.org/proper/commons-logging/ |
SLF4J | http://www.slf4j.org |
Logback | http://logback.qos.ch |
Log4j2 | https://logging.apache.org/log4j/2.x/ |
2、不同框架的java日志
2.1 log4j
Log4j 介绍
Log4j(Log for Java)是 Apache 的一个开源项目,通过使用 Log4j,可以控制日志信息输出到日志文件,也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用程序的代码。
Log4j 使用步骤说明
(1)选择 jar 包
(2)log4j.properties 配置
Log4j 有三个主要组件:记录器、Appender 和布局,这三种类型的组件协同工作,使开发人员能够根据消息类型和级别记录消息,并在运行时控制这些消息的格式和报告位置。
记录器:Log4j 建议只使用四个级别,优先级从高到低分别是 ERROR、WARN、INFO、DEBUG。ALL,打印所有的日志;OFF,关闭所有的日志输出。
appender:就是指定日志信息输出到哪个地方,可同时指定多个输出目的地。
布局:可以灵活设置如何输出日志内容。
#配置根 Logger #改代码表示输输出 info 级别以上的日志,文件分别输出,一个是 file,一个是 error log4j.rootLogger=info,file,error #配置 file 日志信息输出目的地 Appender #定义名为 file 的输出端是每天产生一个日志文件 log4j.appender.file=org.apache.log4j.DailyRollingFileAppender #指定日志信息的最低输出级别位 INFO,默认为 DEBUG log4j.appender.file.Threshold=INFO #指定当前消息输出到 jpm/log4j/log.log 文件中 log4j.appender.file.File=/jpm/log4j/log.log #指定按天来滚动日志文件 log4j.appender.file.DatePattern=yyyy-MM-dd #配置日志信息的格式(布局)Layout 是可以灵活地指定布局模式 log4j.appender.file.layout=org.apache.log4j.PatternLayout #格式化日志,Log4j 采用类似 C 语言中的 printf 函数的打印格式格式化日志信息 log4j.appender.file.layout.ConversionPattern=[%d{yyyy-MM-ddHH:mm:ss}][%-5p][jpm-%c{1}-%M(%L)]-%m%n #指定输出信息的编码 log4j.appender.file.encoding=UTF-8 #配置 error 日志信息输出目的地 Appender #定义名为 error 的输出端是每天产生一个日志文件 log4j.appender.error=org.apache.log4j.DailyRollingFileAppender #指定日志信息的最低输出级别位 ERROR,默认为 DEBUG log4j.appender.error.Threshold=ERROR #指定当前消息输出到 jpm/log4j/error.log 文件中 log4j.appender.error.File=/jpm/log4j/error.log #指定按月来滚动日志文件 log4j.appender.error.DatePattern=yyyy-MM #配置日志信息的格式(布局)Layout 是可以灵活地指定布局模式 log4j.appender.error.layout=org.apache.log4j.PatternLayout #格式化日志,Log4j 采用类似 C 语言中的 printf 函数的打印格式格式化日志信息 log4j.appender.error.layout.ConversionPattern=[%d{yyyy-MM-ddHH:mm:ss}][%-5p][jpm-%c{1}-%M(%L)]-%m%n #指定输出信息的编码 log4j.appender.error.encoding=UTF-8 #使某个功能的日志单独输出到指定的日志文件 log4j.logger.saveUserLog=INFO,saveUserLog #该配置就是让 job 的日志只输出到自己指定的日志文件中,表示 Logger 不会在父 Logger 的 appender 里输出,默认为 true log4j.additivity.saveUserLog=false log4j.appender.saveUserLog=org.apache.log4j.DailyRollingFileAppender log4j.appender.saveUserLog.File=/jpm/log4j/saveUserLog.log log4j.appender.saveUserLog.DatePattern=yyyy-MM-dd log4j.appender.saveUserLog.Append=true log4j.appender.saveUserLog.layout=org.apache.log4j.PatternLayout log4j.appender.saveUserLog.layout.ConversionPattern=%m%n log4j.appender.error.encoding=UTF-8
Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,打印参数如下: %m 输出代码中指定的消息
%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%r 输出自应用启动到输出该log信息耗费的毫秒数 %c 输出所属的类目,通常就是所在类的全名 %t 输出产生该日志事件的线程名 %n 输出一个回车换行符,Windows平台为“rn”,Unix平台为“n” %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921 %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
(final Logger logger = Logger.getLogger(TestLog4j.class);传入的类用于获取调用的类%c)
2.2 JUL
较少使用。
2.3 JCL
commons-logging 提供的是一个日志接口,是为那些需要建立在不同环境下使用不同日志架构的组件或库的开发者创建的,
其中包括 log4j 以及 Java Log 的日志架构。commons-logging 有两个基本的抽象类:Log(基本记录器)和 LogFactory(负责创建 Log 实例),
把日志信息抽象成 commons-logging 的 Log 接口,并由 commons-logging 在运行时决定使用哪种日志架构。
因为 Log4j 的强大功能,commons-logging 一般会和 Log4j 一起使用,这几乎成为了 Java 日志的标准工具。
2.4 SLF4j
SLF4J(Simple Logging Facade for Java,Java 简单日志门面)和 commons-loging 一样也是对不同日志框架提供的一个门面封装,
可以在部署的时候不修改任何配置即可接入一种日志实现方案,能支持多个参数,并通过 {} 占位符进行替换。
看这个 Log4J 示例:
Logger.debug("Hello " + name);
由于字符串拼接的问题(注:上述语句会先拼接字符串,再根据当前级别是否低于 Debug 决定是否输出本条日志,即使不输出日志,字符串拼接操作也会执行),因此许多公司一般强制使用下面的语句,这样只有当前处于 DEBUG 级别时才会执行字符串拼接:
if (logger.isDebugEnabled()) {
LOGGER.debug(“Hello ” + name);
}
它避免了字符串拼接问题,可是有点太繁琐了,而 SLF4J 提供下面这样简单的语法:
LOGGER.debug("Hello {}", name);
它的形式类似第一条示例,但是又没有字符串拼接问题,也不像第二条那样繁琐。
正是因为 SLF4J 的这个占位符功能,使得人们越来越多的使用 SLF4J 这个接口用到实际开发项目中。
5. Logback 使用示例
LogBack 介绍
LogBack 和 Log4j 都是开源日记工具库,LogBack 是 Log4j 的改良版本,比 Log4j 拥有更多的特性,同时也带来很大性能提升。LogBack 官方建议配合 Slf4j 使用,这样可以灵活地替换底层日志框架。
Logback 主要由三个模块组成:
- logback-core
- logback-classic
- logback-access
其中 logback-core 提供了 LogBack 的核心功能,是另外两个组件的基础。logback-classic 的地位和作用等同于 Log4J,它也被认为是 Log4J 的一个改进版,并且它实现了简单日志门面 SLF4J,因此当想配合 SLF4J 使用时,需要将 logback-classic 加入 classpath;而 logback-access 主要作为一个与 Servlet 容器交互的模块,比如说 Tomcat 或者 Jetty,提供一些与 HTTP 访问相关的功能。
logback配置
<?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!-- status 用来指定 Log4j 本身的打印日志的级别 --> <!--monitorInterval:Log4j 能够自动检测修改配置文件和重新配置本身,设置间隔秒数 --> <configuration status="WARN" monitorInterval="30"> <!--先定义所有的 appender --> <appenders> <!--这个输出控制台的配置 --> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式 --> <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %logger{36} - %msg%n" /> </console> <!--定义输出到指定位置的文件 --> <File name="log" fileName="/jpm/log4j2/logs/log.log" append="true"> <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %logger{36} - %msg%n" /> </File> <!-- 这个会打印出所有的 info 及以下级别的信息,每次大小超过 size,则这 size 大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 --> <RollingFile name="RollingFileInfo" fileName="/jpm/log4j2/logs/info.log" filePattern="/jpm/log4j2/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log"> <!--控制台只输出 level 及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) --> <!-- DENY,日志将立即被抛弃不再经过其他过滤器; NEUTRAL,有序列表里的下个过滤器过接着处理日志; ACCEPT,日志会被立即处理,不再经过剩余过滤器。 --> <ThresholdFilter level="error" onMatch="DENY" onMismatch="ACCEPT" /> <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %logger{36} - %msg%n" /> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="100 MB" /> </Policies> <!-- DefaultRolloverStrategy 属性如不设置,则默认为最多同一文件夹下 7 个文件,这里设置了 30 --> <DefaultRolloverStrategy max="30" /> </RollingFile> <RollingFile name="RollingFileError" fileName="/jpm/log4j2/logs/error.log" filePattern="/jpm/log4j2/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY" /> <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %logger{36} - %msg%n" /> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="100 MB" /> </Policies> </RollingFile> </appenders> <!--只有定义了 logger 并引入的 appender,appender 才会生效 --> <loggers> <!--过滤掉 spring 和 MyBatis 的一些无用的 DEBUG 信息 --> <logger name="org.springframework" level="INFO"></logger> <logger name="org.mybatis" level="INFO"></logger> <root level="INFO"> <appender-ref ref="Console" /> <appender-ref ref="log" /> <appender-ref ref="RollingFileInfo" /> <appender-ref ref="RollingFileError" /> </root> </loggers> </configuration>
6. Log4j2 使用示例
Log4j2 介绍
Apache Log4j 2 是对 Log4j 的升级,与其前身 Log4j 1.x 相比有了显着的改进,并提供了许多 Logback 可用的改进,同时支持 JCL 及 SLF4J。
Log4j2 使用步骤说明
(1)选择 jar 包
引入 Log4j2 必要的包:log4j-api、log4j-core。
同样使用slf4j
(5)关于日志 level
共有 8 个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
- **All:**最低等级的,用于打开所有日志记录
- **Trace:**是追踪,就是程序推进以下,就可以写个 trace 输出,因此 trace 应该会特别多,不过没关系,我们可以设置最低日志级别不让它输出
- **Debug:**指出细粒度信息事件对调试应用程序是非常有帮助的
- **Info:**消息在粗粒度级别上突出强调应用程序的运行过程
- **Warn:**输出警告及 Warn 以下级别的日志
- **Error:**输出错误信息日志
- **Fatal:**输出每个严重的错误事件将会导致应用程序的退出的日志
- **OFF:**最高等级的,用于关闭所有日志记录
7. JCL(Java Common Logging)+ Log4j 使用示例
JCL(Java Common Logging)+ Log4j 介绍
使用 commons-logging 的 Log 接口,并由 commons-logging 在运行时决定使用哪种日志架构(如 Log4j)。现在,Apache 通用日志工具 commons-logging 和 Log4j 已经成为 Java 日志的标准工具,这个组合是比较常用的一个日志框架组合。
JCL(Java Common Logging)+ Log4j 使用步骤说明
(1)选择 jar 包
commons-logging-1.2 + log4j1.2.17
只需要一行即可,放在 classpath 下,如果是 Maven 中就在 src/resources 下,不过如果没有 common-logging.properties 文件,但是 src 下有 log4j.properties 配置也可以正常的输出 Log4j 设置的日志。
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
package jpm.jcllog4j; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class TestJclAndLog4j { public static void main(String[] args) { final Log LOGGER = LogFactory.getLog(TestJclAndLog4j.class); LOGGER.debug("TestJclAndLog4j debug log."); LOGGER.info("TestJclAndLog4j info log."); LOGGER.error("TestJclAndLog4j error log."); } }
8. SLF4J + Log4j 使用示例
SLF4J + Log4j 介绍
SLF4j + Log4j 与 JCL + Log4J 的使用方式差不多,主要差异就在 SLF4J 用绑定包(slf4j-Log4j12.jar)来告知用哪种日志实现,而 JCL 是通过配置文件来获得该选择哪个日志实现。
package jpm.slf4jlog4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TestSlf4jAndLog4j { public static void main(String[] args) { final Logger LOGGER = LoggerFactory.getLogger(TestSlf4jAndLog4j.class); LOGGER.debug("TestSlf4jAndLog4j debug log:{}", "debug"); LOGGER.info("TestSlf4jAndLog4j info log:{}", "info"); LOGGER.error("TestSlf4jAndLog4j error log:{}", "error"); } }
日志记录原则:
- 注意日志级别,尤其是 info 和 error 不能用混;
- 注意记录信息的准确性,切记日志表达不清楚;
- 注意不同的代码段日志说明不能重复;
- 捕获异常后,要及时记录异常详细信息,并把异常传递到外部;
- 时刻铭记,日志的记录是为了后期查询问题带来方便,因此重要的代码务必要记录日志。
程序片段:
try { LOGGER.info("根据用户编码查询用户信息-开始,userId:{}" , userId); User user = userService.getUserById(userId); LOGGER.info("根据用户编码查询用户信息-结束,userId:{}" , userId); } catch (CustomException e) { LOGGER.error("根据用户编码查询用户信息-自定义异常:{}" , e.getMessage()); throw new CustomException("根据用户编码查询用户信息-自定义异常{}" , e.getMessage(), e); } catch (Exception e) { LOGGER.error("根据用户编码查询用户信息-捕获异常:{}" , e.toString()); throw new ServiceException(根据用户编码查询用户信息-捕获异常:{}" , e.toString(), e); }