searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Spring,SpringBoot,Mybatis日志框架浅析

2023-08-18 08:33:09
14
0

一、日志框架

1)目前主流的日志框架有:log4j1,log4j2,logback,jul(JDK内部实现的日志框架)

      日志框架那么多,我们平时写代码的时候,难道要硬编码某一种具体的日式框架?由此引出JCL 和 SLF4J。

2)JCL:全称为Jakarta Commons Logging,是Apache提供通用日志的API,其原理如下:

内部维护了一个数组,通过反射去获取具体的日志实现框架,加载顺序是按照数组顺序来的,因此,一旦找到log4j,那么就会使用log4j,没找到就使用JDK提供的日志框架jul。

其缺点也很明显,在代码里直接写死了4种日志框架,其他日志框架,无法使用

private static final String[] classesToDiscover =
	new String[]{"org.apache.commons.logging.impl.Log4JLogger",
	"org.apache.commons.logging.impl.Jdk14Logger",
	"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
	"org.apache.commons.logging.impl.SimpleLog"};
 

3)SLF4J 一套抽象的日志API,其原理如下:

SLF4J需要使用绑定器,绑定到具体的日志实现框架,才能完成日志打印。


如果一个项目中,一个模块使用了log4j1 ,另外一个模块使用了slf4j+logback绑定器,那项目中就会有两种日志格式。

如何解决这个问题呢?使用桥接器,把log4j1的日志桥接到slf4j,这样打印格式都会统一到logback。

二、Spring日志

Spring4使用的是JCL,Spring5使用的是spring-jcl(因为JCL不维护了,因此spring把源码搞过来,自己封装成了一个模块)

spring-jcl 原理如下:

static {
		if (isPresent(LOG4J_SPI)) {
			if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
				// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
				// however, we still prefer Log4j over the plain SLF4J API since
				// the latter does not have location awareness support.
				logApi = LogApi.SLF4J_LAL;
			}
			else {
				// Use Log4j 2.x directly, including location awareness support
				logApi = LogApi.LOG4J;
			}
		}
		else if (isPresent(SLF4J_SPI)) {
			// Full SLF4J SPI including location awareness support
			logApi = LogApi.SLF4J_LAL;
		}
		else if (isPresent(SLF4J_API)) {
			// Minimal SLF4J API without location awareness support
			logApi = LogApi.SLF4J;
		}
		else {
			// java.util.logging as default
			logApi = LogApi.JUL;
		}
	}
	

从上面我们可以看出,Spring5.* 比较偏爱log4j2这个日志框架。

 

三、SpringBoot日志

SpringBoot集成各种开源组件, 是如何做到统一使用logback来打印日志呢?

SpringBoot 通过引入log4j2桥接器(log4j-to-slf4j),JUL桥接器(jul-to-slf4j),将其他的开源组件使用到的日志(log4j2,jul)桥接到sl4fj

 

 

四、Mybatis日志

通过源码可以发现,Mybatis日志处理方式,也是顺序去加载日志框架,加载到哪个就使用哪个,跟JCL框架比较像。

而且我们可以发现,Mybatis是比较偏爱slf4j,一旦找到了slf4j就不会再去找其他的日志框架了。

   
package org.apache.ibatis.logging;

import java.lang.reflect.Constructor;
import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl;
import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl;
import org.apache.ibatis.logging.log4j.Log4jImpl;
import org.apache.ibatis.logging.log4j2.Log4j2Impl;
import org.apache.ibatis.logging.nologging.NoLoggingImpl;
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.logging.stdout.StdOutImpl;

public final class LogFactory {
    public static final String MARKER = "MYBATIS";
    private static Constructor<? extends Log> logConstructor;

    private LogFactory() {
    }

    public static Log getLog(Class<?> clazz) {
        return getLog(clazz.getName());
    }

