Logback学习系列(一)- 项目搭建


Logback学习系列


创建一个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 相关问题时非常有用。

以下是在您的应用程序中启用日志记录所需的三个步骤的列表。

  1. 配置logback环境。您可以通过几种或多或少复杂的方式来做到这一点。稍后会详细介绍。
  2. 在您希望执行日志记录的每个类中,通过调用 org.slf4j.LoggerFactory 类的 getLogger() 方法,将当前类名或类本身作为参数传递来检索 Logger 实例。
  3. 通过调用它的打印方法来使用这个记录器实例,即 debug()、info()、warn() 和 error() 方法。这将在配置的 appender 上产生日志输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值