日志与spring5新日志体系笔记(包换mybatis日志)

主流的log技术

  1. log4j
<dependency>
	 <groupId>log4j</groupId>
	 <artifactId>log4j</artifactId>
	 <version>1.2.12</version>
</dependency>

可以不需要依赖第三方的技术
直接记录日志

简单配置文件如下

log4j.rootLogger = info , stdout 

### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %p [ %t ] %C. %M(%L) | %m%n

测试

public static void main(String[] args) {
    Logger log4j = Logger.getLogger("log4j");
    log4j.info("log4j");
}

输出如下

2019-09-10 12:11:49 INFO [ main ] com.zdd.log.Log4jDemo. main(8) | log4j
  1. jcl

jakartaCommonsLoggingImpl

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.1</version>
</dependency>

测试

public static void main(String[] args) {
    Log jcl = LogFactory.getLog("jcl");
    jcl.info("jcl");
}

如果没有引入log4j依赖的话,打印的是jul的日志

九月 10, 2019 12:15:36 下午 com.zdd.log.jclDemo main
信息: jcl

如果引入log4j的依赖,打印的就是log4j的日志

2019-09-10 12:17:07 INFO [ main ] com.zdd.log.jclDemo. main(9) | jcl

jcl他不直接记录日志,他是通过第三方记录日志(jul)

如果使用jcl来记录日志,在没有log4j的依赖情况下,是用jul
如果有了log4j则使用log4j

jcl=Jakarta commons-logging ,是apache公司开发的一个抽象日志通用框架,本身不实现日志记录,但是提供了记录日志的抽象方法即接口(info,debug,error…),底层通过一个数组存放具体的日志框架的类名,然后循环数组依次去匹配这些类名是否在app中被依赖了,如果找到被依赖的则直接使用,所以他有先后顺序。

图片

上图为jcl中存放日志技术类名的数组,默认有四个,后面两个可以忽略。

图片

上图81行就是通过一个类名去load一个class,如果load成功则直接new出来并且返回使用。
如果没有load到class这循环第二个,直到找到为止。

图片

可以看到这里的循环条件必须满足result不为空,
也就是如果没有找到具体的日志依赖则继续循环,如果找到则条件不成立,不进行循环了。
总结:顺序log4j>jul

  1. jul

java自带的一个日志记录的技术,直接使用

测试

public static void main(String[] args) {
    Logger jul = Logger.getLogger("jul");
    jul.info("jul");
}

结果

九月 10, 2019 12:20:07 下午 com.zdd.log.julDemo main
信息: jul
  1. log4j2
  2. slf4j

slf4j他也不记录日志,通过绑定器绑定一个具体的日志记录来完成日志记录

slf4j也是类似于jcl的框架,本身不提供日志服务,而是通过第三方日志框架来提供日志服务的。slf4j通过绑定器绑定其他日志,是通过导入依赖的形式来绑定的。

使用slf4j首先要引入slf4j-api依赖

 <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.7.26</version>
 </dependency>

接着就需要根据自己需要的日志来绑定对应的日志,需要注意的是这只是绑定器,有可能并没有内置真正的日志服务,所以可能需要先引入需要绑定的日志依赖

    <!--  slf4j绑定log4j -->
<!--    <dependency>-->
<!--      <groupId>org.slf4j</groupId>-->
<!--      <artifactId>slf4j-log4j12</artifactId>-->
<!--      <version>1.7.26</version>-->
<!--    </dependency>-->

    <!--  slf4j绑定jul -->
<!--    <dependency>-->
<!--      <groupId>org.slf4j</groupId>-->
<!--      <artifactId>slf4j-jdk14</artifactId>-->
<!--      <version>1.7.26</version>-->
<!--    </dependency>-->

    <!--  slf4j绑定jcl -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jcl</artifactId>
      <version>1.7.26</version>
    </dependency>

这样就会直接使用绑定的日志服务

public static void main(String[] args) {
    Logger slf4j = LoggerFactory.getLogger("slf4j");
    slf4j.info("slf4j");
}

slf4j的绑定关系图
在这里插入图片描述

slf4j不止是有绑定器还有桥接器
如果说绑定器是使用slf4j绑定其他的日志
那么桥接就是使其他的日志使用slf4j的日志服务
绑定是对于自身而言的,桥接则是针对于其他日志的

这样就可以通过桥接和绑定的方式来统一spring框架和项目自身的日志服务。比如spring使用的log4j,而自己的项目则使用的是jul,这个这时候就有两种解决方案

  • 改变自己项目的日志服务
  • 改变spring的日志服务

在slf4j之前只能选择第一个方案,但是因为slf4j桥接功能,所以我们能过改变spring的日志了。
在这里插入图片描述
我们只需要引入相应的依赖就可以使对应的日志服务最后使用slf4j的日志

各种日志桥接slf4j的依赖如下,这样如果使用被被桥接的日志其实是在使用slf4j

   <!--  log4j桥接slf4j  -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>log4j-over-slf4j</artifactId>
      <version>1.7.26</version>
    </dependency>

    <!-- jcl桥接slf4j -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>1.7.26</version>
    </dependency>

    <!-- jul桥接slf4j -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jul-to-slf4j</artifactId>
      <version>1.7.26</version>
    </dependency>

但是对于复杂的项目架构来说,这样很容易形成循环绑定和桥接
在这里插入图片描述

  1. logback
  2. simple-log

各种日志技术的关系和作用

图片

spring日志技术分析