    public static Log getLog(String logger) {
        try {
            return (Log)logConstructor.newInstance(logger);
        } catch (Throwable var2) {
            throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + var2, var2);
        }
    }

    public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
        setImplementation(clazz);
    }

    public static synchronized void useSlf4jLogging() {
        setImplementation(Slf4jImpl.class);
    }

    public static synchronized void useCommonsLogging() {
        setImplementation(JakartaCommonsLoggingImpl.class);
    }

    public static synchronized void useLog4JLogging() {
        setImplementation(Log4jImpl.class);
    }

    public static synchronized void useLog4J2Logging() {
        setImplementation(Log4j2Impl.class);
    }

    public static synchronized void useJdkLogging() {
        setImplementation(Jdk14LoggingImpl.class);
    }

    public static synchronized void useStdOutLogging() {
        setImplementation(StdOutImpl.class);
    }

    public static synchronized void useNoLogging() {
        setImplementation(NoLoggingImpl.class);
    }

    private static void tryImplementation(Runnable runnable) {
        if (logConstructor == null) {
            try {
                runnable.run();
            } catch (Throwable var2) {
            }
        }

    }

    private static void setImplementation(Class<? extends Log> implClass) {
        try {
            Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
            Log log = (Log)candidate.newInstance(LogFactory.class.getName());
            if (log.isDebugEnabled()) {
                log.debug("Logging initialized using '" + implClass + "' adapter.");
            }

            logConstructor = candidate;
        } catch (Throwable var3) {
            throw new LogException("Error setting Log implementation.  Cause: " + var3, var3);
        }
    }

    static {
        tryImplementation(LogFactory::useSlf4jLogging);
        tryImplementation(LogFactory::useCommonsLogging);
        tryImplementation(LogFactory::useLog4J2Logging);
        tryImplementation(LogFactory::useLog4JLogging);
        tryImplementation(LogFactory::useJdkLogging);
        tryImplementation(LogFactory::useNoLogging);
    }
}
 

五、其他

1)各种日志框架的绑定器和桥接器

注:绑定器一般已经包含了SLF4J和具体的日志实现框架,因此引入绑定器之后,不需要再显式映入SLF4J和具体的日志框架。


2)log4j1 的桥接器和log4j1不能同时存在,因为log4j1的桥接器,对log4j1 的源码做了修改,同时引入,会报错。

0条评论
0 / 1000
王****全
2文章数
0粉丝数
王****全
2 文章 | 0 粉丝
王****全
2文章数
0粉丝数
王****全
2 文章 | 0 粉丝
原创

Spring,SpringBoot,Mybatis日志框架浅析

2023-08-18 08:33:09
14
0

一、日志框架

1)目前主流的日志框架有:log4j1,log4j2,logback,jul(JDK内部实现的日志框架)

      日志框架那么多,我们平时写代码的时候,难道要硬编码某一种具体的日式框架?由此引出JCL 和 SLF4J。

2)JCL:全称为Jakarta Commons Logging,是Apache提供通用日志的API,其原理如下:

内部维护了一个数组,通过反射去获取具体的日志实现框架,加载顺序是按照数组顺序来的,因此,一旦找到log4j,那么就会使用log4j,没找到就使用JDK提供的日志框架jul。

其缺点也很明显,在代码里直接写死了4种日志框架,其他日志框架,无法使用

private static final String[] classesToDiscover =
	new String[]{"org.apache.commons.logging.impl.Log4JLogger",
	"org.apache.commons.logging.impl.Jdk14Logger",
	"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
	"org.apache.commons.logging.impl.SimpleLog"};
 

3)SLF4J 一套抽象的日志API,其原理如下:

SLF4J需要使用绑定器,绑定到具体的日志实现框架,才能完成日志打印。


如果一个项目中,一个模块使用了log4j1 ,另外一个模块使用了slf4j+logback绑定器,那项目中就会有两种日志格式。

如何解决这个问题呢?使用桥接器,把log4j1的日志桥接到slf4j,这样打印格式都会统一到logback。

二、Spring日志

Spring4使用的是JCL,Spring5使用的是spring-jcl(因为JCL不维护了,因此spring把源码搞过来,自己封装成了一个模块)

spring-jcl 原理如下:

