1.日志框架分类

日志框架分类

1.日志门面:操作日志的一套API

JCL appache推出的

slf4j

2.日志实现

JUL jdk自带的

logback springboot默认

log4j appache推出的老牌的日志框架,停止维护更新

log4j2 是log4j的升级版,提升性能

2.日志框架

1. JUL

JUL

1.使用

jdk自带的不须导包

package com.zzhua;

import org.junit.Test;

import java.util.logging.*;

public class JULTest {


    // 快速入门
    @Test
    public void testQuick()throws Exception{
        // 1.获取日志记录器对象
        Logger logger = Logger.getLogger("com.itheima.JULTest");
        // 2.日志记录输出
        logger.info("hello jul");

        // 通用方法进行日志记录
        logger.log(Level.INFO,"info msg");


        // 通过占位符 方式输出变量值
        String name = "itcast";
        Integer age = 13;
        logger.log(Level.INFO,"用户信息:{0},{1}",new Object[]{name,age});

    }

    // 日志级别
    @Test
    public void testLogLevel()throws Exception{
        // 1.获取日志记录器对象
        Logger logger = Logger.getLogger("com.itheima.JULTest");
        // 2.日志记录输出
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info"); // 默认日志输出级别
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");

    }


    // 自定义日志级别
    @Test
    public void testLogConfig()throws Exception{
        // 1.获取日志记录器对象
        Logger logger = Logger.getLogger("com.itheima.JULTest");


        // 关闭系统默认配置
        logger.setUseParentHandlers(false);

        // 自定义配置日志级别
        // 创建ConsolHhandler 控制台输出
        ConsoleHandler consoleHandler = new ConsoleHandler();

        // 创建简单格式转换对象
        SimpleFormatter simpleFormatter = new SimpleFormatter();

        // 进行关联
        consoleHandler.setFormatter(simpleFormatter);
        logger.addHandler(consoleHandler);


        // 配置日志具体级别
        logger.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);


        // 场景FileHandler  文件输出
        FileHandler fileHandler = new FileHandler("/logs/jul.log");

        // 进行关联
        fileHandler.setFormatter(simpleFormatter);
        logger.addHandler(fileHandler);

        // 2.日志记录输出
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info"); // 默认日志输出级别
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");

    }



    // Logger对象父子关系
    @Test
    public void testLogParent()throws Exception{

        Logger logger1 = Logger.getLogger("com.itheima");
        Logger logger2 = Logger.getLogger("com");

        // 测试
        System.out.println(logger1.getParent() == logger2);
        // 所有日志记录器的顶级父元素 LogManager$RootLogger,name ""
        System.out.println("logger2 Parent:"+logger2.getParent() 
                           + ",name:" + logger2.getParent().getName());

        // 关闭默认配置
        logger2.setUseParentHandlers(false);

        // 设置logger2日志级别
        // 自定义配置日志级别
        // 创建ConsolHhandler 控制台输出
        ConsoleHandler consoleHandler = new ConsoleHandler();

        // 创建简单格式转换对象
        SimpleFormatter simpleFormatter = new SimpleFormatter();

        // 进行关联
        consoleHandler.setFormatter(simpleFormatter);
        logger2.addHandler(consoleHandler);


        // 配置日志具体级别
        logger2.setLevel(Level.ALL);
        consoleHandler.setLevel(Level.ALL);

        logger1.severe("severe");
        logger1.warning("warning");
        logger1.info("info");
        logger1.config("config");
        logger1.fine("fine");
        logger1.finer("finer");
        logger1.finest("finest");
    }



    // 加载自定义配置文件
    @Test
    public void testLogProperties()throws Exception{

        // 读取配置文件,通过类加载器
        InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
        // 创建LogManager
        LogManager logManager = LogManager.getLogManager();
        // 通过LogManager加载配置文件
        logManager.readConfiguration(ins);

        // 创建日志记录器
        Logger logger = Logger.getLogger("com.itheima");

        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");


        Logger logger2 = Logger.getLogger("test");

        logger2.severe("severe test");
        logger2.warning("warning test");
        logger2.info("info test");
        logger2.config("config test");
        logger2.fine("fine test");
        logger2.finer("finer test");
        logger2.finest("finest test");

    }

    
}

logging.properties

# RootLogger 顶级父元素指定的默认处理器为:ConsoleHandler
handlers= java.util.logging.FileHandler

