LogBack
一、架构
Logger 上下文
任何日志 API 的优势在于它能够禁止某些日志 的输出。
命名层次结构 如果一个 logger 的名字加上 . 作为另一个 logger 名字的前缀,那么该 logger 就是另一个 logger 的祖先。如果一个 logger 与另一个 logger 之间没有其他的 logger,那么该 logger 就是另一个 logger 的父级。
每一个 logger 都可以通过它的名字取获取。
// 获取 root logger
Logger rootLogger = LoggerFactory.getLogger(ROOT_LOGGER_NAME);
有效级别
对于一个给定的名称为 L 的logger,它的有效级别为从自身一直回溯到 root logger, 直到找到第一个不为空的层级作为自己的层级。 root logger 有一个默认级别:DEBUG
基本选择规则
如果一条日志的打印级别大于 logger 的有效级别,该日志才可以被打印出来 日志的打印级别为 p, Logger 实例级别为 q, 如果 p >= q, 则该条日志会被输出。
TRANCE < DEBUG < INFO < WARN < ERROR
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(SetLoggerLevel.class);
// 将 logger 的有效级别设置为 warn
logger.setLevel(Level.WARN);
logger.trace("trance");
logger.info("info");
logger.warn("warn");
logger.error("error");
获取 logger
通过 LoggerFactory.getLogger(); 可以获取到具体的 logger,名字相同则返回的 logger 实例也相同。
Appender 与 Layout
Appender:输出目的地。可以将日志输出到console、file、remote socket server、MySQL 、PostgreSQL、Oracle或者其他数据库,JMS、remote UNIX Syslog daemons 中。
一个 logger 可以有多个 appender。
appender 的叠加性 logger L 的日志输出语句会遍历 L 和他的子级中所有的 appender。 如果 L 的子级 looger P 设置了 additivity = false,那么 L 的日志会在 L 所有的(包括P本身) appender 中输出,但是不会在 P 的自己 appender 中输出。
参数化日志
问题1 ; logback 的可变参数是怎么实现的呢?
logback 的配置
初始化顺序
- logback-test.xml
- logback.groovy
- logback.xml
- META-INFO/services/ch.qos.logback.classic.spi.Configurator
- 以上文件都没找到的时候,会提供一个默认的配置
自动配置 logback
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
打印状态信息
- 在 xml 配置错误的时候,控制台会自动打印出 logback 内部状态信息
- 使用 StatusPrinter 类打印出logback 内部状态信息
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(context);
- 在 logback 的配置文件中的 configuration 元素上添加 debug 属性
debug 只与状态信息有关,不影响日志级别
<configuration debug = "true">
...
</configuration>
- 配置一个 OnConsoleStatusListener ,等同于配置 debug = "true"
<configuration>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
...
</configuration>
logback.xml 被修改时,自动加载
在 configuration 标签上添加 scan = "true" 属性。默认 60s 扫描一次
<configuration scan="true">
...
</configuration>
配置文件语法
Appender
什么是 Appender
logback 将写入日志事件委托给一个叫做 appender 的组件。Appender 必须实现 Appender 接口。
doappend(E event) 是 Appender
中最重要的方法。接受一个泛型作为唯一的参数。它的职责是将日志事件进行格式化,然后输出到对应的设备上。
在 classic 中,接收 ILoggingEvent 类型,在 access 中接收 AccessEvent 类型。
AppenderBase
ch.qos.logback.core.UnsynchronizedAppenderBase
public void doAppend(E eventObject) {
// WARNING: The guard check MUST be the first statement in the
// doAppend() method.
// prevent re-entry.
if (Boolean.TRUE.equals(guard.get())) {
return;
}
try {
guard.set(Boolean.TRUE);
// UnsynchronizedAppenderBase 实现了 LifeCycle 接口
if (!this.started) {
// 当 appender 没有启动的时候在内部状态管理系统中发出一条警告信息,这条警告信息只会发 ALLOWED_REPEATS 条
if (statusRepeatCount++ < ALLOWED_REPEATS) {
addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
}
return;
}
if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
return;
}
// ok, we now invoke derived class' implementation of append
this.append(eventObject);
} catch (Exception e) {
if (exceptionCount++ < ALLOWED_REPEATS) {
addError("Appender [" + name + "] failed to append.", e);
}
} finally {
guard.set(Boolean.FALSE);
}
}
logback-core
logback-core 为 logback 其他组件的构建奠定了基础。
OutputStreamAppender
OutputStreamAppender 将事件附加到 java.io.OutputStream 上。这个类提供了其他 appender 构建的基础服务。
| 属性名 | 属性值 | 描述 |
|---|---|---|
| ecoder | Encoder | 决定通过哪种方式将事件写入 java.io.OutputStream |
| immediateFlush | boolean | (默认值为 true )是否立即刷新输出流,确保日志事件被立即写入,并且保证一旦 appender 没有正确关闭,日志事件也不会丢失 |
OutputStreamAppender 类图
ConsoleAppender
ConsoleAppender 将日志事件附加到控制台。通过 System.out 或者 System.error 进行输出,默认前者。(通过一个枚举类实现 ConsoleTarget)
| 属性名 | 属性值 | 描述 |
|---|---|---|
| ecoder | Encoder | 格式化输出 |
| target | String | System.out 或者 System.err |
| withJansi | boolean | 默认为 false,设置 withJansi 为 true 的时候需要添加 jansi jar包。 |
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
可以在控制台看到使用 System.err 进行输出的日志
FileAppender
将日志事件输出到文件中,通过 file 指定目标文件。
| 属性名 | 属性值 | 描述 |
|---|---|---|
| append | boolean | true 表示,每次程序启动时,追加日志。false 表示:每次程序启动时,先清空再添加日志 |
| ecoder | Encoder | 格式化输出 |
| file | String | 文件路径。C:/a.txt 或 C:\\c.txt |
| prudent | boolean | 严谨模式 |
<configuration>
<appender name ="FILE" class="ch.qos.logback.core.FileAppender">
<append>false</append>
<file>F:/FileAppender.log</file>
<immediateFlush>true</immediateFlush>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
文件唯一命名 时间戳 TimeStamp
配置
<?xml version="1.0" encoding="UTF-8"?>
<!-- <configuration debug="true"> 调试模式下,可输出logback的内部日志信息 -->
<configuration>
<!-- 定义变量 -->
<property name="LOG_HOME" value="${user.dir}" />
<!-- 引用其它文件的设置 <property file="res/log_config.properties" /> -->
<!-- appender(输出端) 此处是控制台输出,用的是ConsoleAppender,STDOUT,此名字随意起,和root下的一致就行 -->
<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{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出1 -->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<!-- 输出文件名 -->
<!-- <file>${LOG_HOME}/logs/mylog.log</file> -->
<!-- 输出文件名,引用其它文件的设置 -->
<file>${app_home}/mylog.log</file>
<encoder>
<!--%date日期 %level日志级别 [%thread]当前线程 %logger{50}输出日志的类,50代表包名加类名的总长度限制 [%file:%line]日志所在文件及行数 %msg%n消息及换行 -->
<pattern>%date %level [%thread] %logger{50} [%file:%line] %msg%n
</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE2" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/ifproxy_%d{yyyy-MM-dd}.log</FileNamePattern>
<!-- 最多日志文件数,必须和CleanHistoryOnStart一起使用 -->
<MaxHistory>3</MaxHistory>
<CleanHistoryOnStart>true</CleanHistoryOnStart>
</rollingPolicy>
<encoder>
<!--%date日期 %level日志级别 [%thread]当前线程 %logger{50}输出日志的类,50代表包名加类名的总长度限制 [%file:%line]日志所在文件及行数 %msg%n消息及换行 -->
<pattern>%date %level [%thread] %logger{50} [%file:%line] %msg%n
</pattern>
</encoder>
</appender>
<!-- 级别定义,输出端内容输出级别,大于或等于root定义的level,方能在输出端输出 -->
<root level="trace">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE2" />
</root>
<!-- 单独为某些类定输出级别 -->
<logger name="com.log.test.Test2" level="info"></logger>
<!-- 将指定类(mytest.lala.Test)的日志单独输出到指定的日志文件中(control2)
<appender name="FILE3" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/%d{yyyyMM}/control2_%d{yyyy-MM-dd}.log</FileNamePattern>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="mytest.lala.Test">
<appender-ref ref="FILE3" />
</logger>
-->
</configuration>