static {
		if (isPresent(LOG4J_SPI)) {
			if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
				// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
				// however, we still prefer Log4j over the plain SLF4J API since
				// the latter does not have location awareness support.
				logApi = LogApi.SLF4J_LAL;
			}
			else {
				// Use Log4j 2.x directly, including location awareness support
				logApi = LogApi.LOG4J;
			}
		}
		else if (isPresent(SLF4J_SPI)) {
			// Full SLF4J SPI including location awareness support
			logApi = LogApi.SLF4J_LAL;
		}
		else if (isPresent(SLF4J_API)) {
			// Minimal SLF4J API without location awareness support
			logApi = LogApi.SLF4J;
		}
		else {
			// java.util.logging as default
			logApi = LogApi.JUL;
		}
	}
	

从上面我们可以看出,Spring5.* 比较偏爱log4j2这个日志框架。

 

三、SpringBoot日志

SpringBoot集成各种开源组件, 是如何做到统一使用logback来打印日志呢?

SpringBoot 通过引入log4j2桥接器(log4j-to-slf4j),JUL桥接器(jul-to-slf4j),将其他的开源组件使用到的日志(log4j2,jul)桥接到sl4fj

 

 

四、Mybatis日志

通过源码可以发现,Mybatis日志处理方式,也是顺序去加载日志框架,加载到哪个就使用哪个,跟JCL框架比较像。

而且我们可以发现,Mybatis是比较偏爱slf4j,一旦找到了slf4j就不会再去找其他的日志框架了。

   
package org.apache.ibatis.logging;

import java.lang.reflect.Constructor;
import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl;
import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl;
import org.apache.ibatis.logging.log4j.Log4jImpl;
import org.apache.ibatis.logging.log4j2.Log4j2Impl;
import org.apache.ibatis.logging.nologging.NoLoggingImpl;
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.logging.stdout.StdOutImpl;

public final class LogFactory {
    public static final String MARKER = "MYBATIS";
    private static Constructor<? extends Log> logConstructor;

    private LogFactory() {
    }

    public static Log getLog(Class<?> clazz) {
        return getLog(clazz.getName());
    }

    public static Log getLog(String logger) {
        try {
            return (Log)logConstructor.newInstance(logger);
        } catch (Throwable var2) {
            throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + var2, var2);
        }
    }

    public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
        setImplementation(clazz);
    }

    public static synchronized void useSlf4jLogging() {
        setImplementation(Slf4jImpl.class);
    }

    public static synchronized void useCommonsLogging() {
        setImplementation(JakartaCommonsLoggingImpl.class);
    }

    public static synchronized void useLog4JLogging() {
        setImplementation(Log4jImpl.class);
    }

    public static synchronized void useLog4J2Logging() {
        setImplementation(Log4j2Impl.class);
    }

    public static synchronized void useJdkLogging() {
        setImplementation(Jdk14LoggingImpl.class);
    }

    public static synchronized void useStdOutLogging() {
        setImplementation(StdOutImpl.class);
    }

    public static synchronized void useNoLogging() {
        setImplementation(NoLoggingImpl.class);
    }

    private static void tryImplementation(Runnable runnable) {
        if (logConstructor == null) {
            try {
                runnable.run();
            } catch (Throwable var2) {
            }
        }

    }

    private static void setImplementation(Class<? extends Log> implClass) {
        try {
            Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
            Log log = (Log)candidate.newInstance(LogFactory.class.getName());
            if (log.isDebugEnabled()) {
                log.debug("Logging initialized using '" + implClass + "' adapter.");
            }

            logConstructor = candidate;
        } catch (Throwable var3) {
            throw new LogException("Error setting Log implementation.  Cause: " + var3, var3);
        }
    }

    static {
        tryImplementation(LogFactory::useSlf4jLogging);
        tryImplementation(LogFactory::useCommonsLogging);
        tryImplementation(LogFactory::useLog4J2Logging);
        tryImplementation(LogFactory::useLog4JLogging);
        tryImplementation(LogFactory::useJdkLogging);
        tryImplementation(LogFactory::useNoLogging);
    }
}
 

五、其他

1)各种日志框架的绑定器和桥接器

注:绑定器一般已经包含了SLF4J和具体的日志实现框架,因此引入绑定器之后,不需要再显式映入SLF4J和具体的日志框架。


2)log4j1 的桥接器和log4j1不能同时存在,因为log4j1的桥接器,对log4j1 的源码做了修改,同时引入,会报错。

文章来自个人专栏
java软件开发
1 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0