# RootLogger 顶级父元素默认的日志级别为:ALL
.level= ALL

# 自定义 Logger 使用
com.zzhua.handlers = java.util.logging.ConsoleHandler
com.zzhua.level = CONFIG

# 关闭默认配置
com.zzhua.useParentHanlders = false


# 向日志文件输出的 handler 对象
# 指定日志文件路径 /logs/java0.log
java.util.logging.FileHandler.pattern = ./java%u.log
# 指定日志文件内容大小
java.util.logging.FileHandler.limit = 50000
# 指定日志文件数量
java.util.logging.FileHandler.count = 1
# 指定 handler 对象日志消息格式对象
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 指定以追加方式添加日志内容
java.util.logging.FileHandler.append = true


# 向控制台输出的 handler 对象
# 指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level = ALL
# 指定 handler 对象的日志消息格式对象
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 指定 handler 对象的字符集
java.util.logging.ConsoleHandler.encoding = UTF-8

# 指定日志消息格式
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n

2. LOG4J

1. 引入log4j的依赖

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

2. 使用

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;

@Test
public void test01(){

    // 需要加上下面这句,否则什么也没有输出,还报log4j的警告信息,因为没有配置文件
    BasicConfigurator.configure(); // 代码配置
    
    Logger logger = Logger.getLogger(Log4j_Test.class);
    logger.trace("hello trace ~");
    logger.debug("hello debug ~");
    logger.info("hello info ~");
    logger.warn("hello warn ~");
    logger.error("hello error ~");
    logger.fatal("hello fatal ~");
}

3. log4j.properties

属性配置文件配置: LOG4J会默认读取类路径下的log4j.properties文件

#参考log4j包下的PropertyConfigurator这个类查看配置

#第一个参数是 日志级别,第二个和之后都是appender并且用‘,’隔开
log4j.rootLogger = trace,console                                       #使用下面定义的appender
log4j.appender.console = org.apache.log4j.ConsoleAppender              #定义appender
log4j.appender.console.layout = org.apache.log4j.SimpleLayout          #设置appender的layout
@Test
public void test01(){
    
    LogLog.setInternalDebugging(true); // 使用这句可以开启Log4j自身的日志信息,查看log4j自身的的执行日志信息

    Logger logger = Logger.getLogger(Log4j_Test.class);
    
    logger.trace("hello trace ~");
    logger.debug("hello debug ~");  // 默认日志级别
    logger.info("hello info ~");
    logger.warn("hello warn ~");
    logger.error("hello error ~");
    logger.fatal("hello fatal ~");
}

4. 配置文件详解

# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别=trace,使用的 apeender 为=console
log4j.rootLogger = trace,console

# 自定义 logger 对象设置
# 自定义logger的级别是info,使用的appender是console,console指向下面的console配置
# 这里的com.zzhua是代码调用getLogger(类名.class)时,这个字节码对象所在的包的包名.因此该方法返回的logger就会找到对应的
#                                              Logger,如果没有自定义,那么将会使用rootLogger的配置
log4j.logger.com.zzhua = info,console 
# 自定义另外一个logger的级别是error,使用的appender是空,那么默认继承rootLogger的console
log4j.logger.org.apache = error

# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式 layout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容 
# conversionPattern查看PatternLayout的setConversionPattern可以设置
log4j.appender.console.layout.conversionPattern = [%-10p]%r  %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n


# %m   输出代码中指定的日志信息
# %p   输出优先级,及 DEBUG、INFO 等
# %n   换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n")
# %r   输出自应用启动到输出该 log 信息耗费的毫秒数
# %c   输出打印语句所属的类的全名
# %t   输出产生该日志的线程全名
# %d   输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}
# %l   输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
# %F   输出日志消息产生时所在的文件名称
# %L   输出代码中的行号
# %%   输出一个 "%" 字符

# 日志文件输出的 appender 对象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定消息格式 layout
log4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.file.layout.conversionPattern = [%-10p]%r  %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.file.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8


# 按照文件大小拆分的 appender 对象
# 日志文件输出的 appender 对象
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
# 指定消息格式 layout
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.rollingFile.layout.conversionPattern = [%-10p]%r  %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.rollingFile.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.rollingFile.encoding = UTF-8
# 指定日志文件内容的大小 ,查看RollingFileAppender的setMaxFileSize方法设置
log4j.appender.rollingFile.maxFileSize = 1MB 
# 指定日志文件的数量
log4j.appender.rollingFile.maxBackupIndex = 10


