java日志框架简介

文章目录

概要

文章简要梳理java常见日志框架,学习其背景和实现,通过阅读文章可以掌握如下知识点:

1、学习目前常用日志接口和实现框架,了解其功能作用实现原理。

2、掌握slf4j和logback的日志组合框架,debug跟踪一行日志的打印流程。

3、学习日常开发框架如Spring, Mybatis,了解大佬是如何实现日志模块。

常用日志框架

一、日志框架主要分为两类,日志门面接口和日志具体实现。

  1. 日志门面接口,如JCL 和 slf4j
    1)应用面向接口编程,接口不易变动。
    2)面向接口,可以通过绑定或桥接切换不同日志系统实现。
  2. 日志具体实现,如log4j JUL logback log4j2等。
    1)专注于实现日志打印功能实现。
    2)提供各种日志配置和功能特性。
常见框架有以下:
框架 功能 描述
log4j 实现 Apache 早期开源日志框架
JUL java.util.logging 实现 Sun官方自带日志框架,JDK1.4引入
JCL Apache Commons Logging 接口 Apache 的日志门面,可以切换log4j或JUL具体日志实现
slf4j-api 接口 简单java日志接口,日志门面,简单易用
logback 实现 高性能日志实现框架,包含一下模块: logback-core:基础模块 logback-classic:日志实现模块
log4j2 实现 Apache 的开源日志框架,性能优化版本
slf4j-jcl slf4j-jdk14 slf4j-log412 绑定 slf4j-api具体绑定实现
jcl-over-slf4j jul-to-slf4j log4j-over-slf4j 桥接 其他日志框架slf4j-api具体绑定实现

日志分层可参考如下图:

绑定查找具体实现过程:

slf4j StaticLoggerBinder绑定过程(slf4j-api-1.7.32 )
  1. 如果未初始化,执行初始化:LoggerFactory#performInitialization。
  2. 扫描类文件:org/slf4j/impl/StaticLoggerBinder.class,报告不存在类或存在多个类歧义。
  3. 由具体实现框架提logback-classic提供StaticLoggerBinder,触发其静态绑定。

TIP:sl4j-api 2.x版本使用SPI org.slf4j.spi.SLF4JServiceProvider,

logback提供service实现 ch.qos.logback.classic.spi.LogbackServiceProvider

JCL 运行时动态查找过程:(commons-logging-1.2)
  1. System.getProperty读org.apache.commons.logging.LogFactory
  2. 读META-INF/services/org.apache.commons.logging.LogFactory
  3. 读取类路径下commons-logging.properties,key=org.apache.commons.logging.LogFactory
  4. 使用默认实现org.apache.commons.logging.impl.LogFactoryImpl,按下面顺序获取Logger:
    Log4JLogger
    Jdk14Logger
    Jdk13LumberjackLogger
    SimpleLog
使用桥接修改具体日志实现

如果项目依赖第三包已经其他日志框架接口,那么这时候如何规范化统一日志实现,这时候可以使用桥接:

一行日志的打印过程

下面介绍一行日志的打印过程,以目前市面常见搭配组合slf4j-api-1.7.32 logback-classic-1.2.12为例,代码只展示部分关键源码:

1、LoggerFactory#getLogger(Class<?> clazz),

获取指定名称Logger。

java 复制代码
public static Logger getLogger(String name) {
     ILoggerFactory iLoggerFactory = getILoggerFactory();
     return iLoggerFactory.getLogger(name);
}

2、LoggerFactory#getILoggerFactory,

获取日志工厂LoggerFactory,如果未初始化则执行performInitialization完成日志实现绑定。

java 复制代码
public static ILoggerFactory getILoggerFactory() {
     if (INITIALIZATION_STATE == UNINITIALIZED) {
         synchronized (LoggerFactory.class) {
             if (INITIALIZATION_STATE == UNINITIALIZED) {
                 INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                 performInitialization();
             }
         }
     }
     ......
 }

3、LoggerFactory#bind,

绑定到具体日志实现,扫描类路径资源文件org/slf4j/impl/StaticLoggerBinder.class,加载完成绑定。

java 复制代码
private final static void bind() {
     staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
     reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
     ......
}

4、StaticLoggerBinder.getSingleton().getLoggerFactory(),

