创建一个maven项目,并引入logback
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ch.qos.logback</groupId>
<version>1.2.3</version>
<artifactId>logback-test</artifactId>
<packaging>jar</packaging>
<name>Logback Test Module</name>
<description>logback-test module</description>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
</plugins>
</build>
</project>
然后创建一个java类
package ch.qos.logback.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld1 {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
logger.debug("Hello world.");
}
}
运行以上的java类,此时控制台打印如下信息
19:16:08.327 [main] DEBUG chapters.introduction.HelloWorld1 - Hello world.
在这里我们并没有引入任何的其他配置文件,只是定义了一个Logger实例。
对于logback的默认配置策略,如果没有默认的配置文件存在,则会在根节点logger添加一个默认的ConsoleAppender,这个Appender主要用于往控制台打印日志。
在Logback的生命周期中发生的重要事件可以通过一个名称为StatusManager的组件获取,比如在下面的示例当中我们可以通过StatusPrinter来打印Logback的内部状态。
package ch.qos.logback.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.util.StatusPrinter;
public class HelloWorld2 {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld2");
logger.debug("Hello world.");
// print internal state
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
}
}
执行结果如下
19:46:30.899 [main] DEBUG chapters.introduction.HelloWorld2 - Hello world.
19:46:30,746 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
19:46:30,750 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
19:46:30,750 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
19:46:30,767 |-INFO in ch.qos.logback.classic.BasicConfigurator@5ca881b5 - Setting up default configuration.
Logback 解释说,由于找不到 logback-test.xml 和 logback.xml 配置文件(稍后讨论),它使用其默认策略配置自己,这是一个基本的 ConsoleAppender。 Appender 是一个可以被视为输出目的地的类。 Appender 存在于许多不同的目的地,包括控制台、文件、系统日志、TCP 套接字、JMS 等等。用户还可以根据自己的具体情况轻松创建自己的 Appender。
请注意,如果出现错误,logback 将自动在控制台上打印其内部状态。
源码分析,在前面无论是通过LoggerFactory调用getLogger方法还是getILoggerFactory方法,首先都是首先调用getILoggerFactory。
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
而getILoggerFactory返回的ILoggerFactory是slf4j针对日志工厂的接口抽象。通过这个工厂通过名称获取Logger。在logback对应的实现为ch.qos.logback.classic.LoggerContext。
所以上面获取日志工厂其实就是要构造一个对象,但是ILoggerFactory是slf4j中的包,slf4j并不依赖于logback,那么在slf4j是如何调用到logback中的方法或是类的构造的呢?
在org.slf4j.LoggerFactory#performInitialization方法当中这里首先进行绑定,然后进行版本检查。
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
在bind当中,存在以下的源码
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
这里有一个类org.slf4j.impl.StaticLoggerBinder
,看起来像是slf4j中的类,但其实在slf4j中并不包含这个类。而是在findPossibleStaticLoggerBinderPathSet方法中从当前classpath下查找的。
// We need to use the name of the StaticLoggerBinder class, but we can't
// reference
// the class itself.
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
这里将类名写死,然后去查找,而实现slf4j接口的日志框架都必须定义这个类。当然如果在当前环境下包含有多个实现(其实也就是多个日志框架)时,会打印日志Class path contains multiple SLF4J bindings.
。
private static boolean isAmbiguousStaticLoggerBinderPathSet(Set<URL> binderPathSet) {
return binderPathSet.size() > 1;
}
/**
* Prints a warning message on the console if multiple bindings were found
* on the class path. No reporting is done otherwise.
*
*/
private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
Util.report("Class path contains multiple SLF4J bindings.");
for (URL path : binderPathSet) {
Util.report("Found binding in [" + path + "]");
}
Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
}
}
接下来调用StaticLoggerBinder.getSingleton()方法,也就是会到实现slf4j的框架中了。在logback当中org.slf4j.impl.StaticLoggerBinder的实现通过类加载器保证单例。
/**
* The unique instance of this class. 通过类加载器保证单例
*/
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
private static Object KEY = new Object();
static {
SINGLETON.init();
}
同时包含了LoggerContext的实现,由于StaticLoggerBinder是单例,所以这个LoggerContext对象也是单例。
private boolean initialized = false;
private LoggerContext defaultLoggerContext = new LoggerContext();
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
// 设置默认的Logger上下文名称
private StaticLoggerBinder() {
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
}
LoggerContext对应的默认构造为
public LoggerContext() {
super();
this.loggerCache = new ConcurrentHashMap<String, Logger>();
this.loggerContextRemoteView = new LoggerContextVO(this);
this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
this.root.setLevel(Level.DEBUG);
loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
initEvaluatorMap();
size = 1;
this.frameworkPackages = new ArrayList<String>();
}
在这里创建了一个名称为root的logger,并且设置日志级别为DEBUG,然后存放到一个名称为loggerCache的Map当中。最后如下所示
调用ch.qos.logback.core.util.StatusPrinter#print方法,会通过logger上下文对象(LoggerContext)获取StatusManager对象,
public static void print(Context context, long threshold) {
if (context == null) {
throw new IllegalArgumentException("Context argument cannot be null");
}
StatusManager sm = context.getStatusManager();
if (sm == null) {
ps.println("WARN: Context named \"" + context.getName() + "\" has no status manager");
} else {
print(sm, threshold);
}
}
获取其中的statusList,其中记录了状态日志,然后拼接并打印。
private static PrintStream ps = System.out;
public static void print(StatusManager sm, long threshold) {
StringBuilder sb = new StringBuilder();
// 获取状态列表
List<Status> filteredList = filterStatusListByTimeThreshold(sm.getCopyOfStatusList(), threshold);
// 拼接字符串
buildStrFromStatusList(sb, filteredList);
ps.println(sb.toString());
}
结果如下
22:41:31,727 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
22:41:31,728 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
22:41:31,728 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/E:/official/logback-v_1.2.3/logback-test/target/classes/logback.xml]
22:41:34,922 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
22:41:34,924 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
22:41:34,949 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
22:41:34,967 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
22:41:35,120 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [chapters.introduction] to INFO
22:41:35,120 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG
22:41:35,120 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
22:41:35,121 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
22:41:35,127 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@4566e5bd - Registering current configuration as safe fallback point
这里日志与上面不同,是因为在环境当中添加了logback.xml配置文件,如下所示
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned by default the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="chapters.introduction" level="INFO" />
<!-- Strictly speaking, the level attribute is not necessary since -->
<!-- the level of the root level is set to DEBUG by default. -->
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
前面的例子相当简单。在更大的应用程序中实际情况不会有那么大的不同。日志语句的一般模式不会改变。只有配置过程会有所不同。但是,您可能希望根据需要自定义或配置 logback。 Logback 配置将在后续章节中介绍。
请注意,在上面的示例中,我们已经通过调用 StatusPrinter.print() 方法指示 logback 打印其内部状态。 Logback 的内部状态信息在诊断 logback 相关问题时非常有用。
以下是在您的应用程序中启用日志记录所需的三个步骤的列表。
- 配置logback环境。您可以通过几种或多或少复杂的方式来做到这一点。稍后会详细介绍。
- 在您希望执行日志记录的每个类中,通过调用 org.slf4j.LoggerFactory 类的 getLogger() 方法,将当前类名或类本身作为参数传递来检索 Logger 实例。
- 通过调用它的打印方法来使用这个记录器实例,即 debug()、info()、warn() 和 error() 方法。这将在配置的 appender 上产生日志输出。