spring5.*日志技术实现

spring5使用的spring的jcl(spring改了jcl的代码)来记录日志的,但是jcl不能直接记录日志,采用循环优先的原则。

spring5使用的也是jcl但是使用的spring改造的,并不是原生的,而且默认的是通过jcl绑定jul。

在初始化logFactory时会通过类加载器加载一些指定的日志,如果能过加载到就会将标志变量设置为指定的日志标志,随后就会根据标志变量来创建日志。

还需要注意的是spring5默认使用的是log4j2,这也是为什么加了log4j依赖依然没有使用。

如果真的要在spring5中使用log4j日志,可以引入slf4j并且绑定log4j。

spring5中拿到log的方法,虽然是通过jcl拿到的,当这个jcl已经不是原生的jcl了
在这里插入图片描述
spring5改造后的jcl拿到日志对象的方法,可以看到是通过logApi这个静态变量来选择日志对象的

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

public static Log getLog(String name) {
    switch(logApi) {
    case LOG4J:
        return LogFactory.Log4jDelegate.createLog(name);
    case SLF4J_LAL:
        return LogFactory.Slf4jDelegate.createLocationAwareLog(name);
    case SLF4J:
        return LogFactory.Slf4jDelegate.createLog(name);
    default:
        return LogFactory.JavaUtilDelegate.createLog(name);
    }
}

在初始化时就会给LogApi这个变量赋值

会通过类加载器加载一些指定的日志,如果能过加载到就会将LogApi设置为指定的日志标志,随后就会根据LogApi来创建日志,注意第一个是log4j2,默认是jul

static {
    logApi = LogFactory.LogApi.JUL;
    ClassLoader cl = LogFactory.class.getClassLoader();

    try {
        cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");
        logApi = LogFactory.LogApi.LOG4J;
    } catch (ClassNotFoundException var6) {
        try {
            cl.loadClass("org.slf4j.spi.LocationAwareLogger");
            logApi = LogFactory.LogApi.SLF4J_LAL;
        } catch (ClassNotFoundException var5) {
            try {
                cl.loadClass("org.slf4j.Logger");
                logApi = LogFactory.LogApi.SLF4J;
            } catch (ClassNotFoundException var4) {
            }
        }
    }
}

spring4.*日志技术实现

spring4当中依赖的是原生的jcl,所以就不再重复分析了,具体的可以参考上面对jcl源码的分析

mybatis的日志技术实现

mybatis日志的官网日志描述

初始化

org.apache.ibatis.logging.LogFactory

tryImplementation(new Runnable() {
  @Override
  public void run() {
    useSlf4jLogging();
  }
});

关键代码if (logConstructor == null),没有找到实现则继续找

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

图片图片

具体实现类

mybatis提供很多日志的实现类,用来记录日志,取决于初始化的时候load到的class
图片

上图红色箭头可以看到JakartaCommonsLoggingImpl
中引用了jcl 的类,如果在初始化的时候load到类为JakartaCommonsLoggingImpl
那么则使用jcl 去实现日志记录,但是也是顺序的,顺序参考源码

自己模拟实现mybatis的日志实现

mybatis的官网关于日志的介绍
定义org.apache.ibatis.session.Configuration
参考org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl
分析为什么jcl不记录日志,修改代码

几种mybatis开启日志的方法

如果是使用log4j,那么需要先在log4j配置文件中配置需要打印日志的类或包

官网的描述如下
在这里插入图片描述

这个是日志引用的顺序,所以可以看出第一个是slf4j,所以mybatis默认的是slf4j,但是如果没有依赖slf4j,而且还和spring整合了,因为spring内置了jcl,所以会使用jcl

这个是mybatis初始化会执行的代码
在这里插入图片描述

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

    private LogFactory() {
    }

    public static Log getLog(Class<?> aClass) {
        return getLog(aClass.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());
            log.debug("Logging initialized using '" + implClass + "' adapter.");
            logConstructor = candidate;
        } catch (Throwable var3) {
            throw new LogException("Error setting Log implementation.  Cause: " + var3, var3);
        }
    }

    static {
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useSlf4jLogging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useCommonsLogging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useLog4J2Logging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useLog4JLogging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useJdkLogging();
            }
        });
        tryImplementation(new Runnable() {
            public void run() {
                LogFactory.useNoLogging();
            }
        });
    }
}

默认方式

如上面所示其实只需要引入mybatis中日志工厂的静态代码块里的日志的依赖就可以使用日志了,当然有些时候会需要直接指定使用的日志,那么就需要使用下面的方法。

第一种方法

可以在配置对象中指定使用哪一个,这里其实和xml的配置是一样的
在这里插入图片描述
第二种方法

还有种方法是在mybatis初始化前调用对应的配置方法,这种方法在官网已经说的很清楚了,下面是配置使用log4j日志
在这里插入图片描述
官网描述如下
在这里插入图片描述
其实不论是上面的方式还是下面的方式都会调用到下面的方法,它也是LogFactory中的方法,会将设置的日志对象的构造器创建出来赋值给logConstructor这个变量

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());
        log.debug("Logging initialized using '" + implClass + "' adapter.");
        logConstructor = candidate;
    } catch (Throwable var3) {
        throw new LogException("Error setting Log implementation.  Cause: " + var3, var3);
    }
}

这样在初始化时在静态代码块中就不会被重新设置了
在这里插入图片描述

架构系统如何考虑日志

old:jcl+log4j
new:slf4j+jul


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值