具体日志实现绑定初始化,如绑定到logback的StaticLoggerBinder,调用logback实现类StaticLoggerBinder#init->ContextInitializer#autoConfig,实现具体绑定类初始化。

logback ContextInitializer配置

1)ContextInitializer#configureByResource,按顺序检查如下配置文件logback-test.xml,logback.groovy,logback.xml,则存在使用配置文件。

2)SPI 加载是否指定Configurator配置实现,是则使用该配置实现。

3)如果上述都没有,则使用默认配置实现BasicConfigurator。

java 复制代码
public ILoggerFactory getLoggerFactory() {
        public void autoConfig() throws JoranException {
        StatusListenerConfigHelper.installIfAsked(loggerContext);
        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);
            }
        }
    }
}

5、LoggerContext#getLogger,

获取指定名称logger。

java 复制代码
public final Logger getLogger(final String name) {
	......
	synchronized (logger) {
        childLogger = logger.getChildByName(childName);
        if (childLogger == null) {
            childLogger = logger.createChildByName(childName);
            loggerCache.put(childName, childLogger);
            incSize();
        }
    }
	......
}

6、Logger#info,

具体一行日志打印,以Info级别日志打印为例:

  1. 全局过滤器 TurboFilter判断是否打印
    2)构建LoggingEvent,将日志事件投递给Appender#doAppend
    3)调用Appender配置过滤Filter判断是否打印
    4)交给具体Appender完成日志事件处理,实现有:
    控制台ConsoleAppender
    文件FileAppender
    归档文件RollingFileAppender
    异步AsyncAppender
    数据库DBAppender
    等等。
java 复制代码
private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
                    final Throwable t) {

    final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);

    if (decision == FilterReply.NEUTRAL) {
        if (effectiveLevelInt > level.levelInt) {
            return;
        }
    } else if (decision == FilterReply.DENY) {
        return;
    }

    buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
}

其他日志打印格式化相关:

Appender

Encoder

Layout

开源框架日志模块

Spring && Springboot

Spring使用JCL门面,如果不引入commons-logging或其他实现jar。按上面描述会使用JDK自带JUL作为日志实现。

Springboot提供logging的starter。

spring-boot-starter-logging, 默认使用logback实现,桥接了log4j和JUL到slf4j。

TIP: 如果项目有引入commons-logging, 还需要手动引入桥接 jcl-over-slf4j

xml 复制代码
<dependencies>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-to-slf4j</artifactId>
      <version>2.13.3</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jul-to-slf4j</artifactId>
      <version>1.7.30</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>

Springboot框架日志初始化流程如下:

1、监听Spring容器生命周期

2)LoggingSystem#get,加载具体日志系统实现,默认按如下优先级:

3)日志系统切入Springboot应用生命周期

监听ApplicationStartingEvent,触发LoggingSystem#beforeInitialize

监听ApplicationEnvironmentPreparedEvent,触发LoggingSystem#initialize

监听onApplicationPreparedEvent,注册日志系统相关单例bean到Spring容器

监听ContextClosedEvent/onApplicationFailedEvent, 日志系统清理

Mybatis

Mybatis对常见日志框架包装一层,使用自定义日志接口,通过配置或默认规则设置具体日志实现框架:

1)尝试加载实现,按如下顺序优先级加载。

2)如果通过配置指定具体实现,则使用具体实现。

java 复制代码
Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
相关推荐
一只特立独行的猪6112 小时前
Java面试——集合篇
java·开发语言·面试
讓丄帝愛伱3 小时前
spring boot启动报错:so that it conforms to the canonical names requirements
java·spring boot·后端
weixin_586062023 小时前
Spring Boot 入门指南
java·spring boot·后端
Dola_Pan6 小时前
Linux文件IO(二)-文件操作使用详解
java·linux·服务器
wang_book6 小时前
Gitlab学习(007 gitlab项目操作)
java·运维·git·学习·spring·gitlab
蜗牛^^O^7 小时前
Docker和K8S
java·docker·kubernetes
从心归零7 小时前
sshj使用代理连接服务器
java·服务器·sshj
IT毕设梦工厂8 小时前
计算机毕业设计选题推荐-在线拍卖系统-Java/Python项目实战
java·spring boot·python·django·毕业设计·源码·课程设计
Ylucius9 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
七夜zippoe9 小时前
分布式系统实战经验
java·分布式