精华内容
下载资源
问答
  • java操作日志框架
    千次阅读
    2021-11-10 10:22:26

    1. 需求概述

    日常开发中,不管是单体还是分布式微服务,服务监控是必不可少的环节,没有监控的应用是很可怕的,你不知道应用服务处于什么状态,有什么风险,什么时候会宕掉。服务监控除了一些大型的监控工具,比如ELK,Zabbx,Prometheus,Arthas等工具外,本文要讲的是轻量级的日志监控工具,轻量到可以不引入Spring,就是一个简单的请求前后出入参以及异常的打印,以及自定义一些过滤器拦截器,traceID等功能,而不必每个服务的每个方法都手动处理。

    实现本身也很简单,只需要通过切面+注解实现,github上发现auto-log无论是从架构设计,到实现,规范,注释,包括使用方式都值得推荐,感谢大佬,这里只做引荐,简单梳理使用验证。

    2. auto-log简介

    2.1 auto-log定义

    auto-log 是一款为 java 设计的自动日志监控框架。

    2.2 auto-log目的

    经常会写一些工具,有时候手动加一些日志很麻烦,引入 spring 又过于大材小用。
    所以希望从从简到繁实现一个工具,便于平时使用。

    2.3 auto-log特性

    • 基于注解+字节码,配置灵活
    • 自动适配常见的日志框架
    • 支持编程式的调用
    • 支持注解式,完美整合 spring
    • 支持整合 spring-boot
    • 支持慢日志阈值指定,耗时,入参,出参,异常信息等常见属性指定
    • 支持 traceId 特性
    • 支持类级别定义注解
    • 支持自定义拦截器和过滤器

    2.4 注解说明

    @AutoLog
    核心注解 @AutoLog 的属性说明如下:
    在这里插入图片描述@TraceId
    @TraceId 放在需要设置 traceId 的方法上,比如 Controller 层,mq 的消费者,rpc 请求的接受者等。

    属性类型默认值说明
    idClass默认为 uuidtraceId 的实现策略
    putIfAbsentbooleanfalse是否在当前线程没有值的时候才设置值
    enablebooleantrue是否启用
    interceptorClass[]默认实现拦截器实现,支持指定多个和自定义

    2.5 自定义拦截器

    内置拦截器
    AutoLogInterceptor 默认实现。

    自定义拦截器
    1.直接继承自 AbstractAutoLogInterceptor 类,并且实现对应的方法即可。
    2.使用的时候指定自定义拦截器。
    @AutoLog(interceptor = MyAutoLogInterceptor.class)

    2.6 自定义过滤器

    内置过滤器
    WebParamFilter 主要用于过滤 HttpRequest HttpServlet 等无法直接 JSON 序列化的对象。

    自定义过滤器
    1.直接继承 AbstractParamFilter 类实现对应的方法即可。
    2.使用的时候指定自定义过滤器。
    @AutoLog(paramFilter = MyParamFilter.class)

    2.7 注意事项

    1.auto-log默认是开启的@EnableAutoLog,无需再次开启,多次开启也不影响使用。
    2.集成springboot只需要引入stater即可,无需任何配置。

    2.8 开源地址

    Github: https://github.com/houbb/auto-log
    Gitee: https://gitee.com/houbinbin/auto-log

    3.实现验证

    3.1 引入依赖

    <!--autolog-->
     <dependency>
     	<groupId>com.github.houbb</groupId>
    	<artifactId>auto-log-springboot-starter</artifactId>
    	<version>0.0.15</version>
     </dependency>
    

    只需要引入 jar 即可,其他的什么都不用配置。
    使用方式和 spring 一致。

    3.2 代码实现

    LogController

    package com.zrj.autolog.controller;
    
    import com.github.houbb.auto.log.annotation.AutoLog;
    import com.github.houbb.auto.log.annotation.TraceId;
    import com.zrj.autolog.entity.Log;
    import com.zrj.autolog.entity.Response;
    import com.zrj.autolog.filter.MyLogParamFilter;
    import com.zrj.autolog.interceptor.MyLogInterceptor;
    import com.zrj.autolog.service.LogService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    
    /**
     * 用户控制类
     *
     * @author zrj
     * @since 2021-06-21 16:44:24
     */
    @Slf4j
    @RestController
    @RequestMapping("/log")
    @Api(tags = "日志管理", description = "日志管理")
    public class LogController {
    
        @Resource
        private LogService logService;
    
        /**
         * 测试接口
         * 自定义过滤器,拦截器
         */
        @TraceId
        @GetMapping("/test")
        @ApiOperation(value = "测试接口")
        @AutoLog(description = "测试接口", costTime = true, interceptor = MyLogInterceptor.class, paramFilter = MyLogParamFilter.class)
        public Response<String> test() {
            return Response.success("日志管理测试成功", null);
        }
    
        /**
         * 自动日志管理
         */
        @TraceId
        @AutoLog(description = "自动日志管理", costTime = true)
        @PostMapping("/autolog")
        @ApiOperation(value = "自动日志管理测试")
        public Response<Log> autolog(@RequestBody Log log) {
            Log build = logService.getLog();
            if (build != null) {
                return Response.success("查询成功", build);
            }
            return Response.fail("查询失败");
        }
    
    }
    
    

    MyLogParamFilter

    package com.zrj.autolog.filter;
    
    import com.github.houbb.auto.log.core.support.filter.param.AbstractParamFilter;
    
    /**
     * 自定义入参过滤器(paramFilter)
     *
     * @author zrj
     * @since 2021/11/10
     **/
    public class MyLogParamFilter extends AbstractParamFilter {
        /**
         * 执行参数过滤
         *
         * @param params 入参
         * @return 结果
         * @since 0.0.12
         */
        @Override
        protected Object[] doFilter(Object[] params) {
            Object[] newParams = new Object[1];
            newParams[0] = "自定义过滤器设置我我想要的值";
            return newParams;
        }
    }
    
    

    MyLogInterceptor

    package com.zrj.autolog.interceptor;
    
    import com.github.houbb.auto.log.annotation.AutoLog;
    import com.github.houbb.auto.log.api.IAutoLogInterceptorContext;
    import com.github.houbb.auto.log.core.support.interceptor.autolog.AbstractAutoLogInterceptor;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.Arrays;
    
    /**
     * 自定义日志拦截器(interceptor)
     *
     * @author zrj
     * @since 2021/11/10
     **/
    @Slf4j
    public class MyLogInterceptor extends AbstractAutoLogInterceptor {
        /**
         * 方法执行以前
         * @param autoLog 注解
         * @param context 上下文
         * @since 0.0.10
         */
        @Override
        protected void doBefore(AutoLog autoLog, IAutoLogInterceptorContext context) {
            log.info("我的自定义入参:" + Arrays.toString(context.filterParams()));
        }
    
        /**
         * 方法执行以后
         * @param autoLog 注解
         * @param result  方法执行结果
         * @param context 上下文
         * @since 0.0.10
         */
        @Override
        protected void doAfter(AutoLog autoLog, Object result, IAutoLogInterceptorContext context) {
            log.info("我的自定义出参:" + result);
        }
    
        /**
         * 异常处理
         * @param autoLog   注解
         * @param exception 异常信息
         * @param context   上下文
         * @since 0.0.10
         */
        @Override
        protected void doException(AutoLog autoLog, Exception exception, IAutoLogInterceptorContext context) {
            log.info("我的自定义异常:");
            exception.printStackTrace();
        }
    }
    

    3.3 处理结果

    MyLogInterceptor       : 我的自定义入参:[自定义过滤器设置我我想要的值]
    MyLogInterceptor       : 我的自定义出参:Response(message=日志管理测试成功, data=null, code=200)
    AutoLogInterceptor     : [f4794449d6454e13807fdd5a5fffc2dd] <自动日志管理>入参: [{"des":"日志描述","level":"日志级别","name":"日志名称","id":"日志ID"}].
    e.impl.LogServiceImpl  : 这里保存日志信息
    AutoLogInterceptor     : [f4794449d6454e13807fdd5a5fffc2dd] <自动日志管理>出参:{"code":"200","data":{"des":"测试啦","id":"20211109001","level":"info","name":"自动日志"},"message":"查询成功"}.
    AutoLogInterceptor     : [f4794449d6454e13807fdd5a5fffc2dd] <自动日志管理>耗时:70ms.
    
    更多相关内容
  • Java日志框架

    2021-02-24 17:16:32
    眼下java应用日志收集都是採用日志框架(slf4j、apachecommonslogging)+日志系统(log4j、log4j2、LogBack、JUL等)的方式。日志框架:提供日志调用的接口,实际的日志输出托付给日志系统实现。JCL...
  • Java日志框架整合详情

    2022-06-02 09:20:01
    Java日志框架整合详情
  • Java日志框架详解.ppt

    2022-06-08 11:42:55
    Java日志框架详解.ppt
  • Java 日志框架详解

    千次阅读 2021-12-23 21:29:22
    Java 日志框架详解,JUL,jcl、log4j、slf4j、logback、log4j2

    1. JUL学习

    JUL全称Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框 架使用方便,学习简单,能够在小型应用中灵活使用。

    1.1 架构介绍

    image-20211222111107875

    • Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger通常时应用程序访问日志系统的入口程序。

    • Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。

    • Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。

    • Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,我可以将Level和Loggers,Appenders做关联以便于我们过滤消息。

    • Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。

    总结

    用户使用Logger来进行日志记录,Logger持有若干个Handler,日志的输出操作是由Handler完成的。在Handler在输出日志前,会经过Filter的过滤,判断哪些日志级别过滤放行哪些拦截,Handler会将日志内容输出到指定位置(日志文件、控制台等)。Handler在输出日志时会使用Layout,将输出内容进行排版。

    @Test
    public void test01(){
        Logger logger = Logger.getLogger("cn.quguai.JULTest");
        logger.info("Hello JUL");
    
        logger.log(Level.INFO, "info msg");
    
        logger.log(Level.INFO, "message:{0}:{1}", new Object[]{"quguai", 1});
    }
    

    1.2 日志级别

    java.util.logging.Level中定义了日志的级别:

    • SEVERE(最高值)
    • WARNING
    • INFO (默认级别)
    • CONFIG
    • FINE
    • FINER
    • FINEST(最低值)

    还有两个特殊的级别:

    • OFF,可用来关闭日志记录。
    • ALL,启用所有消息的日志记录。
    @Test
    public void testLevel(){
        Logger logger = Logger.getLogger("cn.quguai.JULTest");
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
    

    1.3 自定义日志级别配置

    @Test
    public void testSetLevel() throws IOException {
        Logger logger = Logger.getLogger("cn.quguai.JULTest");
    
        // 关闭原有处理器
        logger.setUseParentHandlers(false);
    
        // 创建ConsoleHandle 和 SimpleFormatter 格式化器
        ConsoleHandler consoleHandler = new ConsoleHandler();
        SimpleFormatter simpleFormatter = new SimpleFormatter();
    
        // 进行关联
        logger.addHandler(consoleHandler);
        consoleHandler.setFormatter(simpleFormatter); // 默认就是SimpleFormatter
    
        // 创建FileHandle
        FileHandler fileHandler = new FileHandler("./jul.log");
    
        // 进行关联
        logger.addHandler(fileHandler);
        fileHandler.setFormatter(simpleFormatter);
    
        // 设置默认级别
        consoleHandler.setLevel(Level.ALL);
        logger.setLevel(Level.ALL);
        // fileHandler.setLevel(Level.ALL); 默认沿用logger的配置
    
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
    }
    

    1.4 Logger 父子关系

    @Test
    public void testLogParent(){
        Logger logger1 = Logger.getLogger("cn.quguai");
        Logger logger2 = Logger.getLogger("cn");
        // 默认顶级Logger对象是 java.util.logging.LogManager$RootLogger
        System.out.println(logger2.getParent());
    
        // 修改logger2的配置
        logger2.setUseParentHandlers(false);
        ConsoleHandler consoleHandler = new ConsoleHandler();
        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");
    }
    

    1.5 Logger 默认配置文件

    文件路径:D:\jdk-11.0.10\conf\logging.properties

    java.util.logging.Logger#getLogger() ->
    java.util.logging.Logger#demandLogger ->
    java.util.logging.LogManager#getLogManager ->
    java.util.logging.LogManager#ensureLogManagerInitialized->
    java.util.logging.LogManager#readPrimordialConfiguration ->
    java.util.logging.LogManager#readConfiguration() ->
    java.util.logging.LogManager#getConfigurationFileName

    ############################################################
    #  	Global properties
    ############################################################
    
    # "handlers" specifies a comma separated list of log Handler 
    # classes.  These handlers will be installed during VM startup.
    # Note that these classes must be on the system classpath.
    # By default we only configure a ConsoleHandler, which will only
    # show messages at the INFO and above levels.
    handlers= java.util.logging.ConsoleHandler
    
    # To also add the FileHandler, use the following line instead.
    #handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
    
    # Default global logging level.
    # This specifies which kinds of events are logged across
    # all loggers.  For any given facility this global level
    # can be overriden by a facility specific level
    # Note that the ConsoleHandler also has a separate level
    # setting to limit messages printed to the console.
    .level= INFO
    
    ############################################################
    # Handler specific properties.
    # Describes specific configuration info for Handlers.
    ############################################################
    
    # default file output is in user's home directory.
    java.util.logging.FileHandler.pattern = %h/java%u.log   # 当前用户目录下 java01.log
    java.util.logging.FileHandler.limit = 50000
    java.util.logging.FileHandler.count = 1
    # Default number of locks FileHandler can obtain synchronously.
    # This specifies maximum number of attempts to obtain lock file by FileHandler
    # implemented by incrementing the unique field %u as per FileHandler API documentation.
    java.util.logging.FileHandler.maxLocks = 100
    java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
    
    # Limit the message that are printed on the console to INFO and above.
    java.util.logging.ConsoleHandler.level = INFO
    java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
    
    # Example to customize the SimpleFormatter output format 
    # to print one-line log message like this:
    #     <level>: <log message> [<date/time>]
    #
    # java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n
    
    ############################################################
    # Facility specific properties.
    # Provides extra control for each logger.
    ############################################################
    
    # For example, set the com.xyz.foo logger to only log SEVERE
    # messages:
    com.xyz.foo.level = SEVERE
    

    1.6 自定义配置文件

    # 顶级父元素的默认的处理器
    handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler   // 追加文件管理器
    
    # 顶级父元素RootLogger的默认输出级别
    .level= ALL  // 修改默认级别
    # 文件配置路径
    java.util.logging.FileHandler.pattern = ./java%u.log
    # 指定日志文件内容大小
    java.util.logging.FileHandler.limit = 50000
    # 指定日志文件数量
    java.util.logging.FileHandler.count = 1
    
    java.util.logging.FileHandler.maxLocks = 100
    java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter  #XML格式化器
    java.util.logging.FileHandler.append = true
    
    java.util.logging.ConsoleHandler.level = ALL  # 修改默认级别
    java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
    java.util.logging.ConsoleHandler.encoding = UTF-8
    
    @Test
    public void testProperties() throws IOException {
        InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
        
        // LogManager 是一个单例对象
        LogManager logManager = LogManager.getLogManager();
        logManager.readConfiguration(ins);
    
        Logger logger = Logger.getLogger("cn.quguai.JULTest");
        logger.severe("severe");
        logger.warning("warning");
        logger.info("info");
        logger.config("config");
        logger.fine("fine");
        logger.finer("finer");
        logger.finest("finest");
        ins.close();
    }
    

    1.7 JUL配置文件详情

    1.7.1 ConsoleHandler配置形式

    java.util.logging.ConsoleHandler.encoding = UTF-8

    image-20211222155356134

    1.7.2 配置文件格式化方式

    java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n

    SurrogateLogger.getSimpleFormat 获取配置文件中的key值对应的格式化方式

    image-20211222160043652

    1.7.3 追加文件

    java.util.logging.FileHandler.append = true

    1.7.4 自定义Logger

    # 自定义Logger
    cn.quguai.handlers = java.util.logging.FileHandler
    cn.quguai.level = CONFIG
    cn.quguai.useParentHandlers=false
    

    1.8 日志原理解析

    1. 初始化LogManager
      1. LogManager加载logging.properties配置
      2. 添加Logger到LogManager
    2. 从单例LogManager获取Logger
    3. 设置级别Level,并指定日志记录LogRecord
    4. Filter提供了日志级别之外更细粒度的控制
    5. Handler是用来处理日志输出位置
    6. Formatter是用来格式化LogRecord的

    image-20211222164607347

    2. Log4j

    官网地址 | 官方文档

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

    2.1 日志级别

    级别含义
    fatal指出每个严重的错误事件将会导致应用程序的退出。
    error虽然发生错误事件,但仍然不影响系统的继续运行。
    warn表明会出现潜在的错误情形。
    info一般和在粗粒度级别上,强调应用程序的运行全程。
    debug一般用于细粒度级别上,对调试应用程序非常有帮助。默认级别
    trace是程序追踪,可以用于输出程序运行中的变量,显示执行的流程。

    推荐:实际开发中只会使用到最中间的四个步骤。

    @Test
    public void testQuick(){
        Logger logger = Logger.getLogger(Log4jTest.class);
        BasicConfigurator.configure();
        // logger.setLevel(Level.INFO);
        logger.fatal("fatal");
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");  // 默认级别
        logger.trace("trace");
    }
    

    2.2 Log4j 组件

    Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和 Layout(日志格式化器)组成。

    其中 Loggers 控制日志的输出级别与日志是否输出;Appenders 指定日志的输出方式(输出到控制台、文件 等);Layout 控制日志信息的输出格式。

    2.2.1 Loggers

    日志记录器,负责收集处理日志记录,实例的命名就是类“XX”的full quailied name(类的全限定名), Logger的名字大小写敏感,其命名有继承机制:例如:name为org.apache.commons的logger会继承 name为org.apache的logger。

    Log4J中有一个特殊的logger叫做“root”,他是所有logger的根,也就意味着其他所有的logger都会直接 或者间接地继承自root。root logger可以用Logger.getRootLogger()方法获取。

    但是,自log4j 1.2版以来, Logger 类已经取代了 Category 类。对于熟悉早期版本的log4j的人来说, Logger 类可以被视为 Category 类的别名。

    img

    2.2.2 Appenders

    Appender 用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。Log4j 常用的输出目的地 有以下几种:

    输出端类型作用
    ConsoleAppender将日志输出到控制台
    FileAppender将日志输出到文件中
    DailyRollingFileAppender将日志输出到一个日志文件,并且每天输出到一个新的文件
    RollingFileAppender将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大 小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
    JDBCAppender把日志信息保存到数据库中

    2.2.3 Layouts

    布局器 Layouts用于控制日志输出内容的格式,让我们可以使用各种需要的格式输出日志。Log4j常用 的Layouts:

    格式化器类型作用
    HTMLLayout格式化日志输出为HTML表格形式
    SimpleLayout简单的日志输出格式化,打印的日志格式为(info - message)
    PatternLayout最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式, 就是用默认的转换格式

    2.3 配置文件详情

    配置文件的后缀名称全部来源于对应的set方法注入,例如conversionPattern 使用SetConversionPattern方法完成

    log4j.rootLogger = trace,console
    
    # 指定appender
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    # 指定layout
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    # 指定格式化信息
    log4j.appender.console.layout.conversionPattern = %r [%5t] %p %l - %m%n
    

    2.4 配置文件源码

    2.4.1 支持的配置文件

    public class LogManager {
        /** @deprecated */
        public static final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
        static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
        /** @deprecated */
        public static final String DEFAULT_CONFIGURATION_KEY = "log4j.configuration";
        /** @deprecated */
        public static final String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass";
        /** @deprecated */
        public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride";
    }
    

    org.apache.log4j.LogManager ->
    org.apache.log4j.helpers.OptionConverter#selectAndConfigure ->
    org.apache.log4j.PropertyConfigurator ->

    public class PropertyConfigurator implements Configurator {
        protected Hashtable registry = new Hashtable(11);
        protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
        static final String CATEGORY_PREFIX = "log4j.category.";
        static final String LOGGER_PREFIX = "log4j.logger.";
        static final String FACTORY_PREFIX = "log4j.factory";
        static final String ADDITIVITY_PREFIX = "log4j.additivity.";
        static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
        static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
        static final String APPENDER_PREFIX = "log4j.appender.";
        static final String RENDERER_PREFIX = "log4j.renderer.";
        static final String THRESHOLD_PREFIX = "log4j.threshold";
        public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
        private static final String INTERNAL_ROOT_NAME = "root";
    }
    

    2.4.2 rootLogger 解析原理

    rootLogger 的配置源码 org.apache.log4j.PropertyConfigurator#parseCategory

    void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value) {
        LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value + "].");
        // 按照逗号进行分割
        StringTokenizer st = new StringTokenizer(value, ",");
        if (!value.startsWith(",") && !value.equals("")) {
            if (!st.hasMoreTokens()) {
                return;
            }
    		// 第一个参数作为 level
            String levelStr = st.nextToken();
            LogLog.debug("Level token is [" + levelStr + "].");
            if (!"inherited".equalsIgnoreCase(levelStr) && !"null".equalsIgnoreCase(levelStr)) {
                logger.setLevel(OptionConverter.toLevel(levelStr, Level.DEBUG));
            } else if (loggerName.equals("root")) {
                LogLog.warn("The root logger cannot be set to null.");
            } else {
                logger.setLevel((Level)null);
            }
    
            LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
        }
    
        logger.removeAllAppenders();
    	// 后续的参数作为 appender
        while(st.hasMoreTokens()) {
            String appenderName = st.nextToken().trim();
            if (appenderName != null && !appenderName.equals(",")) {
                LogLog.debug("Parsing appender named \"" + appenderName + "\".");
                Appender appender = this.parseAppender(props, appenderName);
                if (appender != null) {
                    logger.addAppender(appender);
                }
            }
        }
    }
    

    2.5 内部日志记录

    public class LogLog {
        public static void debug(String msg) {
            if (debugEnabled && !quietMode) {
                System.out.println("log4j: " + msg);
            }
        }
    }
    

    可以使用 LogLog.setInternalDebugging(true) 打开开关,这是LogLog的内部静态方法。

    2.6 Layouts 格式

    在 log4j.properties 配置文件中,我们定义了日志输出级别与输出端,在输出端中分别配置日志的输出 格式。

    log4j 采用类似 C 语言的 printf 函数的打印格式格式化日志信息,具体的占位符及其含义如下:

    形式含义
    %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输出代码中的行号,【不推荐】会影响性能
    %%输出一个 “%” 字符

    可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式。如:

    形式含义
    %5c输出category名称,最小宽度是5,category<5,默认的情况下右对齐
    %-5c输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
    %.5c输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
    %20.30ccategory名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉

    2.7 日志文件配置

    log4j.rootLogger = trace,console
    
    # 指定appender
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    # 指定layout
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    # 指定格式化信息
    log4j.appender.console.layout.conversionPattern = %r [%5t] %p %l - %m%n
    
    log4j.appender.file = org.apache.log4j.FileAppender
    log4j.appender.file.layout = org.apache.log4j.PatternLayout
    log4j.appender.file.layout.conversionPattern = %r [%5t] %p %l - %m%n
    # 来源于SetFile方法
    log4j.appender.file.file = ./log4j.log
    log4j.appender.file.encoding = UTF-8
    

    2.7.1 RollingFileAppender

    log4j.rootLogger = trace,rollingFile
    
    log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
    log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.rollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
    log4j.appender.rollingFile.file = ./log4j.log
    log4j.appender.rollingFile.encoding = UTF-8
    log4j.appender.rollingFile.maxFileSize = 10KB
    log4j.appender.rollingFile.maxBackupIndex = 1
    

    2.7.2 DailyRollingFileAppender

    log4j.rootLogger = trace,dailyRollingFile
    
    # 按照文件的大小进行拆分
    log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.dailyRollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
    log4j.appender.dailyRollingFile.file = ./log4j.log
    log4j.appender.dailyRollingFile.encoding = UTF-8
    # 指定日期拆分规则
    log4j.appender.dailyRollingFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss
    

    2.7.3 JDBCAppender

    log4j.rootLogger = trace,sql
    
    # 按照数据库级别进行日志记录
    log4j.appender.sql = org.apache.log4j.jdbc.JDBCAppender
    log4j.appender.sql.layout = org.apache.log4j.PatternLayout
    log4j.appender.sql.Driver = com.mysql.jdbc.Driver
    log4j.appender.sql.URL = jdbc:mysql://localhost:3306/test
    log4j.appender.sql.User = root
    log4j.appender.sql.Password = root
    log4j.appender.sql.Sql = INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
    
    CREATE TABLE `log` (
        `log_id` int(11) NOT NULL AUTO_INCREMENT,
        `project_name` varchar(255) DEFAULT NULL COMMENT '目项名',
        `create_date` varchar(255) DEFAULT NULL COMMENT '创建时间',
        `level` varchar(255) DEFAULT NULL COMMENT '优先级',
        `category` varchar(255) DEFAULT NULL COMMENT '所在类的全名',
        `file_name` varchar(255) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称 ',
        `thread_name` varchar(255) DEFAULT NULL COMMENT '日志事件的线程名',
        `line` varchar(255) DEFAULT NULL COMMENT '号行',
        `all_category` varchar(255) DEFAULT NULL COMMENT '日志事件的发生位置',
        `message` varchar(4000) DEFAULT NULL COMMENT '输出代码中指定的消息',
        PRIMARY KEY (`log_id`)
    );
    

    2.8 自定义Logger

    level会进行覆盖,appender会进行继承,也就是dailyRollingFileconsole会同时起作用。

    该**包名**下面的全部类都是使用这个这种类型

    log4j.rootLogger = trace,dailyRollingFile
    
    # 自定义logger
    log4j.logger.cn.quguai = info,console
    
    log4j.rootLogger = trace,dailyRollingFile
    
    log4j.logger.cn.quguai = info,console
    
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.conversionPattern = %r [%5t] %p %l - %m%n
    
    log4j.appender.file = org.apache.log4j.FileAppender
    log4j.appender.file.layout = org.apache.log4j.PatternLayout
    log4j.appender.file.layout.conversionPattern = %r [%5t] %p %l - %m%n
    log4j.appender.file.file = ./log4j.log
    log4j.appender.file.encoding = UTF-8
    
    log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
    log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.rollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
    log4j.appender.rollingFile.file = ./log4j.log
    log4j.appender.rollingFile.encoding = UTF-8
    log4j.appender.rollingFile.maxFileSize = 1KB
    log4j.appender.rollingFile.maxBackupIndex = 5
    
    log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.dailyRollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
    log4j.appender.dailyRollingFile.file = ./log4j.log
    log4j.appender.dailyRollingFile.encoding = UTF-8
    log4j.appender.dailyRollingFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss
    
    log4j.appender.sql = org.apache.log4j.jdbc.JDBCAppender
    log4j.appender.sql.layout = org.apache.log4j.PatternLayout
    log4j.appender.sql.Driver = com.mysql.jdbc.Driver
    log4j.appender.sql.URL = jdbc:mysql://localhost:3306/test
    log4j.appender.sql.User = root
    log4j.appender.sql.Password = root
    log4j.appender.sql.Sql = INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
    

    3. JCL日志门面

    image-20211222203416866

    3.1 JCL入门

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

    默认使用JUL作为日志底层实现,当导入log4j的包的时候自动进行转换。

    @Test
    public void testQuick(){
        Log log = LogFactory.getLog(JCLTest.class);
        BasicConfigurator.configure();
        log.fatal("s");
        log.error("error");
        log.warn("warning");
        log.info("info");
        log.debug("debug");
        log.trace("trace");
    }
    

    3.2 JCL原理

    Log

    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"
    };
    

    流程如下:

    org.apache.commons.logging.LogFactory#getLog(java.lang.Class)
    org.apache.commons.logging.impl.LogFactoryImpl#getInstance(java.lang.String)
    org.apache.commons.logging.impl.LogFactoryImpl#newInstance
    org.apache.commons.logging.impl.LogFactoryImpl#discoverLogImplementation

    for(int i = 0; i < classesToDiscover.length && result == null; ++i) {
        result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
    }
    

    4. Slf4J 技术

    官网文档 | 官方地址

    4.1 Slf4j 入门

    使用 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.26</version>
    </dependency>
    

    slf4j 相比于JCL 只提供了5个日志级别,去除了fatal

    public class Slf4jTest {
        private static final Logger logger = LoggerFactory.getLogger(Slf4jTest.class);
        
        @Test
        public void testQuick(){
            logger.error("error");
            logger.warn("warning");
            logger.info("info");  // slf4j-simple 默认日志级别 简单的日志框架的都是这个
            logger.debug("debug"); // 复杂的日志框架都是这个,logback log4j
            logger.trace("trace");
        }
    }
    

    输出形式

    logger.info("用户:{}{}", "李扬", 1);
    try {
        int i = 10 / 0;
    } catch (Exception e) {
        logger.error("出现异常", e);
    }
    

    4.2 日志绑定

    本质上就是依靠slf4j进行日志门面的同意管理

    • 蓝色部分代表默认遵循了slf4j的规范可以直接进行使用
    • 青色部分代表了适配层,需要引入桥接进行使用

    • slf4j-nop 代表了日志的开关,如果引入这个包,代表日志不在进行使用,全部禁用;
    • 适配层的包,默认会将上下两层的包全部导入,例如slf4j-log412.jar,默认依赖了slf4j和log4j;
    • 直接实现的包,默认也直接依赖了slf4j;

    推荐直接引入处于第三层的包(红色边框)就可以完全导入所有依赖。

    绑定流程

    1. 添加slf4j-api的依赖
    2. 使用slf4j的API在项目中进行统一的日志记录
    3. 绑定具体的日志实现框架(其实默认就已经引入了slf4j的依赖)
      1. 绑定已经实现了slf4j的日志框架,直接添加对应依赖
      2. 绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖
    4. slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)

    4.3 绑定原理流程

    org.slf4j.LoggerFactory#getLogger(java.lang.String)

    org.slf4j.LoggerFactory#getILoggerFactory()

    org.slf4j.LoggerFactory#performInitialization()

    org.slf4j.LoggerFactory#bind()

    org.slf4j.LoggerFactory#findPossibleStaticLoggerBinderPathSet() 其中的STATIC_LOGGER_BINDER_PATH=“org/slf4j/impl/StaticLoggerBinder.class”,只要实现这个方法就可以完成绑定操作。

    4.4 桥接原理

    桥接和绑定不同,绑定适用于前期的选型问题,确定下来就已经定死了,桥接适用于一些老代码的日志切换为Slf4j进行管理。不会改变原有的代码格式。桥接解决的是项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转换到slf4j的实现。

    1. 先去除之前老的日志框架的依赖
    2. 添加SLF4J提供的桥接组件
    3. 为项目添加SLF4J的具体实现

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>log4j-over-slf4j</artifactId>
        <version>1.7.26</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.8</version>
    </dependency>
    

    注意问题:

    1. jcl-over-slf4j.jarslf4j-jcl.jar不能同时部署。前一个jar文件将导致JCL将日志系统的选择委托给 SLF4J,后一个jar文件将导致SLF4J将日志系统的选择委托给JCL,从而导致无限循环;
    2. log4j-over-slf4j.jarslf4j-log4j12.jar不能同时出现;
    3. jul-to-slf4j.jarslf4j-jdk14.jar不能同时出现;
    4. 所有的桥接都只对Logger日志记录器对象有效,如果程序中调用了内部的配置类或者是 Appender,Filter等对象,将无法产生效果。

    5. logback

    官网文档 | 中文文档

    logback-classic 默认导入了 slf4j 以及 logback-core(都是一个人的嘛)

    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.8</version>
    </dependency>
    

    默认日志级别debug (复杂日志框架(log4j logback)都是debug,简单实现日志框架(jul slf4j-sample)都是info)

    image-20211223154125775

    @Test
    public void testQuick() {
        logger.error("error");
        logger.warn("warn");
        logger.info("info");
        logger.debug("debug");  // 默认级别
        logger.trace("trace");
    }
    

    5.1 配置文件

    可以通过 PropertiesTranslator 将log4j.properties 转化为xml形式

    logback会依次读取以下类型配置文件:

    • logback.groovy
    • logback-test.xml
    • logback.xml
    • 如果均不存在会采用默认配置(BasicConfigurator,直接输出到控制台上)

    logback组件之间的关系

    • Logger: 日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也 可以定义日志类型、级别;
    • Appender: 用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等;
    • Layout: 负责把事件转换成字符串,格式化的日志信息的输出。在logback中Layout对象被封 装在encoder中。

    5.1.1 配置文件详情

    大写的简化形式都不推荐,影响性能问题

    格式化含义备注
    %-5level = %p = %le日志级别
    %d{yyyy-MM-dd HH:mm:ss.SSS}日期
    %c{length} = %logger = %lo类的完整名称后面数字代表简写的形式不代表长度
    %thread= %t线程名称
    %r = %relative程序启动到创建日志记录的时间单位是毫秒
    %m = %msg信息
    %n换行
    %C = %class全限定类名避免使用
    %Mmethod避免使用
    %L行号避免使用
    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
        
        <!--配置集中管理属性-->
        <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
        
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <!--控制输出流对象,默认为System.out 变为红色字体-->
            <target>System.err</target>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
        </appender>
    
        <root level="ALL">
            <appender-ref ref="console"/>
        </root>
    </configuration>
    

    5.1.2 文件输出

    一般封装好的类都处于classic包下面,没有封装好的,进行二次开发的都处于core包下面

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
        <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
        <property name="log_dir" value="./logs"/>
    
        <appender name="file" class="ch.qos.logback.core.FileAppender">
            <file>${log_dir}/logback.log</file>
            <append>true</append>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
        </appender>
    
        <root level="ALL">
            <appender-ref ref="file"/>
        </root>
    </configuration>
    

    5.1.3 自定义html输出

    一般封装好的类都处于classic包下面,没有封装好的,进行二次开发的都处于core包下面

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
        <!--配置集中管理属性-->
        <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
        <property name="log_dir" value="./logs"/>
        
        <appender name="file" class="ch.qos.logback.core.FileAppender">
            <file>${log_dir}/logback.log</file>
            <append>true</append>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
        </appender>
    
        <appender name="html" class="ch.qos.logback.core.FileAppender">   <==classic包下面
            <file>${log_dir}/logback.html</file>
            <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">   <==core包下面
                <layout class="ch.qos.logback.classic.html.HTMLLayout">        <==classic包下面
                    <pattern>%d{HH:mm:ss.SSS} %thread %-5level %c{36} - %msg%n</pattern>
                </layout>
            </encoder>
        </appender>
    
        <root level="ALL">
            <appender-ref ref="file"/>
            <appender-ref ref="html"/>
        </root>
    </configuration>
    

    5.1.4 日志拆分和归档压缩

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
        <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
        <property name="log_dir" value="./logs"/>
    
        <!--日志拆分和归档压缩-->
        <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${log_dir}/roll_logback.log</file>
            <append>true</append>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <!--按照文件大小进行拆分-->
                <maxFileSize>1mb</maxFileSize>
                <!--按照时间进行拆分和归档压缩-->
                <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
            </rollingPolicy>
        </appender>
    
        <root level="ALL">
            <appender-ref ref="rollFile"/>
        </root>
    </configuration>
    

    5.1.5 日志过滤器

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
        <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
        <property name="log_dir" value="./logs"/>
    
        <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${log_dir}/roll_logback.log</file>
            <append>true</append>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <maxFileSize>1mb</maxFileSize>
                <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
            </rollingPolicy>
            <!--过滤器-->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">   <== 过滤器
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
    
        <root level="ALL">
            <appender-ref ref="rollFile"/>
        </root>
    </configuration>
    

    5.1.6 异步日志记录

    异步日志借助其他的appender完成异步任务,底层使用阻塞队列完成

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
        <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
        <property name="log_dir" value="./logs"/>
    
        <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${log_dir}/roll_logback.log</file>
            <append>true</append>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <maxFileSize>1mb</maxFileSize>
                <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
            </rollingPolicy>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
        <!--异步日志记录-->
        <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
            <appender-ref ref="rollFile"/>  <== 借助rollFile完成异步任务
        </appender>
    
        <root level="ALL">
            <appender-ref ref="async"/>
        </root>
    </configuration>
    

    5.1.7 自定义Logger

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
        <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
        <property name="log_dir" value="./logs"/>
    
        <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${log_dir}/roll_logback.log</file>
            <append>true</append>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <maxFileSize>1mb</maxFileSize>
                <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
            </rollingPolicy>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
    
        <root level="ALL">
            <appender-ref ref="rollFile"/>
        </root>
         
         <!--自定义logger additivity=false不存在父子继承关系-->
        <logger name="cn.quguai" level="info" additivity="false">
            <appender-ref ref="console"/>
        </logger>
    </configuration>
    

    **注意:**即使会输出root级别的文件,但是大小均为0kb。

    5.2 logback-access的使用

    logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。我们可以使 用logback-access模块来替换tomcat的访问日志。

    1. 将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下

    2. 修改$TOMCAT_HOME/conf/server.xml中的Host元素中添加:

      <Valve className="ch.qos.logback.access.tomcat.LogbackValve" />
      
    3. logback默认会在$TOMCAT_HOME/conf下查找文件 logback-access.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <configuration>
      <!-- always a good activate OnConsoleStatusListener -->
      	<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/>
      	<property name="LOG_DIR" value="${catalina.base}/logs"/>
      	<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
      		<file>${LOG_DIR}/access.log</file>
      		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      			<fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
      		</rollingPolicy>
          	<encoder>
      		<!-- 访问日志的格式 -->
      			<pattern>combined</pattern>  <== logback集成的格式信息可以参考下方链接
      		</encoder>
      	</appender>
      	<appender-ref ref="FILE"/>
      </configuration>
      
    4. 官方配置: https://logback.qos.ch/access.html#configuration

    6. Log4j2

    官方文档 | 配置文件

    6.1 快速入门

    <!--日志门面-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.11.1</version>
    </dependency>
    <!--日志实现-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.11.1</version>
    </dependency>
    
    public class Log4j2Test {
        private static final Logger logger = LogManager.getLogger(Log4j2Test.class);
    
        @Test
        public void testQuick(){
            logger.fatal("fatal");
            logger.error("error"); // 默认级别
            logger.warn("warn");
            logger.info("info");
            logger.debug("debug");
            logger.trace("trace");
        }
    }
    

    实际开发当中依然使用slf4jlogback的组合方式进行

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.15.0</version>
    </dependency>
    

    6.2 配置文件

    配置文件参数上和log4j形式上十分相同

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="warn" monitorInterval="5">
        <properties>
            <property name="LOG_HOME">./logs</property>
        </properties>
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/>
            </Console>
            <File name="file" fileName="${LOG_HOME}/myfile.log">
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
            </File>
            <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
            </RandomAccessFile>
            <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
                         filePattern="D:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyyMM-dd-HH-mm}-%i.log">
                <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %msg%n"/>
                <Policies>
                    <!--系统启动时,会自动进行触发,生成一个新的日志文件-->
                    <OnStartupTriggeringPolicy/>
                    <SizeBasedTriggeringPolicy size="10 MB"/>
                    <TimeBasedTriggeringPolicy/>
                </Policies>
                <!--同一个文件夹下,最大的文件个数-->
                <DefaultRolloverStrategy max="30"/>
            </RollingFile>
        </Appenders>
        <Loggers>
            <!--默认使用rootLogger-->
            <Root level="trace">
                <AppenderRef ref="Console"/>
            </Root>
        </Loggers>
    </Configuration>
    

    6.3 异步日志(额外依赖)

    image-20211223193012334

    • 异步日志形式

    image-20211223193037614

    Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应 前面我们说的Appender组件和Logger组件。
    注意:配置异步日志需要添加依赖

    <dependency>
        <groupId>com.lmax</groupId>
        <artifactId>disruptor</artifactId>
        <version>3.3.4</version>
    </dependency>
    

    6.3.1 AsyncAppender方式

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="warn" monitorInterval="5">
        <properties>
            <property name="LOG_HOME">./logs</property>
        </properties>
        <Appenders>
            <File name="file" fileName="${LOG_HOME}/myfile.log">
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
            </File>
            <Async name="Async">  <==异步形式
                <AppenderRef ref="file"/>
            </Async>
        </Appenders>
        <Loggers>
            <!--默认使用rootLogger-->
            <Root level="error">
                <AppenderRef ref="Async"/> <==异步形式
            </Root>
        </Loggers>
    </Configuration>
    

    6.3.2 AsyncLogger方式

    AsyncLogger才是log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的 更快。你可以有两种选择:全局异步和混合异步。

    • 全局异步就是,所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加一个 log4j2.component.properties 配置;

      Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
      
    • 混合异步就是,你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。

      <?xml version="1.0" encoding="UTF-8"?>
      <Configuration status="warn" monitorInterval="5">
          <properties>
              <property name="LOG_HOME">./logs</property>
          </properties>
          <Appenders>
              <Console name="Console" target="SYSTEM_OUT">
                  <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/>
              </Console>
              <File name="file" fileName="${LOG_HOME}/myfile.log">
                  <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
              </File>
          </Appenders>
          <Loggers>
              <!--默认使用rootLogger-->
              <Root level="error">
                  <AppenderRef ref="Console"/>
              </Root>
      
              <!--自定义异步Logger对象 includeLocation:关闭日志行号信息 addivitity:是否进行继承-->
              <AsyncLogger name="cn.quguai" level="info" includeLocation="false" addivitity="false">
                  <AppenderRef ref="file"/>
              </AsyncLogger>
          </Loggers>
      </Configuration>
      

    使用异步日志需要注意的问题:

    1. 如果使用异步日志,AsyncAppenderAsyncLogger全局日志,不要同时出现。性能会和 AsyncAppender一致,降至最低。
    2. 设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。

    7. SpringBoot 日志配置

    默认使用slf4j + logback 进行实现

    <dependency>
        <artifactId>spring-boot-starter-logging</artifactId>
        <groupId>org.springframework.boot</groupId>
    </dependency>
    

    7.1 快速入门

    直接使用spring配置

    logging.level.cn.quguai = debug
    
    logging.pattern.console=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%p] %c -%m%n
    logging.pattern.file=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%p] %c -%m%n
    
    # 两者只能留下一个path默认文件名是spring.log
    #logging.file.name=./logs/springboot.log
    logging.file.path=./logs 
    
    logging.logback.rollingpolicy.file-name-pattern=${logging.file.path}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip
    logging.logback.rollingpolicy.max-file-size=1KB
    logging.logback.rollingpolicy.max-history=30
    
    @SpringBootTest
    class SpringBootLogApplicationTests {
    
        private static final Logger logger = LoggerFactory.getLogger(SpringBootLogApplicationTests.class);
    
        @Test
        void contextLoads() {
            logger.info("info");  // 默认级别
            logger.debug("debug");
    
            // 即使桥接器的形式使用log4j2的门面输出,默认还是会转为slf4j+logback
            org.apache.logging.log4j.Logger logger = LogManager.getLogger(SpringBootLogApplicationTests.class);
            logger.info("info log4j2");
            logger.debug("debug log4j2");
        }
    }
    

    7.2 指定配置

    给类路径下放上每个日志框架自己的配置文件;SpringBoot就不使用默认配置的了

    日志框架配置文件
    Logbacklogback-spring.xml, logback.xml
    Log4j2log4j2-spring.xml , log4j2.xml
    JULlogging.properties

    7.3 使用SpringBoot解析日志配置

    logback-spring.xml:由SpringBoot解析日志配置

    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <springProfile name="default">
            <pattern>${pattern}</pattern>
        </springProfile>
        <springProfile name="test">
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} ==== %msg%n</pattern>
        </springProfile>
    </encoder>
    
    spring.profiles.active=test
    

    7.4 日志切换到log4j2

    image-20211223210425792

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>   <== 排除logging依赖:logback依赖
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>  <== 引入log4j2的依赖,底层依然使用slf4j
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    

    image-20211223211107925

    7. 配置文件汇总

    7.1 JUL配置文件

    handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
    .level= ALL
    
    # Self-Logger
    cn.quguai.handlers = java.util.logging.FileHandler
    cn.quguai.level = CONFIG
    cn.quguai.useParentHandlers=false
    
    java.util.logging.FileHandler.pattern = ./java%u.log
    java.util.logging.FileHandler.limit = 50000
    java.util.logging.FileHandler.count = 1
    
    java.util.logging.FileHandler.maxLocks = 100
    java.util.logging.FileHandler.append = true
    java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
    
    java.util.logging.ConsoleHandler.level = ALL
    java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
    java.util.logging.ConsoleHandler.encoding = UTF-8
    java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n
    

    7.2 Log4j 配置文件

    log4j.rootLogger = trace,dailyRollingFile
    
    # ???logger
    log4j.logger.cn.quguai = info,console
    
    # ??appender
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    # ??layout
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    # ???????
    log4j.appender.console.layout.conversionPattern = %r [%5t] %p %l - %m%n
    
    log4j.appender.file = org.apache.log4j.FileAppender
    log4j.appender.file.layout = org.apache.log4j.PatternLayout
    log4j.appender.file.layout.conversionPattern = %r [%5t] %p %l - %m%n
    log4j.appender.file.file = ./log4j.log
    log4j.appender.file.encoding = UTF-8
    
    # ???????????
    log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
    log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.rollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
    log4j.appender.rollingFile.file = ./log4j.log
    log4j.appender.rollingFile.encoding = UTF-8
    # ???????????
    log4j.appender.rollingFile.maxFileSize = 1KB
    # ?????????
    log4j.appender.rollingFile.maxBackupIndex = 5
    
    # ???????????
    log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
    log4j.appender.dailyRollingFile.layout.conversionPattern = %r [%5t] %p %l - %m%n
    log4j.appender.dailyRollingFile.file = ./log4j.log
    log4j.appender.dailyRollingFile.encoding = UTF-8
    # ????????
    log4j.appender.dailyRollingFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss
    
    # ?????????????
    log4j.appender.sql = org.apache.log4j.jdbc.JDBCAppender
    log4j.appender.sql.layout = org.apache.log4j.PatternLayout
    log4j.appender.sql.Driver = com.mysql.jdbc.Driver
    log4j.appender.sql.URL = jdbc:mysql://localhost:3306/test
    log4j.appender.sql.User = root
    log4j.appender.sql.Password = root
    log4j.appender.sql.Sql = INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
    

    7.3 Logback 配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration>
        <!--配置集中管理属性-->
        <!-- 日志输出格式:
            %-5level = %p = %le
            %d{yyyy-MM-dd HH:mm:ss.SSS}日期
            %c{length}类的完整名称 = %logger = %lo :后面数字代表简写的形式不代表长度
            %C = %class :避免使用
            %M为method   :避免使用
            %L为行号     :避免使用
            %thread线程名称 = %t
            %m或者%msg为信息
            %n换行 -->
        <property name="pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %c{36} - %msg%n"/>
        <property name="log_dir" value="./logs"/>
    
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <!--控制输出流对象,默认为System.out 变为红色字体-->
            <target>System.err</target>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
        </appender>
    
        <!--文件输出-->
        <appender name="file" class="ch.qos.logback.core.FileAppender">
            <file>${log_dir}/logback.log</file>
            <append>true</append>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
        </appender>
    
        <!--html 格式输出-->
        <appender name="html" class="ch.qos.logback.core.FileAppender">
            <file>${log_dir}/logback.html</file>
            <!--自定义 encoder -->
            <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
                <layout class="ch.qos.logback.classic.html.HTMLLayout">
                    <pattern>%d{HH:mm:ss.SSS} %thread %-5level %c{36} - %msg%n</pattern>
                </layout>
            </encoder>
        </appender>
    
        <!--日志拆分和归档压缩-->
        <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${log_dir}/roll_logback.log</file>
            <append>true</append>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${pattern}</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <!--按照文件大小进行拆分-->
                <maxFileSize>1mb</maxFileSize>
                <!--按照时间进行拆分和归档压缩-->
                <fileNamePattern>${log_dir}/roll_logback.%d{yyyy-MM-dd-HH-mm-ss}.log%i.zip</fileNamePattern>
                <!--只保留最近30天的-->
                <maxHistory>30</maxHistory>
            </rollingPolicy>
            <!--过滤器-->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
        <!--异步日志记录-->
        <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
            <appender-ref ref="rollFile"/>
        </appender>
    
        <root level="ALL">
            <appender-ref ref="console"/>
            <appender-ref ref="async"/>
            <appender-ref ref="html"/>
        </root>
    
        <!--自定义logger additivity是否存在父子继承关系-->
        <logger name="cn.quguai" level="info" additivity="false">
            <appender-ref ref="console"/>
        </logger>
    </configuration>
    

    7.4 Log4j2 配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="warn" monitorInterval="5">
        <properties>
            <property name="LOG_HOME">./logs</property>
        </properties>
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n"/>
            </Console>
            <File name="file" fileName="${LOG_HOME}/myfile.log">
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
            </File>
            <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %m%n"/>
            </RandomAccessFile>
            <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
                         filePattern="D:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyyMM-dd-HH-mm}-%i.log">
                <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
                <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l%c{36} - %msg%n"/>
                <Policies>
                    <!--系统启动时,会自动进行触发,生成一个新的日志文件-->
                    <OnStartupTriggeringPolicy/>
                    <SizeBasedTriggeringPolicy size="10 MB"/>
                    <TimeBasedTriggeringPolicy/>
                </Policies>
                <!--同一个文件夹下,最大的文件个数-->
                <DefaultRolloverStrategy max="30"/>
            </RollingFile>
            <!--使用异步Appender 等同于Logback-->
            <!--<Async name="Async">
                <AppenderRef ref="file"/>
            </Async>-->
        </Appenders>
        <Loggers>
            <!--默认使用rootLogger-->
            <Root level="error">
                <AppenderRef ref="Console"/>
            </Root>
    
            <!--自定义异步Logger对象 includeLocation:关闭日志行号信息 addivitity:是否进行继承-->
            <AsyncLogger name="cn.quguai" level="info" includeLocation="false" addivitity="false">
                <AppenderRef ref="file"/>
            </AsyncLogger>
        </Loggers>
    </Configuration>
    

    使用异步日志需要注意的问题:

    1. 如果使用异步日志,AsyncAppenderAsyncLogger全局日志,不要同时出现。性能会和 AsyncAppender一致,降至最低。
    2. 设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。
    展开全文
  • Java日志框架&日志门面介绍

    千次阅读 多人点赞 2021-01-01 19:30:08
    在使用Java的时候,想必大家或多或少都对我们使用的日志api有所疑惑,为什么网上有那么多日志框架可以选择,但是我们平时打印日志的时候用的却基本都是log.info("xxx")这种形式,为什么在我们的pom.xml中要配置那么...


    在使用Java的时候,想必大家或多或少都对我们使用的日志api有所疑惑,为什么网上有那么多日志框架可以选择,但是我们平时打印日志的时候用的却基本都是 log.info("xxx")这种形式,为什么在我们的 pom.xml中要配置那么多的日志依赖,或者有时还需要主动将某些第三方依赖里面的日志依赖给主动去掉,相信读完本篇文章,你对这些问题的疑惑将一一解除。

    一、日志

    生活中的日志是记录你生活点点滴滴,让它把你内心的世界表露出来,更好的诠释自己的内心世界,而电脑里的日志可以是有价值的信息宝库,也可以是毫无价值的数据泥潭。

    以上的介绍你百度百科对日志这个概念的介绍,想必阅读本篇文章的人都对日志是有一定的了解的了,所以本文也不做过多的赘述,我们直接进入后面对日志框架以及门面的介绍

    二、常见日志框架

    在Java领域,常见的日志框架有JUL、Log4j、Logback、log4j2

    历史

    log4j可以当之无愧地说是Java日志框架的元老,1999年发布首个版本,2012年发布最后一个版本,2015年正式宣布终止,至今还有无数的系统在使用log4j,甚至很多新系统的日志框架选型仍在选择log4j。

    然而老的不等于好的,在IT技术层面更是如此。尽管log4j有着出色的历史战绩,但早已不是Java日志框架的最优选择。

    在log4j被Apache Foundation收入门下之后,由于理念不合,log4j的作者Ceki离开并开发了slf4j和logback。

    slf4j因其优秀的性能和理念很快受到了广泛欢迎,2016年的统计显示,github上的热门Java项目中,slf4j是使用率第二名的类库(第一名是junit)。

    logback则吸取了log4j的经验,实现了很多强大的新功能,再加上它和slf4j能够无缝集成,也受到了欢迎。

    在这期间,Apache Logging则一直在关门憋大招,log4j2在beta版鼓捣了几年,终于在2014年发布了GA版,不仅吸收了logback的先进功能,更通过优秀的锁机制、LMAX Disruptor、"无垃圾"机制等先进特性,在性能上全面超越了log4j和logback。

    好了。那么上面我们讲完了框架们的历史,下面我们正式对这些日志框架进行基本的介绍

    各大框架介绍

    JUL

    在这里插入图片描述
    j.u.l是java.util.logging包的简称,是JDK在1.4版本中引入的Java原生日志框架。Java Logging API提供了七个日志级别用来控制输出。这七个级别分别是:SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST。

    Log4j(1999-2015)

    Log4j 是一种非常流行的日志框架,由Ceki Gülcü首创,之后将其开源贡献给 Apache 软件基金会。

    通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

    Log4j也有七种日志级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG和TRACE。

    log4j可以说是当之无愧的Java日志框架的元老,1999年发布首个版本,2012年发布最后一个版本,2015年正式宣布终止,至今还有无数的系统在使用log4j,甚至很多新系统的日志框架选型仍在选择log4j。

    在log4j被Apache Foundation收入门下之后,由于理念不合,log4j的作者Ceki离开并开发了slf4j和logback。

    Logback(2006-?)

    Logback也是一个很成熟的日志框架,从06年开始第一个版本,迭代至今也十几年了。其中Logback、Log4j、Slf4j都是出自同一个人之手

    logback 主要分为3个模块:

    • logback-core:核心代码模块
    • logback-classic:log4j的一个改良版本,同时实现了slf4j的接口,这样你如果之后要切换其他日志组件也是一件很容易的事
    • logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能

    Log4j2

    前面介绍过Log4j,这里要单独介绍一下Log4j2,之所以要单独拿出来说,而没有和Log4j放在一起介绍,是因为作者认为,Log4j2已经不仅仅是Log4j的一个升级版本了,而是从头到尾被重写的,log4j2 是 log4j 1.x 的升级版,其参考了 logback 的一些优秀的设计,并且修复了一些问题,其实可以认为这其实就是完全不同的两个框架。

    在现在我们单独使用时:其jar包分别是

    • org.apache.logging.log4j » log4j-api-2.x.x.jar(api包,类似于日志门面)
    • org.apache.logging.log4j » log4j-core-2.x.x.jar(具体实现)
      注意其前缀是log4j而不是log4j2

    上面我们并没有对这些日志框架进行非常详细的使用介绍,感兴趣的同学们可以自行查询相关资料进一步了解,这里我们仅提供这些日志框架的对比信息

    Logback与Log4j2对比

    在以上介绍的日志框架中,logback和log4j2都宣称自己是log4j的后代,一个是出于同一个作者,另一个则是在名字上根正苗红。由于Log4j Apache已经不再更新,所以我们现在使用的日志框架,基本就是在logback以及log4j2之中选择一个。

    撇开血统不谈,我们比较一下log4j2和logback:

    • log4j2比logback更新:log4j2的GA版在2014年底才推出,比logback晚了好几年,这期间log4j2确实吸收了slf4j和logback的一些优点(比如日志模板),同时应用了不少的新技术
    • 由于采用了更先进的锁机制和LMAX Disruptor库,log4j2的性能优于logback,特别是在多线程环境下和使用异步日志的环境下
    • 二者都支持Filter(应该说是log4j2借鉴了logback的Filter),能够实现灵活的日志记录规则(例如仅对一部分用户记录debug级别的日志)
    • 二者都支持对配置文件的动态更新
    • 二者都能够适配slf4j,logback与slf4j的适配应该会更好一些,毕竟省掉了一层适配库(同一作者)
    • logback能够自动压缩/删除旧日志
    • logback提供了对日志的HTTP访问功能
    • log4j2实现了“无垃圾”和“低垃圾”模式。简单地说,log4j2在记录日志时,能够重用对象(如String等),尽可能避免实例化新的临时对象,减少因日志记录产生的垃圾对象,减少垃圾回收带来的性能下降

    关于它们两者的性能对比,有兴趣的大家可以参阅这篇文章

    三、日志门面

    前面介绍了四种日志框架,也就是说,我们想要在应用中打印日志的时候,可以使用以上四种类库中的任意一种。比如想要使用Log4j,那么只要依赖Log4j的jar包,配置好配置文件并且在代码中使用其API打印日志就可以了。

    不知道有多少人看过《阿里巴巴Java开发手册》,其中有一条规范做了『强制』要求:

    在这里插入图片描述

    说好了以上四种常用的日志框架是给Java应用提供的方便进行记录日志的,那为什么又不让在应用中直接使用其API呢?这里面推崇使用的SLF4J是什么呢?所谓的门面模式又是什么东西呢?

    什么是日志门面

    日志门面,是门面模式的一个典型的应用。

    门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用,这里我们也可以抽象地理解为Java的接口。

    在这里插入图片描述

    就像前面介绍的几种日志框架一样,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。

    为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。

    在软件开发领域有这样一句话:计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。而门面模式就是对于这句话的典型实践。

    为什么需要日志门面

    前面提到过一个重要的原因,就是为了在应用中屏蔽掉底层日志框架的具体实现。这样的话,即使有一天要更换代码的日志框架,只需要修改jar包,最多再改改日志输出相关的配置文件就可以了。这就是解除了应用和日志框架之间的耦合。

    有人或许会问了,如果我换了日志框架了,应用是不需要改了,那日志门面不还是需要改的吗?

    要回答这个问题,我们先来举一个例子,再把门面模式揉碎了重新解释一遍。

    日志门面就像饭店的服务员,而日志框架就像是后厨的厨师。对于顾客这个应用来说,我到饭店点菜,我只需要告诉服务员我要一盘番茄炒蛋即可,我不关心后厨的所有事情。因为虽然主厨从把这道菜称之为『番茄炒蛋』A厨师换成了把这道菜称之为『西红柿炒鸡蛋』的B厨师。但是,顾客不需要关心,他只要下达『番茄炒蛋』的命令给到服务员,由服务员再去翻译给厨师就可以了。

    所以,对于一个了解了”番茄炒蛋的多种叫法”的服务员来说,无论后厨如何换厨师,他都能准确的帮用户下单。

    同理,对于一个设计的全面、完善的日志门面来说,他也应该是天然就兼容了多种日志框架的。所以,底层框架的更换,日志门面几乎不需要改动。

    以上,就是日志门面的一个比较重要的好处——解耦。

    常用日志门面

    介绍过了日志门面的概念和好处之后,我们看看Java生态体系中有哪些好的日志门面的实现可供选择。

    Slf4j

    Java简易日志门面(Simple Logging Facade for Java,缩写SLF4J),是一套包装Logging 框架的界面程式,以外观模式实现。可以在软件部署的时候决定要使用的 Logging 框架,目前主要支援的有Java Logging API、Log4j及logback等框架。以MIT 授权方式发布。其作者是 Log4j 以及 Logback 的作者 Ceki Gülcü

    其实,SLF4J只是一个门面服务而已,他并不是真正的日志框架,真正的日志的输出相关的实现还是要依赖其背后的日志实现Log4j2、logback等日志框架的。

    Commons-Logging

    Apache Commons Logging(原名 Jakarta Commons Logging,JCL)是一个基于Java的日志记录实用程序,是用于日志记录和其他工具包的编程模型。它通过其他一些工具提供API,日志实现和包装器实现。

    commons-logging和SLF4J的功能是类似的,主要是用来做日志 门面的。提供更加好友的API工具。

    四、日志桥接

    这里我们用Slf4j为例,通过Slf4j可以在部署的时候不修改任何配置即可接入一种日志实现方案。但是,他在它编译时需要静态绑定真正的Log库。使用Slf4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合(各种桥接包)。

    这个东西要用图才能说得清楚,借用官网一张图:

    在这里插入图片描述

    包名桥接流功能
    jcl-over-slf4j.jarjcl -> slf4jJakarta Commons Logging日志框架到 slf4j 的桥接
    jul-to-slf4j.jarjuc -> slf4jjava.util.logging的日志桥接到 slf4j
    log4j-over-slf4j.jarlog4j -> slf4j将log4j 的日志,桥接到slf4j
    osgi-over-slf4j.jarosgi -> slf4j将osgi环境下的日志,桥接到slf4j
    slf4j-api.jarslf4j 的api接口jar包
    slf4j-jcl.jarlf4j -> jclslf4j 转接到 Jakarta Commons Logging日志输出框架
    slf4j-jdk14.jarslf4j -> julslf4j 转接到 java.util.logging,注意这个包不能和jul-to-slf4j.jar同时用,否则会死循环!
    slf4j-log4j12.jarslf4j -> log4jslf4j 转接到 log4j,注意这个包不能和log4j-over-slf4j.jar同时用,否则会死循环!
    slf4j-nop.jarslf4j -> nullslf4j的空接口输出绑定,丢弃所有日志输出
    slf4j-simple.jarslf4j -> slf4j-simpleslf4j自带的简单日志输出接口

    其实,在现在的日志框架中,有些框架本身也有针对其他的日志框架做一部分桥接的功能,如Log4j2

    包名桥接流功能
    log4j-1.2-api.jarlog4j -> log4j2将log4j 的日志转接到log4j2日志框架,注意这里的1.2不是版本号,而是1-to-2的意思
    log4j-api.jarlog4j2的api接口jar包
    log4j-core.jarlog4j2的日志输出核心jar包
    log4j-slf4j-impl.jarslf4j -> log4j2slf4j 转接到 log4j2 的日志输出框架
    log4j-to-slf4j.jarlog4j2 -> slf4j将 log4j2的日志桥接到 slf4j
    log4j-juljul -> log4j2将jul的日志桥接到log4j2

    从这里就已经可以看到,日志框架之间的关系有点乱。

    因为log4j2和slf4j都能对接多种日志框架,所以这些包的依赖,作用,还有命名,都容易让人混淆。

    PS:Log4j第一代的包名就叫Log4j,其最新的版本为2012年更新的1.2.17,后续的各种log4j-xxx,其实大部分都属于Log4j2的包了

    下面我们可以演示下各种日志类型的桥接包应该如何引入

    log4j -> log4j2

    那么就得去掉 log4j 1.x jar,添加log4j-1.2-api.jar,配合 log4j-api-2.x.x.jar 和 log4j-core-2.x.x.jar 即可,依赖如下

    log4j-1.2-api.jar
        log4j-api-2.x.x.jar
            log4j-core-2.x.x.jar
    

    log4j2 -> log4j

    这里我们只是演示如何做桥接,实际上当然不建议这么做的。
    理清了上面的jar包作用,就会发现,可以通过 log4j2 -> slf4j -> log4j 的方式来实现。

    需要的jar包,根据依赖关系分别是:

    log4j-api-2.x.x.jar
        log4j-to-slf4j.jar
            slf4j-api-x.x.x.jar
                slf4j-log4j12-x.x.x.jar
                    log4j-1.2.17.jar
    

    log4j -> slf4j

    将代码中的log4j日志桥接到 slf4j,需要如下jar包

    log4j-over-slf4j-x.x.x.jar
        slf4j-api-x.x.x.jar
    

    log4j2 -> slf4j

    将代码中的log4j2日志桥接到 slf4j,需要如下jar包

    log4j-api-2.x.x.jar
        log4j-to-slf4j-2.x.x.jar
            slf4j-api-x.x.x.jar
    

    slf4j -> log4j2

    将slf4j日志,采用log4j2实现进行输出,需要如下jar包

    slf4j-api-x.x.x.jar
        log4j-slf4j-impl.jar
            log4j-api.jar
                log4j-core.jar
    

    五、Spring日志解析

    spring日志依赖

    通常在我们的spring-boot-starter依赖中,会默认引入日志依赖如下:

    在这里插入图片描述

    我们通过查看Spring默认的日志依赖引入,可以清楚的看到,其使用日志门面slf4j来适配底层的日志框架,而日志框架用的则是logback,同时提供了对Java默认的JUL转换的SLF4J的适配器。

    细心的朋友们可以看到,这里spring还默认引入了一个log4j-to-slf4j的适配器,在我们主动将该配置Exclude之后其并不会影响我们项目的运行。

    PS:这里的log4j-to-slf4j大家不要以为是Log4j一代哈,根据上文日志桥接章节所说,它其实是Log4j2,只是官方省写了2,其作用是将 log4j2的日志桥接到 slf4j

    在这里插入图片描述

    那么为什么Spring默认要引入这个配置呢?

    这其实是为了提高我们代码依赖的鲁棒性,现如今,很多的组件/框架都会使用日志进行日志打印,而我们无法保证他们用的一定是logback/log4j2,因此,spring通过默认引入log4j2的桥接包,可以在我们引入使用log4j2作为日志框架的第三方组件时,自动将其桥接到slf4j,进而使用我们指定的日志框架(如spring默认的logback)实现进行打印

    为什么Spring默认使用Logback而不是Log4j2作为日志实现?

    针对这个问题,大家可以查看该链接进行了解

    修改日志实现

    如果我们不想使用Spring默认的日志框架,那么也可以采用类似的方式将它们对应的 spring-boot-starter 依赖模块加到 Maven 依赖中即可,如Log4j2使用如下依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    

    但一定不要将这些完成同一目的的 spring-boot-starter 都加到依赖中(这就是我们开头讲到的为什么有些日志依赖需要我们主动排除掉)

    根据不同的日志系统,可以按如下规则组织配置文件名,就能被Spring正确加载:

    • Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
    • Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
    • Log4j2:log4j2-spring.xml, log4j2.xml
    • JDK (Java Util Logging):logging.properties

    Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项

    如果你即想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,可以通过logging.config属性指定自定义的名字:

    logging.config=classpath:logging-config.xml
    

    总结

    在Java生态体系中,围绕着日志,有很多成熟的解决方案。关于日志输出,主要有两类工具。

    一类是日志框架,主要用来进行日志的输出的,比如输出到哪个文件,日志格式如何等。 另外一类是日志门面,主要一套通用的API,用来屏蔽各个日志框架之间的差异的。

    所以,对于Java工程师来说,关于日志工具的使用,最佳实践就是在应用中使用如Log4j2/Logback + SLF4J 这样的组合来进行日志输出。

    这样做的最大好处,就是业务层的开发不需要关心底层日志框架的实现及细节,在编码的时候也不需要考虑日后更换框架所带来的成本。这也是门面模式所带来的好处。

    综上,请不要在你的Java代码中出现任何Log4j等日志框架的API的使用,而是应该直接使用SLF4J这种日志门面。

    文章参考链接:
    https://www.jianshu.com/p/85d141365d39
    https://www.hollischuang.com/archives/3000
    https://www.jianshu.com/p/d7b0e981868d
    https://www.springboottutorial.com/logging-with-spring-boot-logback-slf4j-and-log4j

    展开全文
  • Java最详细常用日志框架介绍

    千次阅读 2020-07-09 23:32:44
    Java日志概述 对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪,基于日志的业务逻辑统计分析等都离不日志。java领域存在多种日志框架,目前常用的日志... Log4j是几种Java日志框架之一。 Log4j 2 Apache

    Java日志概述

    对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪,基于日志的业务逻辑统计分析等都离不日志。java领域存在多种日志框架,目前常用的日志框架包括Log4j1,Log4j2,Commons Logging,Slf4j,Logback,Jul。

    Java常用日志框架类别

    Log4j Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在则是Apache软件基金会的一个项目。 Log4j是几种Java日志框架之一。

    Log4j 2 Apache Log4j 2是apache开发的一款Log4j的升级产品。

    Commons Logging Apache基金会所属的项目,是一套Java日志接口,之前叫Jakarta Commons Logging,后更名为Commons Logging。

    Slf4j 类似于Commons Logging,是一套简易Java日志门面,本身并无日志的实现。(Simple Logging Facade for Java,缩写Slf4j)。

    Logback 一套日志组件的实现(Slf4j阵营)。

    Jul (Java Util Logging),自Java1.4以来的官方日志实现。

    看了上面的介绍是否会觉得比较混乱,这些日志框架之间有什么异同,都是由谁在维护,在项目中应该如何选择日志框架,应该如何使用? 下文会逐一介绍。

    Java常用日志框架历史

    • 1996年早期,欧洲安全电子市场项目组决定编写它自己的程序跟踪API(Tracing API)。经过不断的完善,这个API终于成为一个十分受欢迎的Java日志软件包,即Log4j。后来Log4j成为Apache基金会项目中的一员。

    • 期间Log4j近乎成了Java社区的日志标准。据说Apache基金会还曾经建议Sun引入Log4j到java的标准库中,但Sun拒绝了。

    • 2002年Java1.4发布,Sun推出了自己的日志库JUL(Java Util Logging),其实现基本模仿了Log4j的实现。在JUL出来以前,Log4j就已经成为一项成熟的技术,使得Log4j在选择上占据了一定的优势。

    • 接着,Apache推出了Jakarta Commons Logging,JCL只是定义了一套日志接口(其内部也提供一个Simple Log的简单实现),支持运行时动态加载日志组件的实现,也就是说,在你应用代码里,只需调用Commons Logging的接口,底层实现可以是Log4j,也可以是Java Util Logging。

    • 后来(2006年),Ceki Gülcü不适应Apache的工作方式,离开了Apache。然后先后创建了Slf4j(日志门面接口,类似于Commons Logging)和Logback(Slf4j的实现)两个项目,并回瑞典创建了QOS公司,QOS官网上是这样描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一个通用,可靠,快速且灵活的日志框架)。

    • 现今,Java日志领域被划分为两大阵营:Commons Logging阵营和Slf4j阵营。
      Commons Logging在Apache大树的笼罩下,有很大的用户基数。但有证据表明,形式正在发生变化。2013年底有人分析了GitHub上30000个项目,统计出了最流行的100个Libraries,可以看出Slf4j的发展趋势更好:
      在这里插入图片描述

    • Apache眼看有被Logback反超的势头,于2012-07重写了Log4j1.x,成立了新的项目Log4j 2, Log4j 2具有Logback的所有特性。

    java常用日志框架关系

    • Log4j2与Log4j1发生了很大的变化,Log4j2不兼容Log4j1。
    • Commons Logging和Slf4j是日志门面(门面模式是软件工程中常用的一种软件设计模式,也被称为正面模式、外观模式。它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用)。Log4j和Logback则是具体的日志实现方案。可以简单的理解为接口与接口的实现,调用者只需要关注接口而无需关注具体的实现,做到解耦。
    • 比较常用的组合使用方式是Slf4j与Logback组合使用,Commons Logging与Log4j组合使用。
    • Logback必须配合Slf4j使用。由于Logback和Slf4j是同一个作者,其兼容性不言而喻。

    Commons Logging与Slf4j实现机制对比

    Commons Logging实现机制

    Commons Logging是通过动态查找机制,在程序运行时,使用自己的ClassLoader寻找和载入本地
    具体的实现。详细策略可以查看commons-logging-*.jar包中的
    org.apache.commons.logging.impl.LogFactoryImpl.java文件。由于Osgi不同的插件使用独立的
    ClassLoader,Osgi的这种机制保证了插件互相独立, 其机制限制了Commons Logging在Osgi中的正常使用。
    

    Slf4j实现机制

    Slf4j在编译期间,静态绑定本地的Log库,因此可以在Osgi中正常使用。它是通过查找类路径
    下org.slf4j.impl.StaticLoggerBinder,然后在StaticLoggerBinder中进行绑定。
    

    项目中选择日志框架选择

    如果是在一个新的项目中建议使用Slf4j与Logback组合,这样有如下的几个优点:

    • Slf4j实现机制决定Slf4j限制较少,使用范围更广。由于Slf4j在编译期间,静态绑定本地的LOG库使得通用性要比Commons Logging要好。
    • Logback拥有更好的性能。Logback声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在Logback中需要3纳秒,而在Log4J中则需要30纳秒。LogBack创建记录器(logger)的速度也更快:13毫秒,而在Log4J中需要23毫秒。更重要的是,它获取已存在的记录器只需94纳秒,而Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的。
    • Commons Logging开销更高
    # 在使Commons Logging时为了减少构建日志信息的开销,通常的做法是
    if(log.isDebugEnabled()){
      log.debug("User name: " +
        user.getName() + " buy goods id :" + good.getId());
    }
    
    # 在Slf4j阵营,你只需这么做:
    log.debug("User name:{} ,buy goods id :{}", user.getName(),good.getId());
    
    # 也就是说,Slf4j把构建日志的开销放在了它确认需要显示这条日志之后,减少内存和Cup的开销,使用占位符号,代码也更为简洁
    
    • Logback文档免费。Logback的所有文档是全面免费提供的,不象Log4J那样只提供部分免费文档而需要用户去购买付费文档。

    Slf4j用法

    Slf4j与其它日志组件的关系说明

    • Slf4j的设计思想比较简洁,使用了Facade设计模式,Slf4j本身只提供了一个slf4j-api-version.jar包,这个jar中主要是日志的抽象接口,jar中本身并没有对抽象出来的接口做实现。
    • 对于不同的日志实现方案(例如Logback,Log4j…),封装出不同的桥接组件(例如logback-classic-version.jar,slf4j-log4j12-version.jar),这样使用过程中可以灵活的选取自己项目里的日志实现。

    Slf4j与其它日志组件调用关系图

    在这里插入图片描述

    Slf4j与其他各种日志组件的桥接说明

    jar包名说明
    slf4j-log4j12-1.7.13.jarLog4j1.2版本的桥接器,你需要将Log4j.jar加入Classpath。
    slf4j-jdk14-1.7.13.jarjava.util.logging的桥接器,Jdk原生日志框架。
    slf4j-nop-1.7.13.jarNOP桥接器,默默丢弃一切日志。
    slf4j-simple-1.7.13.jar一个简单实现的桥接器,该实现输出所有事件到System.err. 只有Info以及高于该级别的消息被打印,在小型应用中它也许是有用的。
    slf4j-jcl-1.7.13.jarJakarta Commons Logging 的桥接器. 这个桥接器将Slf4j所有日志委派给Jcl。
    logback-classic-1.0.13.jar(requires logback-core-1.0.13.jar)Slf4j的原生实现,Logback直接实现了Slf4j的接口,因此使用Slf4j与Logback的结合使用也意味更小的内存与计算开销
    • 具体的接入方式参见下图
      在这里插入图片描述

    Slf4j源码分析

    slf4j-api-version.jar中几个核心类与接口

    类与接口用途
    org.slf4j.LoggerFactory(class)给调用方提供的创建Logger的工厂类,在编译时绑定具体的日志实现组件
    org.slf4j.Logger(interface)给调用方提供的日志记录抽象方法,例如debug(String msg),info(String msg)等方法
    org.slf4j.ILoggerFactory(interface)获取的Logger的工厂接口,具体的日志组件实现此接口
    org.slf4j.helpers.NOPLogger(class)对org.slf4j.Logger接口的一个没有任何操作的实现,也是Slf4j的默认日志实现
    org.slf4j.impl.StaticLoggerBinder(class)与具体的日志实现组件实现的桥接类,具体的日志实现组件需要定义org.slf4j.impl包,并在org.slf4j.impl包下提供此类,注意在slf4j-api-version.jar中不存在org.slf4j.impl.StaticLoggerBinder,在源码包slf4j-api-version-source.jar中才存在此类

    Slf4j调用过程源码分析,只加入slf4j-api-version.jar,不加入任何实现包

    示例代码,pom核心配置如下:

    <dependencies>
      <!--只有slf4j-api依赖-->
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.13</version>
      </dependency>
    </dependencies>
    

    程序入口类如下:
    在这里插入图片描述
    源码追踪分析

    1. 调用LoggerFactory的getLogger()方法创建Logger
      在这里插入图片描述
    2. 调用LoggerFactory的getILoggerFactory方法来创建ILoggerFactory
      在这里插入图片描述
    3. 调用LoggerFactory的performInitialization方法来进行初始化
      在这里插入图片描述
    4. 调用LoggerFactory的bind()方法
      在这里插入图片描述
    5. 调用LoggerFactory的findPossibleStaticLoggerBinderPathSet()方法获取StaticLoggerBinderPath集合
      在这里插入图片描述
    6. 调用LoggerFactory的reportMultipleBindingAmbiguity()方法,记录绑定的StaticLoggerBinder信息
      在这里插入图片描述
    7. LoggerFactory的reportMultipleBindingAmbiguity()方法
      在这里插入图片描述
    8. LoggerFactory的bind()方法找不到StaticLoggerBinder,抛出NoClassDefFoundError异常
      在这里插入图片描述
    9. LoggerFactory的bind()方法捕获NoClassDefFoundError异常,匹配到StaticLoggerBinder关键词记录信息到控制台
      在这里插入图片描述
    10. LoggerFactory的performInitialization()方法内部调用bind()方法结束
      在这里插入图片描述
    11. LoggerFactory的getLogger()方法内部getILoggerFactory()方法调用完成,创建出NOPLoggerFactory,然后由NOPLoggerFactory调用内部的getLogger()方法,创建出NOPLogger
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    12. App类内部的logger实际为NOPLogger,调用logger.info()方法实际调用的是NOPLogger的info方法
      在这里插入图片描述
      在这里插入图片描述

    Slf4j调用过程源码分析,加入slf4j-api-version.jar,与Logback组件

    Slf4j作为门面采用Logback作为实现或者采用其它上面提到过的组件作为实现类似,这里只分析采用Logback组件作为实现
    

    示例代码,pom核心配置如下:

    <dependencies>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.13</version>
      </dependency>
      <!--logback-classic依赖logback-core,会自动级联引入-->
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
      </dependency>
    </dependencies>
    

    程序入口类同上,源码追踪分析,1、2、3、4同上:

    1. 调用LoggerFactory的findPossibleStaticLoggerBinderPathSet()方法获取StaticLoggerBinderPath集合
      在这里插入图片描述
    2. 调用LoggerFactory的bind()方法的staticLoggerBinderPathSet集合对象赋值
      在这里插入图片描述
    3. 在LoggerFactory的bind()方法中调用loback包下的StaticLoggerBinder创建单例对象
      在这里插入图片描述
    4. 在LoggerFactory的bind()方法中调用reportActualBinding()记录日志加载信息
      在这里插入图片描述
      在这里插入图片描述
    5. LoggerFactory中INITIALIZATION_STATE的值为SUCCESSFUL_INITIALIZATION,调用StaticLoggerBinder的单例对象获取ILoggerFactory
      在这里插入图片描述
      在这里插入图片描述
    6. 此时LoggerFactory中的getLogger()方法中获取到的ILoggerFactory实际上是logback jar下的LoggerContext
      在这里插入图片描述
    7. 此时LoggerFactory调用getLogger()方法获取到的Logger实际上是logback jar下的Logger
      在这里插入图片描述
      在这里插入图片描述

    Slf4j调用过程源码分析,加入slf4j-api-version.jar,同时加入多种日志实现组件

    在项目中如果用slf4j-api作为日志门面,有多个日志实现组件同时存在,例如同时存在Logback,
    slf4j-log4j12,slf4j-jdk14,slf4j-jcl四种实现,则在项目实际运行中,Slf4j的绑定选择绑定方式
    将有Jvm确定,并且是随机的,这样会和预期不符,实际使用过程中需要避免这种情况。
    

    示例代码,pom核心配置如下:

    <dependencies>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.25</version>
      </dependency>
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk14</artifactId>
        <version>1.7.25</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jcl</artifactId>
        <version>1.7.25</version>
      </dependency>
    </dependencies>
    

    程序入口类同上,源码追踪分析,基本步骤同上,这里只追踪主要不同点:

    1. 追踪LoggerFactory的bind()方法内部调用findPossibleStaticLoggerBinderPathSet()方法后,从classpath下4个jar包内找到StaticLoggerBinder
      在这里插入图片描述
    2. 此时LoggerFactory的bind()方法内部调用reportMultipleBindingAmbiguity()方法,给出警告信息classpath下同时存在多个StaticLoggerBinder,JVM会随机选择一个StaticLoggerBinder
      在这里插入图片描述

    使用Slf4时如何桥接遗留的api

    在实际环境中我们经常会遇到不同的组件使用的日志框架不同的情况,例如Spring Framework
    使用的是日志组件是Commons Logging,XSocket依赖的则是Java Util Logging。当我们在同一
    项目中使用不同的组件时应该如果解决不同组件依赖的日志组件不一致的情况呢?现在我们需要
    统一日志方案,统一使用Slf4j,把他们的日志输出重定向到Slf4j,然后Slf4j又会根据绑定器把日志
    交给具体的日志实现工具。Slf4j带有几个桥接模块,可以重定向Log4j,JCL和java.util.logging中
    的Api到Slf4j。
    

    遗留的api桥接方案

    jar包名作用
    log4j-over-slf4j-version.jar将Log4j重定向到Slf4j
    jcl-over-slf4j-version.jar将Commons Logging里的Simple Logger重定向到slf4j
    jul-to-slf4j-version.jar将Java Util Logging重定向到Slf4j

    桥接方式参见下图

    在这里插入图片描述

    使用Slf4j桥接注意事项

    • 在使用Slf4j桥接时要注意避免形成死循环,在项目依赖的jar包中不要存在以下情况。
    多个日志jar包形成死循环的条件产生原因
    log4j-over-slf4j.jar和slf4j-log4j12.jar同时存在由于slf4j-log4j12.jar的存在会将所有日志调用委托给log4j。但由于同时由于log4j-over-slf4j.jar的存在,会将所有对log4j api的调用委托给相应等值的slf4j,所以log4j-over-slf4j.jar和slf4j-log4j12.jar同时存在会形成死循环
    jul-to-slf4j.jar和slf4j-jdk14.jar同时存在由于slf4j-jdk14.jar的存在会将所有日志调用委托给jdk的log。但由于同时jul-to-slf4j.jar的存在,会将所有对jul api的调用委托给相应等值的slf4j,所以jul-to-slf4j.jar和slf4j-jdk14.jar同时存在会形成死循环

    遗留api桥接死循环源码分析源码

    这里以项目中集成log4j-over-slf4j与slf4j-log4j12为例,其它组合形成死循环原理相类似。
    

    示例代码,程序入口类同上,源码追踪分析:

    基本步骤同上,调用链路
    LoggerFactory.getLogger()>LoggerFactory.getILoggerFactory()> LoggerFactory.performInitialization()>LoggerFactory.bind()
    
    1. LoggerFactory.bind()方法内部调用StaticLoggerBinder.getSingleton()获取StaticLoggerBinder实例
      在这里插入图片描述
    2. StaticLoggerBinder调用构造方法内部调用Log4jLoggerFactory构造方法创建ILoggerFactory
      在这里插入图片描述
    3. Log4jLoggerFactory加载内部static代码块,校验出classpath下存在org.apache.log4j.Log4jLoggerFactory,抛出异常
      在这里插入图片描述

    排除掉项目中依赖的第三方包的日志依赖

    在实际使用过程中,项目会根据需要引入一些第三方组件,例如常用的Spring,而Spring本身的日志实现使用了Commons Logging,我们又想使用Slf4j+Loback组合,这时候需要在项目中将Commons Logging排除掉,通常会用到以下3种方案,3种方案各有利弊,可以根据项目的实际情况选择最适合自己项目的解决方案。

    方案一:采用maven的exclusion方案

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
        <version>${springframework.version}</version>
    </dependency>
    
    • 这种方案优点是exclusion是maven原生提供的,不足之处是如果有多个组件都依赖了commons-logging,则需要在很多处增加,使用起来不太方便

    方案二:在maven声明commons-logging的scope为provided

    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>1.8.0-beta2</version>
    </dependency>
    
    • 这种方案在调试代码时还是有可能导致IDE将commons-logging放置在classpath下,从而导致程序运行时出现异常

    方案三:在maven私服中增加类似于99.0-does-not-exist这种虚拟的版本号

    <dependency>    
        <groupId>commons-logging</groupId>    
        <artifactId>commons-logging</artifactId>    
        <version>99.0-does-not-exist</version>    
    </dependency> 
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>1.8.0-beta2</version>
    </dependency> 
    
    • 这种方案好处是声明方式比较简单,用IDE调试代码时也不会出现问题,不足之处是99.0-does-not-exist这种版本是maven中央仓库中是不存在的,需要发布到自己的maven私服中。

    总结

    由于历史原因JDK自身提供的Log组件出现的较晚,导致Jdk提供Log组件时第三方社区的日志组件已经比较稳定成熟。经过多年的发展Slf4j+Logback与组合,Commons Logging与Log4j组合两大阵营已经基本成为了Java项目开发的标准,建议在新的项目开发中从这两种方案中选择适合自己项目的组合方案

    参考链接

    Slf4j官网
    Slf4j使用手册1
    Slf4j使用手册2
    Logback官网
    Commons Logging官网

    展开全文
  • Java日志框架性能比较

    千次阅读 2019-01-18 16:26:44
    Java日志框架性能比较 1 Java日志框架性能比较 前面几章,笔者分别介绍了log4j,logback,log4j2三大日志实现框架。 接下来,就用具体的数据比较下,哪个日志框架的性能更好! 单线程:外循环100次,内循环100000次; ...
  • JAVA学习笔记(三十)-日志框架

    千次阅读 2022-02-10 13:55:11
    Java日志概述 对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪,基于日志的业务逻辑统计分析等都离不日志。java领域存在多种日志框架,目前常用的日志... Log4j是几种Java日志框架之一。 Log4j 2 Apac
  • 一种Java日志系统框架的设计与实现
  • java日志系统框架整理(转载)

    万次阅读 2018-02-06 11:45:38
    参考:(方便记录,直接将内容贴过来了。如有侵权,请留言删除,此致敬意!) 首先,在日志系统的森林里面理理头绪,修炼内功。参考文章如下: 1.... ...Java日志系统确实比较丰富,常用的
  • 就拿java来说,在早期的日志都是通过System.out.println()进行记录的,但是这种方式不便于管理,所以apache最先开发了首个日志框架:log4j; 为日志框架奠定了基础; 日志框架出现的历史顺序为 : log4j → JUL → ...
  • Java 日志框架解析:设计模式、性能

    千次阅读 2017-09-28 10:00:54
    在平常的系统开发中,...一个好的日志框架,既要方便易用,也要有较好的性能,减少日志输出对系统内存、CPU 的影响。 研究一款开源项目,学到的不仅仅是这个项目本身,还会学到很多设计思想,可以利用到日常工作中。
  • 一.Java常用的日志框架介绍 二.Java常用日志框架分析 2.1 JUL使用 2.2Log4j使用 1.添加依赖 2.在classpath下面添加配置文件:log4j.properties 3.使用Log4j记录日志 2.3JCL使用分析 2.4SLF4J(酸辣粉4斤) ...
  • JAVA日志框架

    千次阅读 2016-09-13 10:02:05
    1、日志框架 提供日志调用的接口,实际的日志输出委托给日志系统实现。 JCL(Jakarta Commons Logging):比较流行的日志框架,很多框架都依赖JCL,例如Spring等。 SLF4j:提供新的API,初衷是配合Logback使用...
  • Java日志框架的使用(slf4j、Logback)

    千次阅读 2022-04-08 17:48:32
    文章目录引言I slf4j1.1 应用举例1.2 门面模式see also 引言 日记级别 ERROR(40, "ERROR"), WARN(30, "WARN"), INFO(20, "INFO"), DEBUG(10, "DEBUG"), TRACE(0, "TRACE"); ...import org.slf4
  • spring aop 操作日志

    2014-06-04 00:14:07
    本资源用来展示如何使用 spring aop 进行日志记录,例子里面通过aop的配置,把产生的日志存放到当前项目的根目录下,而且对方法执行过程中的参数进行了记录,对于aop如何记录日志不清楚的同学可以看看。
  • Java常用日志框架介绍(转载)

    千次阅读 2018-08-07 18:17:34
    java日志概述 对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪,基于日志的业务逻辑统计分析等都离不日志。java领域存在多种日志框架,目前常用的日志框架包括Log4j,Log4j 2,Commons Logging,Slf...
  • 什么是日志框架 日志框架的选择 Logback的使用与配置 什么是日志框架 是一套能实现日志输出的工具包 能够描述系统运行状态的所有时间都可以算作日志 日志框架的能力 定制输出目标 定制输出格式 携带...
  • Java系统中常用日志框架

    万次阅读 多人点赞 2019-06-03 17:17:50
    文章目录日志介绍日志概念日志作用Java中常用日志框架Java常用日志框架之间的关系日志门面框架日志实现框架 日志介绍 日志概念 日志:在计算机领域,日志文件(logfile)是一个记录了发生在运行中的操作系统或其他...
  • Java常用日志框架介绍

    千次阅读 2017-12-29 23:19:15
    java日志概述 对于一个应用程序来说日志记录是必不可少的一部分。线上问题追踪,基于日志的业务逻辑统计分析等都离不日志。java领域存在多种日志框架,目前常用的日志框架包括Log4j,Log4j 2,Commons Logging,Slf...
  • Java程序员最常用的8个Java日志框架

    千次阅读 2016-12-26 16:52:48
    来源...然而日志系统是一个成熟Java应用所必不可少的,在开发和调试阶段,日志可以帮助我们更好更快地定位bug;在运行维护阶段,日志系统又可以帮我们记录大部分的异常信息,从而帮助我们更好
  • Java常用日志框架及实现

    千次阅读 2015-12-16 09:32:53
    Java常用日志框架及实现 作者:雨水,日期:2015-12-16,CSDN博客: http://blog.csdn.net/gobitan 概述 本文简单介绍了一下Java日志框架,包括接口库和实现库,并在具体使用选择上给出了一些建议。 日志...
  • 日志框架 日志框架的设计与开发 日志框架的原理是提供一个简单的系统来管理错误消息和理解程序的执行流程。 安装日志框架 下载 esiea.projet.archlog 包 为了测试框架的稳定性和性能,Base.java 文件包含一个包含...
  • 首先要说一下,本文对这些Java框架只做了一个总结,常用到的框架总结了14个,都值得深入去了解一下(什么是扎实基本功,这些就是基本功~~),小伙伴如果没有看到自己想学习的框架,可以给我说一下,我去做总结!...
  • java日志框架详解总结(含配置)

    千次阅读 2018-05-25 10:17:41
    该篇是集合了百度众多的日志框架详解,java日志框架分析的总结篇。 具体网址: https://blog.csdn.net/foreverling/article/details/51385128 https://blog.csdn.net/chszs/article/details/8653460 ...
  • 如何在 Java 中进行日志记录

    千次阅读 2021-10-27 14:02:26
    您可以使用本指南为您的应用程序发现、理解和使用正确的 Java 日志库,例如 Log4j2、Logback 或 java.util.logging。 日志“似乎”是一个非常简单的主题,但在实践中可能相当棘手,并且没有在任何地方进行足够详细...
  • 一个免费的java权限后台管理系统框架

    千次下载 热门讨论 2015-01-23 17:52:35
    java权限后台开发框架,采用spring + srpingsecurity + springMVC + Hibernate + freemarker + jquery 等技术实现,页面及源文件打包发布程序。 完整的功能应用,包括:后台权限、人员机构、参数代码、角色权限、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 288,335
精华内容 115,334
关键字:

java操作日志框架