8 分钟
Java Logging
基本概念
日志级别
几乎所有日志系统都有日志级别这个一概念,用来标识该条日志记录的严重程度。
通过配置,日志系统可以只输出日志记录严重程度 在 某个级别之上的日志,而 在 某个级别之下的日志将被丢弃。
日志输出目标
每条日志可以输出到多个位置,比如 标准输出、文件、socket、消息队列、企业内部自定义消息系统。
日志格式化
除了 logger.log(msg)
用户传递的消息 msg
之外,日志系统还能拿到很多额外的辅助信息,比如 打日志的时间、所在类的类名、所在方法名、所在文件行号、环境变量、系统信息等。
这些 额外属性 和 msg
如何使用,通过各个 日志系统 的日志格式化的配置字符串可以进行配置。
日志配置
日志系统一般通过查找 classpath 配置文件的方式 进行自动配置。配置文件的格式一般为 .properties
、 .yaml
、.json
、 .xml
等。这种方式可以在不同环境使用不同的日志配置文件。比如
- 开发环境只需要输出到 标准输出,日志级别一般为 DEBUG
- 生产环境需要输出到 文件 或者 socket,日志级别一般为 INFO
日志系统一般有如下核心配置需要配置,这些配置一般支持全局和指定几个类。
- 日志级别
- 日志格式化
- 日志输出目标
日志实例配置继承
一般情况下每个 类 一个 Logger 实例,这个 Logger 实例命名为 该类 类名。
为了方面,大多数日志框架 会根据包名 进行配置继承,也就是说: com.a.b
,会继承 com.a
的部分配置。
一般会继承如下两个方面的内容
- Level
- 日志输出目标
JUL (JDK Logging)
JUK 指 Java JDK 自带的日志实现,是一套简单的日志实现。
优点
- 使用简单
- 开箱即用
缺点
- 市场占有率较低
- 功能相对较弱:
- 格式化不支持配置文件配置
测试代码
src/main/java/cn/rectcircle/learn/logging/JULUsage.java
package cn.rectcircle.learn.logging;
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class JULUsage {
public static void main(String[] args) throws SecurityException, IOException {
// 0. 读取配置文件
// System.setProperty("java.util.logging.config.file", "my-jul.properties"); // 此方式只能是文件路径
// LogManager.getLogManager().readConfiguration();
LogManager.getLogManager().readConfiguration(JULUsage.class.getResourceAsStream("/my-jul.properties"));
// 1. JUL 基本使用
System.out.println(JULUsage.class.getName());
Logger logger = Logger.getLogger(JULUsage.class.getName());
logger.info("Hello World.");
logger.fine("Hello World.");
// 1.1 参数配置实验
Handler console = new ConsoleHandler();
console.setLevel(Level.SEVERE);
logger.addHandler(console);
System.out.println(logger.getHandlers().length);
System.out.println(logger.getParent().getHandlers().length);
System.out.println(logger.getParent().getParent().getHandlers().length);
logger.setUseParentHandlers(false);
logger.getParent().getParent().getHandlers()[0].setLevel(Level.ALL);
// 2. JUL 七种 日志级别
// SEVERE WARNING INFO CONFIG FINE FINER FINEST
// 默认级别
logger.setLevel(Level.ALL);
logger.severe("严重");
logger.warning("警告");
logger.info("信息");
logger.config("配置");
logger.fine("良好");
logger.finer("较好");
logger.finest("最好");
}
}
src/main/resources/my-jul.properties
# 指定以逗号分隔的日志处理器列表
handlers= java.util.logging.ConsoleHandler
# 根 Logger 的日志级别
.level= INFO
# 文件日志处理器配置
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# 控制台日志处理器配置
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定某个特定 Logger 的
# cn.rectcircle.learn.logging.JULUsage.level = FINE
cn.rectcircle.learn.logging.level = FINE
配置
默认情况下,JUC 将读取 $JAVA_HOME/jre/lib/logging.properties
配置文件进行初始化。
一般可以通过 System.setProperty("java.util.logging.config.file", "/xxx/xxx/my-jul.properties");
配置自定义的配置(该方式无法配置 jar 里面的配置文件,也无法通过 classpath 查找配置文件)。然后通过 LogManager.getLogManager().readConfiguration()
应用配置文件。
另外一种是通过命令行参数 java -Djava.util.logging.config.file=<value>
方式配置,该方式不需要手动调用 LogManager.getLogManager().readConfiguration()
class path 配置可以通过 LogManager.getLogManager().readConfiguration(JULUsage.class.getResourceAsStream("/my-jul.properties"));
方式配置
日志级别
- SEVERE(最高值)
- WARNING
- INFO (默认配置文件指定值)
- CONFIG
- FINE
- FINER
- FINEST(最低值)
可以在如下几个地方配置
.level
根处理器的 level$LoggerName.level
某个特定的处理器的 leveljava.util.logging.XxxHandler.level
处理器内部的 level
某个日志消息最终是否会被写出,经过如下流程
- 当前 Logger 的 level 是什么?如果没有配置,将找到使用当前 Logger 的 Parent Logger 的 level,如果不满足直接丢弃
- 每个 Handler 自己的 Level 是否按满足,如果不足满足直接丢弃
- 最终写出
日志输出目标 Handler
- java.util.logging.ConsoleHandler 以System.err输出日志。
- java.util.logging.FileHandler 将信息输出到文件。(默认配置文件指定值)
- java.util.logging.StreamHandler 以指定的OutputStream实例输出日志。
- java.util.logging.SocketHandler 将信息通过Socket传送至远程主机。
- java.util.logging.MemoryHandler 将信息暂存在内存中。
日志格式化 Formatter
提供两个格式化对象
java.util.logging.SimpleFormatter
java.util.logging.XMLFormatter
如果需要定制格式化字符串,则继承 java.util.logging.Formatter
抽象类。不像其他日志库一样,可以通过配置解决。
日志实例配置继承
JDK Logger 实例,配置存在继承关系(通过 Logger 的 parent 字段)。每个 Logger 都存在一个 根 Logger,该 Logger 将绑定 handlers
配置的处理器,通过 logger.setUseParentHandlers(false)
(默认为 true)来决定是否复用父 Logger 的 handlers
level 若未配置 将继承其 父亲 的 level。
继承关系,通过 Logger 的 name 来决定。com.a.b
是 com.a.b.C
的父亲。
如果配置了 com.a.b.level = XXX
,同时在程序中创建了一个 Name 为 com.a.b.C
的 Logger,继承链为
Logger<ROOT>
^
|
Logger<name=com.a.b>
^
|
Logger<name=com.a.b.C>
源码
- Logger 面向用户获取日志的接口
- LoggerManager 负责管理所有 logger
- Handler 负责最终日志的输出, 例如 ConsoleHandler 输出到终端, FileHandler 输出到文件
- Formatter 用于格式化日志内容, 例如添加时间戳, 输出包名等
- Level 日志级别
Log4J
2015 年起 已停止维护
LogBack
http://www.logback.cn/ http://logback.qos.ch/manual/index.html
Hello World
依赖
<!-- logback 使用的最少需要的三个包 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
测试代码
Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
logger.debug("hello world");
// 打印内部的状态:方便调试 bug
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
- Logback 没有找到 logback-test.xml 和 logback.xml 配置文件,所以通过默认的配置策略-添加一个基本的 ConsoleAppender 来进行配置。
- Appender 即 日志输出目标。
日志级别
一共五级:TRACE < DEBUG < INFO < WARN < ERROR
日志实例配置继承
和 JUL 类似,通过名字前缀确定继承树。继承内容如下
- Level
- Appender (additivity = true 的情况,默认为 true)
日志输出目标 Appender
http://www.logback.cn/04%E7%AC%AC%E5%9B%9B%E7%AB%A0Appenders.html
appender 包括console、file、remote socket server、MySQL、PostgreSQL、Oracle 或者其它的数据库、JMS、remote UNIX Syslog daemons 等
接口为 ch.qos.logback.core.Appender
,自定义建议继承 AppenderBase
常用的内置 Appender
ch.qos.logback.core.ConsoleAppender
输出到命令行ch.qos.logback.core.FileAppender
输出到文件ch.qos.logback.core.rolling.RollingFileAppender
轮转输出到文件ch.qos.logback.classic.net.SocketAppender
(SSLSocketAppender
) 写到远端ch.qos.logback.classic.net.server.ServerSocketAppender
SSLServerSocketAppender
ch.qos.logback.classic.net.SMTPAppender
邮件方式ch.qos.logback.classic.db.DBAppender
写入到数据库
日志格式化 Encoder 与 Layout
http://www.logback.cn/06%E7%AC%AC%E5%85%AD%E7%AB%A0Layouts.html
每个 Appender 可以指定一个 格式化方式,一般使用 PatternLayout
,该方式可以通过规则字符串配置。
接口为 ch.qos.logback.core.Layout
,自定义建议继承 LayoutBase
PatternLayout 模式 基本可以满足大多数需求。参见:
http://www.logback.cn/06%E7%AC%AC%E5%85%AD%E7%AB%A0Layouts.html
其他常用的 Layout
HTMLLayout
ch.qos.logback.classic.log4j.XMLLayout
Encoder 包裹 Layout,用于将 String 转换为 字节数组,控制何时写出。
一般使用的 是 PatternLayoutEncoder
(默认就是)
Filter
http://www.logback.cn/07%E7%AC%AC%E4%B8%83%E7%AB%A0Filters.html
对日志进行过滤
MDC
http://www.logback.cn/07%E7%AC%AC%E4%B8%83%E7%AB%A0Filters.html
为了支持在 同一个线程中打出的日志 引用某个 信息,且这个信息在整个调用链中 唯一确定 。通过 slf4j 的 MDC 机制,可以实现这个一点。
以一个 Web 应用为例,目的为日志可以按照 会话 id 进行筛选,以方便后续 debug 和 分析
- 实现一个 filter
- before 逻辑为 在 MDC 配置一个 session-id
- after 逻辑为 清空 MDC 中的 session-id
- 在配置文件中的
pattern
中 使用%X{}
取值 例如%X{req.remoteHost} %X{req.requestURI}%n%d - %m%n
打印日志的流程
- 用户调用 logger 打条日志方法
- 使用过滤器链贩判断是否需要打印
- 比较日志级别确定是否需要打印
- 创建 LoggingEvent 对象
- 依次调用 appender
- 调用 layout 格式化日志
- 写出到目标
配置
http://www.logback.cn/03%E7%AC%AC%E4%B8%89%E7%AB%A0logback%E7%9A%84%E9%85%8D%E7%BD%AE.html
logback 的初始化步骤:
- logback 会在类路径下寻找名为 logback-test.xml 的文件。
- 如果没有找到,logback 会继续寻找名为 logback.groovy 的文件。
- 如果没有找到,logback 会继续寻找名为 logback.xml 的文件。
- 如果没有找到,将会通过 JDK 提供的 ServiceLoader 工具在类路径下寻找文件 META-INFO/services/ch.qos.logback.classic.spi.Configurator,该文件的内容为实现了 Configurator 接口的实现类的全限定类名。
- 如果以上都没有成功,logback 会通过 BasicConfigurator 为自己进行配置,并且日志将会全部在控制台打印出来。
logback.xml
基本结构
<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>
- configuration 属性
scan="true"
检测到文件变更后,是否自动更新日志对象scanPeriod="30 seconds"
扫描间隔packagingData="true"
展示异常链时附加所在jar包
- configuration 包含三个种类型的孩子
- appender 0 个或多个
- logger 0 个或多个
- root 一个
- appender
name
属性:用于在<appender-ref ref="STDOUT" />
中引用class
属性:用于实体化的类名layout
子元素:0个或1个,默认为 PatternLayoutclass
属性:用于实体化的类名
encoder
子元素:0 或多个 元素,默认为PatternLayoutEncoder
class
属性pattern
子元素
filter
子元素:0个或多个- 其他配置子元素 例如
file
- logger 或 root
level
属性: 当前 logger 的 leveladditivity
属性: 是否使用父 logger 的 appender,默认为 true<appender-ref ref="STDOUT" />
子元素:0个或多个
XML 中支持通过 ${USER_NAME}
进行变量替换
- 变量定义
- 环境变量
java -Dxxx
<property name="USER_NAME" value="/data/logs" />
<property file="F:\project\logback-examples\src\main\resources\variables1.properties"/>
文件路径<property resource="resource1.properties" />
classpath 路径
- 变量查找顺序
- 本地(local scope)
<property name="nodeId" value="firstNode"/>
默认作用域 - 上下文(context scope)
- 系统(system scope)
- 配置方式
<property scope="context" name="nodeId" value="firstNode"/>
- 本地(local scope)
- 变量使用
${name}
${aNme:-golden}
默认值${id:-${userid}}
多次嵌套默认值${${userid}.password}
多次替换
- 系统变量
HOSTNAME
CONTEXT_NAME
- 动态定义属性
<define>
标签
条件表达式
<configuration debug="true">
<if condition='property("HOSTNAME").contains("volong")'>
<then>
<appender name="CON" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="CON" />
</root>
</then>
</if>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${randomOutputDir}/conditional.log</file>
<encoder>
<pattern>${HOSTNAME} %d %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<root level="ERROR">
<appender-ref ref="FILE" />
</root>
</configuration>
引入文件
- file 文件路径
- resource classpath 中查找
url 外部文件
<configuration> <include file="src/main/resources/includedConfig.xml" /> <root level="DEBUG"> <appender-ref ref="includedConsole" /> </root> </configuration>
Example:includedConfig.xml
<included>
<appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d - %m%n</pattern>
</encoder>
</appender>
</included>
SLF4J
SLF4J 是使用最广泛的日志门面,定义了一套接口,为多个日志实现提供适配。支持如下后端
- java.util.logging
- logback
- log4j
- log4j2
使用 SLF4J 需要引入 slf4j-api.jar
基本使用
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
其他语法:
- 占位符
logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);
- Java8 流畅写法
logger.atDebug().addArgument(newT).addArgument(oldT).log("Temperature set to {}. Old temperature was {}.");
常见的 SLF 绑定
slf4j-log4j12
与 log4j version 1.2 绑定,此外需要引入 log4j.jarslf4j-jdk14
与 java.util.logging 绑定,又称为 JDK 1.4 loggingslf4j-nop
绑定NOP,静默丢弃所有日志记录slf4j-simple
绑定 到System.err
进行简单实现的绑定,它将所有事件输出到System.err。仅打印INFO或更高级别的消息。在小型应用程序的上下文中,此绑定可能很有用。slf4j-jcl
绑定 到 Jakarta Commons Logging。此绑定会将所有SLF4J日志记录委派给JCL。logback-classic
绑定到 logback 需要logback-core
log4j-slf4j-impl
绑定到 log4j2
源码
slf4j-api 的 源码十分简单,主要包含
org.slf4j.LoggerFactory
工具类,该类通过 捕捉NoClassDefFoundError
异常 与 绑定 包 的org.slf4j.impl.StaticLoggerBinder
绑定ILoggerFactory
绑定包 来实现 的 接口org.slf4j.Logger
接口,核心门面- 其他工具比如
MDC
实现一个 slf4j 绑定及的方式
- 实现 类
org.slf4j.impl.StaticLoggerBinder
- 实现 接口
org.slf4j.ILoggerFactory
- 实现 接口
org.slf4j.Logger
SpringBoot 集成
Spring Boot Version: 2.x
参考
依赖
不需要显示引入,因为 通过 spring-boot-starter
可以间接引入,包如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Logback 配置
spring-boot-starter-logging 默认使用 Logback
作为日志后端,使用 SLF4j
作为日志门面。并提供了一系列常用配置:
logging.config
log 后端配置文件位置,比如 logback 为classpath:logback.xml
logging.file.name
log 输出文件的文件名,默认为spring.log
logging.file.path
log 输出文件的位置,默认为 null,即不输出文件logging.level.root
全局日志级别 默认为INFO
logging.level.包名
指定指定包名的日志级别logging.pattern.console
命令行输出的格式化模式串,默认为%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
logging.pattern.file
文件输出的格式化模式串%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
logging.pattern.dateformat
日期格式化logging.file.*
文件输出目标的一些列配置
完全自定义
- 在 classpath 下 创建
logback.xml
或者logback-spring.xml
logback-spring.xml
参加 SpringBoot特性文档- 引用 Spring boot 配置文件变量