问题/释疑
1.logback是什么?
logback是log4j团队创建的开源日志组件。与log4j类似,但是比log4j更强大,是log4j的改良版本。主要优势在于:
a) 更快的实现,logback内核重写过,是的性能有了很大的提升,内存占用也更小。
b) logback-classic对slf4j进行了更好的集成
c) 自动重新加载配置文件,当配置文件修改后,logback-classic能自动重新加载配置文件
d) 配置文件能够处理不同的情况,开发人员在不同的环境下(开发,测试,生产)切换的时候,不需要创建多个文件,可以通过<if><else><then>标签来实现
e) 自动压缩已经打出来的日志文件:RollingFileAppender在产生新文件的时候,会自动压缩已经打印出来的日志文件。而且这个压缩的过程是一个异步的过程。
2.logback与slf4j有什么区别?
slf4j是一系列的日志接口,而log4j和logback是具体实现了的日志框架。
a) log4j是apache实现的一个开源日志组件
b) logback同样是由log4j的作者实现的,拥有更好的特性,是slf4j的原生实现
参见:详情
3.logback中的logger、appender、layout?
logger:作为日志的记录器,把它关联到对应的context后,主要用于存放日志对象,也可以定义日志类型,级别。
appender:主要用于指定日志输出的目的地。目的地可以是控制台,文件,远程套接字服务器,数据库mysql等。
layout:负责把时间转换成字符串,格式化日志信息的输出。
4.logback的日志级别有哪些?
logger的日志级别主要包括:TRACE<DEBUG<INFO<WARN<ERROR
定义在ch.qos.logback.classic.Level类中。
public static final Level OFF = new Level(OFF_INT, "OFF");
public static final Level ERROR = new Level(ERROR_INT, "ERROR");
public static final Level WARN = new Level(WARN_INT, "WARN");
public static final Level INFO = new Level(INFO_INT, "INFO");
public static final Level DEBUG = new Level(DEBUG_INT, "DEBUG");
public static final Level TRACE = new Level(TRACE_INT, "TRACE");
public static final Level ALL = new Level(ALL_INT, "ALL");
public static final int OFF_INT = Integer.MAX_VALUE;
public static final int ERROR_INT = 40000;
public static final int WARN_INT = 30000;
public static final int INFO_INT = 20000;
public static final int DEBUG_INT = 10000;
public static final int TRACE_INT = 5000;
public static final int ALL_INT = Integer.MIN_VALUE;
此外OFF表示关闭全部日志,ALL表示开启全部日志。
如果logger没有被分配日志级别,它将从有被分别日志级别的父类那里继承,root logger的默认级别是DEBUG
5.什么是logback的context上下文?
每一个logger都被关联到一个loggerContext中,loggerContext负责生产logger,也负责以树形结构排列各个logger。
logger的获取主要是通过org.slf4j.LoggerFactory的getLogger()方法获取。
getLogger()方法有两种实现方式
a) getLogger(Class Obj)通过传入一个类的形式,来进行logger对象和类的绑定。
b) getLogger(String name)方式是通过传入一个contextName的形式,来指定一个logger。
其中,用同一个名字调用该方法获取的永远都是同一个logger对象。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//通过LoggerFactory获取
private static final Logger logger = LoggerFactory.getLogger(Blog.class);
6.logback中主要模块有哪些?
logback主要有以下三个模块:
logback-core:所有logback模块的基础
logback-classic:是log4j的一个改良版本,同时完整的实现了slf4j api
logback-access:访问模块和servlet容器集成,提供通过http来访问日志的功能。
7.为什么有@slf4j的注解,就可以调用log实例写入日志?
我们可以看到@slf4j 的源码
* Example:
* <pre>
* @Slf4j
* public class LogExample {
* }
* </pre>
*
* will generate:
*
* <pre>
* public class LogExample {
* private static final org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(LogExample.class);
* }
* </pre>
上面的源码注解显示在一个类前加上一个@slf4j 注解,将会在代码中自动生成一个Logger对象。
8.logback的配置文件详解?
a) 根节点<configuration>主要包含三个属性
scan:此属性为true时,配置文件如果发生改变,将会重新加载,默认为true
scanPeriod:设置监测配置文件是否有修改的时间间隔,默认为1分钟,如果没有指定时间单位,默认的单位是毫秒
debug:此属性决定是否打印出logback内部的日志信息,可实时查看logback的运行状态
<configuration scan="true" scanPeriod="60 seconds" debug="true">
</configuration>
b) 子节点<contextName>主要用来设置上下文的名称,前面说到,每个logger都会关联到logger上下文,默认的上下文名称为default。这里可进行配置用来区分不同应用的名称
<contextName>demo</contextName>
c) 子节点<appender>用于配置写日志的组件。有两个必要的属性,name和class
name:用于指定appender的名称
class:用于指定appender全限定名
<appender name = "fileErrorLog" class = "ch.qos.logback.core.rolling.RollingFileAppender"></appender>
主要有三种类型:ConsoleAppender、FileAppender、RollingFileAppender
ConsoleAppender:把日志输到控制台
FileAppender:把日志输到文件
RollingFileAppender:把日志输到文件并且进行定期的清理
d) 子节点<root>是说有logger的上级,只有一个level属性,level属性用来设置打印级别。
<configuration>
<appender name = "consoleLog" class = "ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%d ------ %level [%thread] %caller{1} - %msg%n
</pattern>
</layout>
</appender>
<appender name = "fileInfoLog" class = "ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>
%d ------ %level [%thread] %caller{1} - %msg%n
</pattern>
</encoder>
<!-- Size and time based archiving -->
<rollingPolicy class = "ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/opt/log/dev_manage/dev-manage-info.%d-%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<appender name = "fileErrorLog" class = "ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%d ------ %level [%thread] %caller{1} - %msg%n
</pattern>
</encoder>
<rollingPolicy class = "ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/opt/log/dev_manage/dev-manage-error.%d-%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level = "info">
<appender-ref ref="consoleLog"/>
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
</root>
</configuration>
下面的配置文件摘自其他页面
<?xml version="1.0" encoding="UTF-8"?>
<!-- scan属性未true时,如果配置文档发生改变将会进行重新加载 -->
<!-- scanPeriod属性设置监测配置文件修改的时间间隔,默认单位为毫秒,在scan为true时才生效 -->
<!-- debug属性如果为true时,会打印出logback内部的日志信息 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 定义参数常量 -->
<!-- 日志级别:TRACE<DEBUG<INFO<WARN<ERROR,其中常用的是DEBUG、INFO和ERROR -->
<property name="log.level" value="debug" />
<!-- 文件保留时间 60天-->
<property name="log.maxHistory" value="60" />
<!-- 日志存储路径 -->
<property name="log.filePath" value="/opt/logs" />
<!-- 日志的显式格式 -->
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n"></property>
<!-- 用于说明输出介质,此处说明控制台输出 -->
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<!-- 类似于layout,除了将时间转化为数组,还会将转换后的数组输出到相应的文件中 -->
<encoder>
<!-- 定义日志的输出格式 -->
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- DEBUG,表示文件随着时间的推移按时间生成日志文件 -->
<appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 文件路径 -->
<file>${log.filePath}/debug.log</file>
<!-- 滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 设置文件名称 -->
<fileNamePattern>
${log.filePath}/debug/debug.%d{yyyy-MM-dd}.log
</fileNamePattern>
<!-- 设置最大保存周期 -->
<MaxHistory>${log.maxHistory}</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<!-- 过滤器,过滤掉不是指定日志水平的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置日志级别 -->
<level>DEBUG</level>
<!-- 如果跟该日志水平相匹配,则接受 -->
<onMatch>ACCEPT</onMatch>
<!-- 如果跟该日志水平不匹配,则过滤掉 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- INFO,表示文件随着时间的推移按时间生成日志文件 -->
<appender name="infoAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 文件路径 -->
<file>${log.filePath}/info.log</file>
<!-- 滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 设置文件名称 -->
<fileNamePattern>
${log.filePath}/info/info.%d{yyyy-MM-dd}.log.gz
</fileNamePattern>
<!-- 设置最大保存周期 -->
<MaxHistory>${log.maxHistory}</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<!-- 过滤器,过滤掉不是指定日志水平的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置日志级别 -->
<level>INFO</level>
<!-- 如果跟该日志水平相匹配,则接受 -->
<onMatch>ACCEPT</onMatch>
<!-- 如果跟该日志水平不匹配,则过滤掉 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- ERROR,表示文件随着时间的推移按时间生成日志文件 -->
<appender name="errorAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 文件路径 -->
<file>${log.filePath}/error.log</file>
<!-- 滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 设置文件名称 -->
<fileNamePattern>
${log.filePath}/error/error.%d{yyyy-MM-dd}.log.gz
</fileNamePattern>
<!-- 设置最大保存周期 -->
<MaxHistory>${log.maxHistory}</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<!-- 过滤器,过滤掉不是指定日志水平的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置日志级别 -->
<level>ERROR</level>
<!-- 如果跟该日志水平相匹配,则接受 -->
<onMatch>ACCEPT</onMatch>
<!-- 如果跟该日志水平不匹配,则过滤掉 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 用于存放日志对象,同时指定关联的package位置 -->
<!-- name指定关联的package -->
<!-- level表明指记录哪个日志级别以上的日志 -->
<!-- appender-ref指定logger向哪个文件输出日志信息 -->
<!-- additivity为true时,logger会把根logger的日志输出地址加入进来,但logger水平不依赖于根logger -->
<logger name="com.campus.o2o" level="${log.level}" additivity="true">
<appender-ref ref="debugAppender" />
<appender-ref ref="infoAppender" />
<appender-ref ref="errorAppender" />
</logger>
<!-- 特殊的logger,根logger -->
<root lever="info">
<!-- 指定默认的日志输出 -->
<appender-ref ref="consoleAppender" />
</root>
</configuration>
logback源码解析
slf4j
Logger接口
public interface Logger {
// 返回根logger的name
final public String ROOT_LOGGER_NAME = "ROOT";
// 返回当前Logger实例的name
public String getName();
// 判断trace级别是否打印
public boolean isTraceEnabled();
public void trace(String msg);
/**其他trace构造方法*/
// 判断debug级别是否打印
public boolean isDebugEnabled();
public void debug(String msg);
/**其他debug构造方法*/
// 判断info级别是否打印
public boolean isInfoEnabled();
public void info(String msg);
/**其他info构造方法*/
// 判断warn级别是否打印
public boolean isWarnEnabled();
public void warn(String msg);
/**其他warn构造方法*/
// 判断error级别是否打印
public boolean isErrorEnabled();
public void error(String msg);
/**其他error构造方法*/
}
这个接口需要被log4j或logback来实现。
LoggerFactory类
/**
* The <code>LoggerFactory</code> is a utility class producing Loggers for
* various logging APIs, most notably for log4j, logback and JDK 1.4 logging.
* Other implementations such as {@link org.slf4j.impl.NOPLogger NOPLogger} and
* {@link org.slf4j.impl.SimpleLogger SimpleLogger} are also supported.
*/
LoggerFactory是各种各样日志api用来生成Loggers的一个很有用的工厂类,尤其是log4j、logback和jdk的logging。其他的实现像NOPLogger和SimpleLogger也是支持的。
看一下它的getLogger方法,通过logger的name来获得logger实例
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
这里ILoggerFactory的getLogger(name),也是一个接口中的方法,需要logback去实现
public interface ILoggerFactory {
public Logger getLogger(String name);
}
获得ILoggerFactory实例的方法,想当于是初始化的过程
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");
}
logback
LogContext实现上面提到的ILoggerFactory接口
public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle
实现getLogger(name)方法
@Override
public final Logger getLogger(final String name) {
if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
}
// if we are asking for the root logger, then let us return it without
// wasting time
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
}
int i = 0;
Logger logger = root;
// check if the desired logger exists, if it does, return it
// without further ado.
Logger childLogger = (Logger) loggerCache.get(name);
// if we have the child, then let us return it without wasting time
if (childLogger != null) {
return childLogger;
}
// if the desired logger does not exist, them create all the loggers
// in between as well (if they don't already exist)
String childName;
while (true) {
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -1) {
childName = name;
} else {
childName = name.substring(0, h);
}
// move i left of the last point
i = h + 1;
synchronized (logger) {
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
childLogger = logger.createChildByName(childName);
loggerCache.put(childName, childLogger);
incSize();
}
}
logger = childLogger;
if (h == -1) {
return childLogger;
}
}
}