文章目录
Logback
Logback与Log4j的作者是同一个人,从官网介绍中也说明了,Logback的目的是作为流行的log4j项目的接替者。
Logback分为三个模块:
- logback-core:核心代码模块,为其他两个模块奠定基础。
- logback-classic:对Log4j的一个改良版本,同时实现了slf4j的接口,因此您可以轻松地在logback和其他日志框架(如log4j或java.util.logging)之间来回切换。
- logback-access:与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。
Logback的Maven引入
我们在项目中引入logback-classic这个依赖后,lib中自动多了这三个jar
<!--这个依赖直接包含了 logback-core 以及 slf4j-api的依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
查看logback-classic这个包的pom文件,原来是通过依赖传递过来的。
Logback与日志门面slf4j
门面模式定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
日志门面,其含义也是如此,我们看一下Logback结合slf4j的使用,我们发现无论是Logger对象还是创建Logger的工厂LoggerFactory都是slf4j包中的类。这和之前的Log4j、JUL不一样,需要再往下看slf4j是如何实现日志功能的。看代码前就可以猜测:肯定是slf4j调用Logback包中的类。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackTest {
public static Logger logger = LoggerFactory.getLogger(LogbackTest.class.getName());
}
我们先看org.slf4j.Logger,发现它是一个接口,定义了一系列日志记录相关的抽象方法。这个接口的注释说的很明白:Logger接口是slf4j-API的主要用户入口点。期望其他日志工具的Logger都实现这个接口。logback的ch.qos.logback.classic.Logger类也确实很听话,直接就实现了这个接口。
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {
}
然后,我们再看一下org.slf4j.LoggerFactory是如何获取到Logger对象的。现获取ILoggerFactory对象,这个对象是一个接口,只定义了一个getLogger方法,由此推测这个接口的实现类应该会缓存已创建的Logger对象,类似于JUL中的LogContext和Log4j中的LoggerRepository的作用。
public static Logger getLogger(String name) {
// 先获取ILoggerFactory
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
getILoggerFactory会先判断INITIALIZATION_STATE这个变量的值,根据名称能看出来这是和初始化相关的变了。LoggerFactory中定义了四个常量值,分别代表四个初始化的状态。
static final int UNINITIALIZED = 0;// 未初始化
static final int ONGOING_INITIALIZATION = 1;// 初始化中
static final int FAILED_INITIALIZATION = 2;// 初始化失败
static final int SUCCESSFUL_INITIALIZATION = 3;// 初始化成功
static final int NOP_FALLBACK_INITIALIZATION = 4;// 无回退初始化
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
// 先判断是否初始化完成,如果没有就进行初始化
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
当LoggerFactory初始化完成后,会通过StaticLoggerBinder.getSingleton().getLoggerFactory()这个方法给我们返回一个LoggerFactory对象,我们看一下StaticLoggerBinder这个类的具体逻辑
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
private LoggerContext defaultLoggerContext = new LoggerContext();
static {
SINGLETON.init();
}
void init() {
try {
try {
// 在这里对空的defaultLoggerContext进行自动配置
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Exception t) { // see LOGBACK-1159
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
我们看一下ContextInitializer这个类是如何初始化LoggerContext,其定义了几个静态常量来代表可配置文件的名称。加载的顺序是:先会从运行时环境中根据CONFIG_FILE_PROPERTY这个常量值的属性获取一个配置的URL,如果获取不到就尝试用TEST_AUTOCONFIG_FILE、GROOVY_AUTOCONFIG_FILE、AUTOCONFIG_FILE依次来进行获取。如果还是获取不到资源的URL就会通过BasicConfigurator类来初始化一个基础的LoggerContext。
final public static String GROOVY_AUTOCONFIG_FILE = "logback.groovy";
final public static String AUTOCONFIG_FILE = "logback.xml";
final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
final public static String CONFIG_FILE_PROPERTY = "logback.configurationFile";
public void autoConfig() throws JoranException {
StatusListenerConfigHelper.installIfAsked(loggerContext);
// 没有任何配置文件时url为null
URL url = findURLOfDefaultConfigurationFile(true);
if (url != null) {
configureByResource(url);
} else {
Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
if (c != null) {
try {
c.setContext(loggerContext);
c.configure(loggerContext);
} catch (Exception e) {
throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
.getCanonicalName() : "null"), e);
}
} else {
// 这一段代码保证了,没有任何配置文件时,也可以进行基础的自动配置,进行日志输出
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}
logback.xml 配置
configuration节点属性
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true" scan="true" scanPeriod="1 seconds">
</configuration>
configuration子节点contextName
设置日志上下文名称,后面输出格式中可以通过定义 %contextName 来打印日志上下文名称。
<contextName>logback</contextName>
configuration子节点property
用来设置相关变量,通过key-value的方式配置,然后在后面的配置文件中通过 ${key}来访问
<property name="app.name" value="logback_test"/>
configuration子节点appender
日志输出组件,主要负责日志的输出以及格式化日志。常用的属性有name和class。
ConsoleAppender
向控制台输出日志内容的组件,只要定义好encoder节点就可以使用。
子节点:
- encoder:对日志进行格式化。
- target:字符串 System.out 或者 System.err ,默认 System.out。
<!-- 输出格式:d%表示时间格式,%thread表示运行线程,%-5level表示缩进5位显示级别,%logger{50} - %msg表示哪一个类输出的信息,%n为换行 -->
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" />
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!--定义了一个过滤器,在LEVEL之下的日志输出不会被打印出来-->
<!--这里定义了DEBUG,也就是控制台不会输出比ERROR级别小的日志-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<!-- encoder 默认配置为PatternLayoutEncoder -->
<!--定义控制台输出格式-->
<encoder>
<pattern>%d [%thread] %-5level %logger{36} [%file : %line] - %msg%n</pattern>
</encoder>
</appender>
FileAppender
向文件输出日志内容的组件,用法也很简单,不过由于没有日志滚动策略,一般很少使用。
子节点:
- file:被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。
- append:如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。
- encoder:对记录事件进行格式化。(具体参数稍后讲解 )
- prudent:如果是 true,日志会被安全的写入文件,即使其他的FileAppender也在向此文件做写入操作,效率低,默认是 false。
RollingFileAppender
向文件输出日志内容的组件,同时可以配置日志文件滚动策略,在日志达到一定条件后生成一个新的日志文件。
有以下子节点:
- file:被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。
- append:如果是 true,日志被追加到文件结尾,如果是 false,清空现存文件,默认是true。
- encoder:对记录事件进行格式化。(具体参数稍后讲解 )
- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。
- triggeringPolicy: 告知 RollingFileAppender 何时激活滚动。
- prudent:当为true时,不支持FixedWindowRollingPolicy。支持TimeBasedRollingPolicy,但是有两个限制,1不支持也不允许文件压缩,2不能设置file属性,必须留空。
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--定义日志输出的路径-->
<!--这里的scheduler.manager.server.home 没有在上面的配置中设定,所以会使用java启动时配置的值-->
<!--比如通过 java -Dscheduler.manager.server.home=/path/to XXXX 配置该属性-->
<file>${scheduler.manager.server.home}/logs/${app.name}.log</file>
<!--定义日志滚动的策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--定义文件滚动时的文件名的格式-->
<fileNamePattern>${scheduler.manager.server.home}/logs/${app.name}.%d{yyyy-MM-dd.HH}.log.gz
</fileNamePattern>
<!--60天的时间周期,日志量最大20GB-->
<maxHistory>60</maxHistory>
<!-- 该属性在 1.1.6版本后 才开始支持-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<!--每个日志文件最大100MB-->
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<!--定义输出格式-->
<encoder>
<pattern>%d [%thread] %-5level %logger{36} [%file : %line] - %msg%n</pattern>
</encoder>
</appender>