Java日志系列——logback,log4j2使用
Java日志系列——logback,log4j2使用
- Logback
- 官方网站
- QuickStart
- 1.添加依赖
- 2.代码
- spi机制
- 自定义Config配置
- logback
- 自定义
- ch.qos.logback.classic.spi.Configurator文件
- 主要组件
- 配置文件式配置
- 官网配置
- 1.在resource下新建logback.xml
- 2.写入logback.xml文件
- 3.测试
- 配置文件方式将日志写入文件中
- 1.在logback.xml中编写FIle类型Appender
- 2.引用appender配置
- 3.测试
- 自定义日志输出
- 滚动日志(rollingFileAppender)
- 过滤器Filter
- 简单过滤器
- 级别过滤器
- 属性
- 异步日志
- log4j2
- 官网地址
- QuickStart
- 1. 依赖
- 2.添加log4j2.xml
- 3.测试
- 使用Slf4j作为日志门面(推荐)
- 1.添加依赖
- 2.使用slf4j的测试
- 配置相关
- 重加载配置文件
- 配置文件
- 配置输出格式
- 配置Logger
- 官网建议以严格的格式进行配置
- JSON格式配置
- YAML配置方式(推荐,个人最喜欢)
- 异步日志
- 依赖
- 全局异步
- 混合异步(局部异步,最常用)
- 关闭行号信息
- 注意点
- 无垃圾模式
- 文档地址
Logback
Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。
Logback主要分为三个模块:
- logback-core:其它两个模块的基础模块
- logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API
- logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能后续的日志代码都是通过SLF4]日志门面搭建日志系统,所以在代码是没有区别,主要是通过修改配置文件和pom.xml依赖
官方网站
https://logback.qos.ch/
QuickStart
1.添加依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
2.代码
@Test
void test(){
final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
}
spi机制
SPI全称Service Provider Interface,是java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件
他是一种服务
发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
自定义Config配置
通过模仿logback我们自己就可以修改config
我们可以找到BasicConfigurator
的源码,这是默认的logback的配置,我们将其模仿自己也可以写一个
public class DefineConfigurator extends ContextAwareBase implements Configurator {
public DefineConfigurator() {
}
@Override
public void configure(LoggerContext lc) {
this.addInfo("Setting up default configuration.");
ConsoleAppender<ILoggingEvent> ca = new ConsoleAppender();
ca.setContext(lc);
ca.setName("console");
LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder();
encoder.setContext(lc);
//格式化处理
final PatternLayout layout = new PatternLayout();
layout.setPattern("%d{HH :mm :ss.sSS} [%thread] %-5level %logger{36} - %msg%n");
// TTLLLayout layout = new TTLLLayout();
layout.setContext(lc);
layout.start();
encoder.setLayout(layout);
ca.setEncoder(encoder);
ca.start();
Logger rootLogger = lc.getLogger("ROOT");
rootLogger.addAppender(ca);
}
}
logback
自定义
ch.qos.logback.classic.spi.Configurator文件
com.example.jul.DefineConfigurator
主要组件
- appender,输出源,一个日志可以后好几个输出源
- encoder,一个appender有一个encoder,负责将一个event事件转换成一组byte数组,并将转换后的字节数据输出到文件中。Encoder负责把事件转换为字节数组,并把字节数组写到合适的输出流。因此,encoder可以控制在什么时候、把什么样的字节数组写入到其拥有者维护的输出流中。Encoder接口有两个实现类,LayoutWrappingEncoder与PatternLayoutEncoder。注意:在logback 0.9.19版之前没有encoder。在之前的版本里,多数appender依靠layout来把事件转换成字符串并用java.io.Writer把字符串输出。在之前的版本里,用户需要在FileAppender里嵌入一个PatternLayout。
- layout,格式化数据将event事件转化为字符串,解析的过程
配置文件式配置
logback会依次读取以下类型配置文件:
- logback.groovy
- logback-test.xml
- logback.xml
如果均不存在会采用默认配置
官网配置
https://logback.qos.ch/manual/configuration.html
1.在resource下新建logback.xml
2.写入logback.xml文件
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg ok %n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
3.测试
@Test
void test(){
final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
}
配置文件方式将日志写入文件中
1.在logback.xml中编写FIle类型Appender
加入name叫做FIle
的Appender,让他应用ch.qos.logback.core.FileAppender
类
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>D://myApp.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg -to file %n</pattern>
</encoder>
</appender>
2.引用appender配置
找到root节点,使用appender-ref
进行引入
<root level="debug">
<appender-ref ref="FILE" />
</root>
3.测试
自定义日志输出
我们还可以通过配置logger节点的方式,对单独的logger进行配置
如下在logback.xml中配置了叫test的logger直接在控制台进行输出,当然你还可以指定到文件,流中等这些都取决于你怎么配置
//name
final Logger logger = LoggerFactory.getLogger("test");
//logback.xml
<logger name="test" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
滚动日志(rollingFileAppender)
<appender name="ROLL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 设置按尺寸和时间(同时满足)分割 -->
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>D://rolling.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</encoder>
</appender>
过滤器Filter
我们需要将filter配置在appender中
简单过滤器
<appender>
<filter class="chapters.filters.SampleFilter" />
</appender>
级别过滤器
LevelFilter 根据精确的级别匹配过滤事件。 如果事件的级别等于配置的级别,则过滤器接受或拒绝该事件,具体取决于 onMatch 和 onMismatch 属性的配置。
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
属性
通过配置属性我们可以进行复用增强移植性
<property name="p1" value="define value"></property>
然后我们就可以通过${}
来进行使用
异步日志
主线程应该用于执行业务,而日志不应该占用主线程,所以我们应该采用异步的方式进行优化
AsyncAppender 异步记录 ILoggingEvents
。 它仅充当事件调度程序,因此必须引用另一个附加程序才能执行任何有用的操作
实际上我们是在外面配好appender然后到异步中进行ref引入的
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
属性名称 | 类型 | 描述 |
---|---|---|
queueSize | int | 阻塞队列的最大容量。 默认, queueSize 设置为 256。 |
discardingThreshold | int | 默认情况下,当阻塞队列有 20% 容量时 剩下的,它将丢弃 TRACE、DEBUG 和 INFO 级别的事件, 只保留 WARN 和 ERROR 级别的事件。 为了保持所有 事件,将 discardingThreshold 为 0。 |
includeCallerData | boolean | 提取呼叫者数据可能相当昂贵。 改善 性能,默认情况下,与事件关联的调用者数据 事件添加到事件队列时不提取。 经过 等“廉价”数据 MDC 复制 你可以指导这个 appender 通过将 includeCallerData 属性设置为 true 来包含调用者数据。 |
maxFlushTime | int | 根据引用的 appender 的队列深度和延迟, 这 AsyncAppender可能需要不可接受的数量 是时候完全刷新队列了。 当。。。的时候 LoggerContext是 停了下来, AsyncAppender stop方法等待 直到这个超时,工作线程才能完成。 利用 maxFlushTime 指定最大队列刷新 以毫秒为单位的超时。 无法在此范围内处理的事件 窗口被丢弃。 此值的语义与 Thread.join(long) 。 |
neverBlock | boolean | 如果 false(默认)appender 将阻塞 追加到一个完整的队列而不是丢失消息。 调成 trueappender 只会删除消息和 不会阻止您的应用程序。 |
log4j2
Apache Log4]2是对Log4j的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:
- 异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
- 性能提升,log4j2相较于log4j和logback都具有很明显的性能提升,后面会有官方测试的数据。
- 自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用。
- 无垃圾机制,避免频繁的日志收集导致jvm的gc
官网地址
https://logging.apache.org/log4j/2.x/index.html
QuickStart
1. 依赖
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.2</version>
</dependency>
2.添加log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
3.测试
@Test
void test(){
final Logger logger = LogManager.getLogger(this.getClass().getName());
logger.fatal("fatal");
logger.error("error");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
}
使用Slf4j作为日志门面(推荐)
1.添加依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.2</version>
</dependency>
2.使用slf4j的测试
@Test
void test(){
final Logger logger = LoggerFactory.getLogger(this.getClass().getName());
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
//占位符输出方式
logger.warn("{}first,{}second","zhangsan",18);
}
配置相关
重加载配置文件
在Configuration
节点上配置monitorInterval
属性
以下配置36000s重加载
<Configuration monitorInterval="30">
</Configuration>
配置文件
<Appenders>
<File name="File1" fileName="output.log" bufferedIO="false" advertiseURI="file://path/to/output.log" advertise="true">
...
</File>
</Appenders>
配置输出格式
//第一种
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
//第二种
<PatternLayout>
<Pattern>%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Pattern>
</PatternLayout>
配置Logger
通过在loggers下配置子logger指定名称可以自定义单个logger
<Loggers>
<Logger name="name1">
<AppenderRef ref="name"/>
<filter ... />
</Logger>
</Loggers>
官网建议以严格的格式进行配置
以下是官网的给出的建议配置
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" strict="true" name="XMLConfigTest"
packages="org.apache.logging.log4j.test">
<Properties>
<Property name="filename">target/test.log</Property>
</Properties>
<Filter type="ThresholdFilter" level="trace"/>
<Appenders>
<Appender type="Console" name="STDOUT">
<Layout type="PatternLayout" pattern="%m MDC%X%n"/>
<Filters>
<Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/>
<Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/>
</Filters>
</Appender>
<Appender type="Console" name="FLOW">
<Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
<Filters>
<Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</Appender>
<Appender type="File" name="File" fileName="${filename}">
<Layout type="PatternLayout">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</Layout>
</Appender>
</Appenders>
<Loggers>
<Logger name="org.apache.logging.log4j.test1" level="debug" additivity="false">
<Filter type="ThreadContextMapFilter">
<KeyValuePair key="test" value="123"/>
</Filter>
<AppenderRef ref="STDOUT"/>
</Logger>
<Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
<AppenderRef ref="File"/>
</Logger>
<Root level="trace">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
JSON格式配置
使用 JSON 进行配置
除了 XML,Log4j 还可以使用 JSON 进行配置。 JSON 格式与简洁的 XML 格式非常相似。 每个键代表一个插件的名称,与之关联的键/值对就是它的属性。 如果一个键包含的不仅仅是一个简单的值,它本身就是一个从属插件。 在下面的示例中,ThresholdFilter、Console 和 PatternLayout 都是插件,而 Console 插件的 name 属性将被分配一个 STDOUT 值,而 ThresholdFilter 将被分配一个调试级别。
{ "configuration": { "status": "error", "name": "RoutingTest",
"packages": "org.apache.logging.log4j.test",
"properties": {
"property": { "name": "filename",
"value" : "target/rolling1/rollingtest-$${sd:type}.log" }
},
"ThresholdFilter": { "level": "debug" },
"appenders": {
"Console": { "name": "STDOUT",
"PatternLayout": { "pattern": "%m%n" },
"ThresholdFilter": { "level": "debug" }
},
"Routing": { "name": "Routing",
"Routes": { "pattern": "$${sd:type}",
"Route": [
{
"RollingFile": {
"name": "Rolling-${sd:type}", "fileName": "${filename}",
"filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz",
"PatternLayout": {"pattern": "%d %p %c{1.} [%t] %m%n"},
"SizeBasedTriggeringPolicy": { "size": "500" }
}
},
{ "AppenderRef": "STDOUT", "key": "Audit"}
]
}
}
},
"loggers": {
"logger": { "name": "EventLogger", "level": "info", "additivity": "false",
"AppenderRef": { "ref": "Routing" }},
"root": { "level": "error", "AppenderRef": { "ref": "STDOUT" }}
}
}
}
YAML配置方式(推荐,个人最喜欢)
Log4j 还支持将 YAML 用于配置文件。 该结构遵循与 XML 和 YAML 配置格式相同的模式。
Configuration:
status: warn
name: YAMLConfigTest
properties:
property:
name: filename
value: target/test-yaml.log
thresholdFilter:
level: debug
appenders:
Console:
name: STDOUT
target: SYSTEM_OUT
PatternLayout:
Pattern: "%m%n"
File:
name: File
fileName: ${filename}
PatternLayout:
Pattern: "%d %p %C{1.} [%t] %m%n"
Filters:
ThresholdFilter:
level: error
Loggers:
logger:
-
name: org.apache.logging.log4j.test1
level: debug
additivity: false
ThreadContextMapFilter:
KeyValuePair:
key: test
value: 123
AppenderRef:
ref: STDOUT
-
name: org.apache.logging.log4j.test2
level: debug
additivity: false
AppenderRef:
ref: File
Root:
level: error
AppenderRef:
ref: STDOUT
异步日志
异步日志记录可以通过在单独的线程中执行 I/O 操作来提高应用程序的性能。 Log4j 2 在这方面做了很多改进。
- 异步 Loggers 是 Log4j 2 中的新增功能。它们的目的是尽快从对 Logger.log 的调用返回到应用程序。 您可以选择使所有 Logger 异步或混合使用同步和异步 Logger。 使所有 Loggers 异步将提供最佳性能,而混合则为您提供更大的灵活性。
- LMAX 干扰器技术。 异步 Logger 在内部使用 Disruptor,一个无锁的线程间通信库,而不是队列,从而产生更高的吞吐量和更低的延迟。
- 作为 Async Loggers 工作的一部分,Asynchronous Appenders 已得到增强,可以在批处理结束时(当队列为空时)刷新到磁盘。 这会产生与配置“immediateFlush=true”相同的结果,即所有接收到的日志事件始终在磁盘上可用,但效率更高,因为它不需要在每个日志事件上都接触磁盘。 (异步 Appender 在内部使用 ArrayBlockingQueue,并且不需要类路径上的中断器 jar。)
依赖
Log4j-2.9 和更高版本需要在类路径上使用disruptor-3.3.4.jar 或更高版本。 在 Log4j-2.9 之前,需要disruptor-3.0.0.jar 或更高版本。
<!-- https://mvnrepository.com/artifact/com.lmax/disruptor -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.2</version>
</dependency>
全局异步
这个配置很简单,只要创建一个叫log4j2.component.properties
的文件然后加入Log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
或者Log4j2.contextSelector=org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector
混合异步(局部异步,最常用)
借助AsyncLogger
即可设置异步日志,直接指明name这和普通的appender是一样的
<Loggers>
<AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
<AppenderRef ref="RandomAccessFile"/>
</AsyncLogger>
</Loggers>
关闭行号信息
即配置属性:includeLocation="false"
为了让性能进一步提升
注意点
- 如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。性能会和AsyncAppender—致,降至最低。
- 设置
includeLocation=false
,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。
无垃圾模式
从版本2.6开始,默认情况下Log4j以“无垃圾”模式运行,其中重用对象和缓冲区,并且尽可能不分配临时对象。还有一个“低垃圾”模式,它不是完全无垃圾,但不使用ThreadLocal字段。
Log4j2.6中的无垃圾日志记录部分通过重用ThreadLocal字段中的对象来实现,部分通过在将文本转换为字节时重用缓冲区来实现。
这个模式是默认开启的,大大的提升了性能
文档地址
https://logging.apache.org/log4j/2.x/manual/garbagefree.html