# 按照时间规则拆分的 appender 对象
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
# 指定消息格式 layout
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.dailyFile.layout.conversionPattern = [%-10p]%r  %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.dailyFile.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.dailyFile.encoding = UTF-8
# 指定日期拆分规则
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss


#mysql
log4j.appender.logDB=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout=org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver=com.mysql.jdbc.Driver
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/test
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=root
log4j.appender.logDB.Sql=INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')

3. JCL

日志门面,appache出的,仅支持JUL和log4j

提供了两个抽象类: Log接口 和 LoggerFactory抽象类 ,别导错包了 -> org.apache.commons.logging

1. 导入commons-logging的包

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

2.使用

// 如果导入了log4j的依赖,那么下面代码用的就是log4j的实现

如果没有导入,那么用的就是org.apache.commons.logging.impl.Jdk14Logger

import org.apache.commons.logging.Log;             // 注意导包
import org.apache.commons.logging.LogFactory;      // 注意导包
import org.junit.Test;

public class JCL_test {
    @Test
    public void test01(){
        Log log = LogFactory.getLog(JCL_test.class);
        log.info("info - msg");

    }
}

3.原理

Log log = LogFactory.getLog(JCL_test.class);
#当使用LogFactory获取Log对象实例的时候,

return getFactory().getInstance(clazz);
它会先获取到LogFactory的实例,默认没有配置的情况下就是LogFactoryImpl,然后调用getInstance方法

instance = discoverLogImplementation(name); // 寻找实现
for(int i=0; i<classesToDiscover.length && result == null; ++i) { // 依次遍历classesToDiscover
    result = createLogFromClass(classesToDiscover[i], logCategory, true);
}

依次遍历的
private static final String[] classesToDiscover = {
    "org.apache.commons.logging.impl.Log4JLogger",   // 首先的就是Log4JLogger,注意包名
    "org.apache.commons.logging.impl.Jdk14Logger",
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
    "org.apache.commons.logging.impl.SimpleLog"
};
   
我们首先看看这个类
import java.io.Serializable;
import org.apache.commons.logging.Log;
import org.apache.log4j.Logger;    // 这个类导入了log4j包的类,
import org.apache.log4j.Priority;  // 因此如果没有导入log4j包的类的话,那么就会抛异常,所以会按顺序使用下面的类
import org.apache.log4j.Level;     // 而不会使用log4jlogger了
public class Log4JLogger implements Log, Serializable {...}

4. slf4j

1. 引入依赖

<!-- slf4j 日志门面,仅提供一套接口,须导入具体实现,才能使用 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.26</version>
</dependency>

<!-- slf4j 内置的简单实现,后期可依照下面的适配图所示的依赖替换掉-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.21</version>
</dependency>

2. 使用

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Slf4jTest {

    public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);

    // 快速入门
    @Test
    public void test01()throws Exception{
        // 日志输出
        LOGGER.error("error");
        LOGGER.warn("wring");
        LOGGER.info("info"); // 默认级别
        LOGGER.debug("debug");
        LOGGER.trace("trace");

        // 使用占位符输出日志信息
        String name = "itheima";
        Integer age = 14;
        LOGGER.info("用户:{},{}",name,age);

        // 将系统的异常信息输出
        try {
            int i = 1/0;
        } catch (Exception e) {
           // e.printStackTrace();
            LOGGER.error("出现异常:",e);

        }
    }
}

3.功能

1.slf4j绑定日志的实现

单独导入slf4j-api的依赖并不能直接使用,需要导入其它日志实现依赖的包,才能使用,具体适配可以查看下图。

如果绑定多个日志实现,那么会默认选择第一个。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rfgP6lhu-1595081403718)(assets/concrete-bindings.png)]

#绑定原理:
public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class); // 获取LOGGER
#来到slf4j的获取logger的静态方法
->Logger logger = getLogger(clazz.getName()); // 根据名字获取logger

->public static Logger getLogger(String name) { // 被调用
        ILoggerFactory iLoggerFactory = getILoggerFactory(); // 先获取ILoggerFactory
        return iLoggerFactory.getLogger(name);
  }


getIloggerFactory调用performInitilization方法
->performInitialization(){
	bind();
  }
  
