日志
概述
日志文件是用于记录系统操作时间的文件集合。可分为事件日志和消息日志。具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要作用。
-
调试日志
开发中使用日志能够更加灵活和方便的去重现开发中的的bug问题。
-
系统日志
系统日志是记录系统中硬件、软件和系统问题的信息,还可以监视系统中发生的事件。可以通过它来检查错误发生的原因,或者寻找受到攻击时攻击者留下的痕迹。
java日志框架
日志门面 : JCL 、 slf4j
日志实现 : JUL 、 logback 、 log4j 、 log4j2
日志门面
借鉴于JDBC的思想,为日志系统提供一套门面,可以面向这些接口规范进行开发,避免直接依赖具体的日志框架。
用户通过控制日志门面提供的日志接口实现具体日志框架技术
SLF4J
1. 依赖
<!-- slf4j-core 使用slf4j门面核心 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<!-- slf4j 自带的简单日志实现 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
2. 测试代码
public class slf4jTest {
public final static Logger LOGGER = LogerFactory.getLogger(slf4jTest.class);
@Test
public void test() throws Exception {
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
//使用占位符输出日志
String name=cccq;
Integer age=18;
LOGGER.info("User:{},{}",name,age);
}
}
3. 绑定日志的实现
SLF4J附带几个SLF4J绑定的jar文件,每个绑定对应一个受支持的框架
- 绑定流程
- 添加slf4j-api的依赖
- 使用slf4j的API在项目中进行统一的日志记录
- 绑定具体的日志实现框架
- 绑定已经实现了slf4j的日志框架,直接添加对应依赖
- 绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖
slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)
-
依赖
<<!-- slf4j-core 使用slf4j门面核心 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <!-- log4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.30</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- jul --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.5.6</version> </dependency> <!-- jcl --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jcl</artifactId> <version>1.7.30</version> </dependency> <!-- 不使用日志实现 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.30</version> </dependency>
要切换日志框架,只需替换类路径上的slf4j绑定。 例:从java.util.logging切换到log4j,只需将依赖 slf4j-jdk14 改为 slf4j-log4j12
4. 桥接旧的日志框架
从旧的其他日志实现升级到使用slf4j日志门面,slf4j附带了几个桥接模块,这些模块将对log4j,JCL和java.util.logging的调用重定向,就好像他们是对SLF4J一样
-
步骤 :
- 去除之前老的日志框架的依赖
- 添加SLF4J提供的桥接组件
- 为项目添加SLF4J的具体实现
-
依赖
<!-- log4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>1.7.30</version> </dependency> <!-- jul --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>1.5.6</version> </dependency> <!-- jcl --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.30</version> </dependency>
-
注意问题:
- jcl-over-slf4j 和 slf4j-jcl 不能同时部署,前一个jar将导致jcl将日志系统的选择委托给SLF4J,后一个jar文件将导致SLF4J将日志系统的选择委托给JCL,从而导致死循环
- log4j-over-slf4j 和 slf4j-log4j12 不能同时出现
- jul-over-slf4j 和 slf4j-jdk14 不能同时出现
- 所有的桥接都只对Logger日志记录器对象有效,如果程序中调用了内部的配置类或者是Appender,Filter对象,将无法产生效果
5. 原理解析
- SLF4J通过LoggerFactory加载日志具体的实现对象
- LoggerFactory在初始化的过程中,会通过performInitialization()方法绑定具体的日志实现
- 在绑定具体实现的时候,通过类加载器,加载org/slf4j/impl/StaticLoggerBinder.class
- 所以,只要是一个日志实现框架,在org.slf4j.impl包中提供一个自己的StaticLoggerBinder类,在其中提供具体日志实现的LoggerFactory就可以呗SLF4J所加载
logback
由log4j的创始人设计的另一个开源日志组件,性能强于log4j
主要分为三个模块: 1. logback-core : 其他两个模块的基础模块 2. logback-classic : 是log4j的一个改良版本,同时它完整实现了slf4j API 3. logback-access : 访问模块与Servlet容器继承提供通过Http来访问日志功能
1. 依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
2. 配置
logback会依次读取以下类型配置文件
- logback.groovy
- logback-test.xml
- logback.xml
如果均不存在会采取默认配置
-
logback组件之间的关系
- Logger:日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。
- Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。
- Layout:负责把事件转换成字符串,格式化的日志信息的输出。在logback中Layout对象被封装在encoder中。
-
基本配置信息
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!--日志输出格式: %-5level 级别从左显示5个字符宽度 %d{yyyy-MM-dd HH:mm:ss.SSS} 日期 %c 类的完整名称 %M 为method %L 为行号 %thread 线程名称 %m或者%msg 为信息 %n 换行 --> <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/> <!--Appender: 设置日志信息的去向,常用的有以下几个 ch.qos.logback.core.ConsoleAppender (控制台) ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺 寸的时候产生一个新文件) ch.qos.logback.core.FileAppender (文件) --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--输出流对象形式 默认 System.out 改为 System.err--> <target>System.err</target> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!-- 用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。 <loger>仅有一个name属性,一个可选的level和一个可选的addtivity属性 name:用来指定受此logger约束的某一个包或者具体的某一个类 level: 用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF 如果未设置此属性,那么当前logger将会继承上级的级别。 additivity: 是否向上级loger传递打印信息。默认是true。 <logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个 logger --> <!-- 也是<logger>元素,但是它是根logger。默认debug level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, <root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个 logger --> <root level="ALL"> <appender-ref ref="console"/> </root> </configuration>
-
FileAppender配置
在logback.xml
<!-- 日志文件存放目录 --> <property name="log_dir" value="d:/logs"></property> <!--日志文件输出appender对象--> <appender name="file" class="ch.qos.logback.core.FileAppender"> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> <!--日志输出路径--> <file>${log_dir}/logback.log</file> </appender> <!-- 生成html格式appender对象 --> <appender name="htmlFile" class="ch.qos.logback.core.FileAppender"> <!--日志格式配置--> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="ch.qos.logback.classic.html.HTMLLayout"> <pattern>%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%m</pattern> </layout> </encoder> <!--日志输出路径--> <file>${log_dir}/logback.html</file> </appender> <!--RootLogger对象--> <root level="all"> <appender-ref ref="console"/> <appender-ref ref="file"/> <appender-ref ref="htmlFile"/> </root>
-
RollingFileAppender配置
在logback.xml
<property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/> <!-- 日志文件存放目录 --> <property name="log_dir" value="d:/logs"></property> <!--控制台输出appender对象--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--输出流对象 默认 System.out 改为 System.err--> <target>System.err</target> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!-- 日志文件拆分和归档的appender对象--> <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> <!--日志输出路径--> <file>${log_dir}/roll_logback.log</file> <!--指定日志文件拆分和压缩规则--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--通过指定压缩文件名称,来确定分割文件方式--> <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM- dd}.log%i.gz</fileNamePattern> <!--文件拆分大小--> <maxFileSize>1MB</maxFileSize> </rollingPolicy> </appender> <!--RootLogger对象--> <root level="all"> <appender-ref ref="console"/> <appender-ref ref="rollFile"/> </root>
-
Filter和异步日志配置
在logback.xml
<!--异步日志--> <appender name="async" class="ch.qos.logback.classic.AsyncAppender"> <appender-ref ref="rollFile"/> </appender> <!--RootLogger对象--> <root level="all"> <appender-ref ref="console"/> <appender-ref ref="async"/> </root> <!--自定义logger additivity表示是否从 rootLogger继承配置--> <logger name="com.itheima" level="debug" additivity="false"> <appender-ref ref="console"/> </logger>
-
官方提供的log4j.properties转换成logback.xml
https://logback.qos.ch/translator/
3. logback-access的使用
logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。我们可以使 用logback-access模块来替换tomcat的访问日志。 1. 将logback-access.jar与logback-core.jar复制到$TOMCATHOME/lib/目录下 2. 修改$TOMCATHOME/conf/server.xml中的Host元素中添加:
<Valve className="ch.qos.logback.access.tomcat.LogbackValve" />
-
logback默认会在$TOMCAT_HOME/conf下查找文件 logback-access.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- always a good activate OnConsoleStatusListener --> <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/> <property name="LOG_DIR" value="${catalina.base}/logs"/> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_DIR}/access.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern> </rollingPolicy> <encoder> <!-- 访问日志的格式 --> <pattern>combined</pattern> </encoder> </appender> <appender-ref ref="FILE"/> </configuration>
-
官方配置: https://logback.qos.ch/access.html#configuration
log4g2
Apache Log4j 2是对Log4j的升级版,参考了logback的一些优秀的设计,并且修复了一些问题
提升 :
- 异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
- 性能提升, log4j2相较于log4j 和logback都具有很明显的性能提升,后面会有官方测试的数据。
- 自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用。
- 无垃圾机制,log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集 导致的jvm gc。
1. 入门
-
依赖
<!-- Log4j2 门面API --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.13.0</version> </dependency> <!-- Log4j2 日志实现 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.13.0</version> </dependency>
-
测试
public class Log4j2Test { // 定义日志记录器对象 public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class); @Test public void testQuick() throws Exception { LOGGER.fatal("fatal"); LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info"); LOGGER.debug("debug"); LOGGER.trace("trace"); } } }
-
使用slf4j作为门面,log4j2作为实现
<!-- Log4j2 门面API --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.13.0</version> </dependency> <!-- Log4j2 日志实现 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.13.0</version> </dependency> <!-- 使用slf4j作为日志的门面,使用log4j2来记录日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <!--为slf4j绑定日志实现 log4j2的适配器 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.13.0</version> </dependency>
2. 配置
log4j2默认加载classpath下的 log4j2.xml 文件中的配置。
<?xml version="1.0" encoding="UTF-8"?>
<!--
status="warn" 日志框架本身的输出日志级别
monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5 秒,实现热更新的效果
-->
<Configuration status="warn" monitorInterval="5">
<!--
集中配置属性进行管理
使用时通过:${name}
-->
<properties>
<property name="LOG_HOME">D:/logs</property>
</properties>
<!--日志处理-->
<Appenders>
<!--控制台输出 appender-->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L - -- %m%n" />
</Console>
<!--日志文件输出 appender-->
<File name="file" fileName="${LOG_HOME}/myfile.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</File>
<!--使用随机读写流的日志文件输出 appender,性能提高-->
<RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</RandomAccessFile>
<!--按照一定规则拆分的日志文件的 appender-->
<RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log" filePattern="D:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy- MM-dd-HH-mm}-%i.log">
<!--日志级别过滤器-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
<!--日志消息格式-->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
<Policies>
<!--在系统启动时,出发拆分规则,生产一个新的日志文件-->
<OnStartupTriggeringPolicy />
<!--按照文件大小拆分,10MB -->
<SizeBasedTriggeringPolicy size="10 MB" />
<!--按照时间节点拆分,规则根据filePattern定义的-->
<TimeBasedTriggeringPolicy />
</Policies>
<!--在同一个目录下,文件的个数限定为 30 个,超过进行覆盖-->
<DefaultRolloverStrategy max="30" />
</RollingFile>
</Appenders>
<!--logger 定义-->
<Loggers>
<!--使用 rootLogger 配置 日志级别 level="trace"-->
<Root level="trace">
<!--指定日志使用的处理器-->
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
3.异步日志
log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益,
Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应前面的Appender组件和Logger组件。
-
依赖
<!-- 异步日志依赖 --> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.2</version> </dependency>
-
AsyncAppender方式
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="warn"> <properties> <property name="LOG_HOME">D:/logs</property> </properties> <Appenders> <!--日志文件输出 appender--> <File name="file" fileName="${LOG_HOME}/myfile.log"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> </File> <Async name="Async"> <AppenderRef ref="file"/> </Async> </Appenders> <Loggers> <Root level="error"> <!--使用异步 appender--> <AppenderRef ref="Async"/> </Root> </Loggers> </Configuration
-
AsyncLogger方式
AsyncLogger 是log4j2的重点,也是官方推荐的异步方式。它可以使得调用Logger.log返回的更快。可以有两种选择:全局异步和混合异步。
-
全局异步
-
所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加一个log4j2.component.properties 配置文件;
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
-
-
混合异步
-
可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <properties> <property name="LOG_HOME">D:/logs</property> </properties> <Appenders> <File name="file" fileName="${LOG_HOME}/myfile.log"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> </File> <Async name="Async"> <AppenderRef ref="file"/> </Async> </Appenders> <Loggers> <AsyncLogger name="com.cccq" level="trace" includeLocation="false" additivity="false"> <AppenderRef ref="file"/> </AsyncLogger> <Root level="info" includeLocation="true"> <AppenderRef ref="file"/> </Root> </Loggers> </Configuration>
-
如上配置: com.cccq 日志是异步的,root日志是同步的。
4.性能
Log4j2最厉害的地方在于异步输出日志时的性能表现
Log4j2有三种模式:
- 全局使用异步模式;
- 部分Logger采用异步模式;
- 异步Appender。
无垃圾记录:
垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,花费大量精力来控制这些暂停。
许多日志库(包括以前版本的Log4j)在稳态日志记录期间分配临时对象,如日志事件对象,字符串,字符数组,字节数组等。这会对垃圾收集器造成压力并增加GC暂停发生的频率。
从版本2.6开始,默认情况下Log4j以“无垃圾”模式运行,其中重用对象和缓冲区,并且尽可能不分配临时对象。还有一个“低垃圾”模式,它不是完全无垃圾,但不使用ThreadLocal字段。
Log4j 2.6中的无垃圾日志记录部分通过重用ThreadLocal字段中的对象来实现,部分通过在将文本转换为字节时重用缓冲区来实现。