->private static final void bind() { // 开始绑定
        String msg;
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            if (!isAndroid()) {
                // 在类路径下寻找org/slf4j/impl/StaticLoggerBinder.class这个文件,并返回路径,可能有多个
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                // 判断是否返回了多个,打印相关信息
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
			// 获取到StaticLoggerBinder实例
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = 3; // 为3
            
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstituteLoggers();
            replayEvents();
            SUBST_FACTORY.clear();
        } catch (NoClassDefFoundError var2) {
            msg = var2.getMessage();
            if (!messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                failedBinding(var2);
                throw var2;
            }

            INITIALIZATION_STATE = 4;
            Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
            Util.report("Defaulting to no-operation (NOP) logger implementation");
            Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
        } catch (NoSuchMethodError var3) {
            msg = var3.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = 2;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }

            throw var3;
        } catch (Exception var4) {
            failedBinding(var4);
            throw new IllegalStateException("Unexpected initialization failure", var4);
        }

    }  

->跳回到getILoggerFactory()这个方法,INITIALIZATION_STATE状态是3
public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == 0) {
            Class var0 = LoggerFactory.class;
            synchronized(LoggerFactory.class) {
                if (INITIALIZATION_STATE == 0) {
                    INITIALIZATION_STATE = 1;
                    performInitialization();
                }
            }
        }

        switch(INITIALIZATION_STATE) {
        case 1:
            return SUBST_FACTORY;
        case 2:
            throw new IllegalStateException("org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
        case 3:
                 // 调用绑定成功的类的 单例对象的getLoggerFactory()方法,
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case 4:
            return NOP_FALLBACK_FACTORY;
        default:
            throw new IllegalStateException("Unreachable code");
        }
    }  	

// 如果导入多个依赖与slf4j绑定,也就是说有多个StaticLoggerBinder[不同依赖,但同包同类名],
//                                     那么按顺序只会有一个能够绑定成功
public class StaticLoggerBinder implements LoggerFactoryBinder {
    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); 
    public static String REQUESTED_API_VERSION = "1.6.99";
    private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();
    //                             如果这个类绑定成功,那么用的就是Log4jLoggerFactory了
    private final ILoggerFactory loggerFactory = new Log4jLoggerFactory();

    public static final StaticLoggerBinder getSingleton() { // 获取到当前类的单例对象
        return SINGLETON;
    }
    
    
    private StaticLoggerBinder() {
        try {
            Level var1 = Level.TRACE;
        } catch (NoSuchFieldError var2) {
            Util.report("This version of SLF4J requires log4j version 1.2.12 or later. +
                        "See also http://www.slf4j.org/codes.html#log4j_version");
        }

    }

    public ILoggerFactory getLoggerFactory() {
        return this.loggerFactory;
    }

    public String getLoggerFactoryClassStr() {
        return loggerFactoryClassStr;
    }
}

// 再来看看slf4j-jdk14的依赖中的StaticLoggerBinder类
public class StaticLoggerBinder implements LoggerFactoryBinder {
    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
    public static String REQUESTED_API_VERSION = "1.6.99";
    private static final String loggerFactoryClassStr = JDK14LoggerFactory.class.getName();
                                         // 这里用的就是JDK14LoggerFactory了
    private final ILoggerFactory loggerFactory = new JDK14LoggerFactory();

    public static final StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    private StaticLoggerBinder() {
    }

    public ILoggerFactory getLoggerFactory() {
        return this.loggerFactory;
    }

    public String getLoggerFactoryClassStr() {
        return loggerFactoryClassStr;
    }
}

2.桥接旧的日志框架

项目原本用的是log4j作为日志框架,但须改成logback。

这个时候,就可以引进slf4j-api和logback-classic、logback-core的依赖。

并且删除掉log4j的依赖,删除的话,原来的代码就会报错,找不到类。通过再引入桥接的log4j-over-slf4j依赖,替换掉原来log4j包中的类(同包同类名),而替换的类会转而调用slf4j-api门面的接口,这样原来的代码就不报错并且不用改动,就完成了修改。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pe0bWx9y-1595081403720)(assets/legacy.png)]

3.注意事项

桥接器和适配器不能同时使用。

适配器是为了让slf4j能够顺利的使用老牌的日志框架,比如log4j。而桥接器是为了修改掉老牌的日志框架,使原有调用老牌的日志框架的代码不用改动,但是调用的是桥接器中代码,转而调用slf4j门面,从而不使用老牌的日志框架。所以如果同时使用会出现死循环,造成栈内存溢出。所以两者不能共存。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