Java各种日志框架使用示例

在实际的开发过程中,为什么Java的日志体系总会给编程人员一种混乱的感觉,根本的原因是因为Java的日志体系没有统一的实现和使用标准。比如正在开发的系统使用的日志框架为Slf4j和Logback,系统中所依赖第三方Jar使用的日志框架为JCL(Apache Common Logging),JUL(Java Util Logging),Log4j或Log4j2。因为日志输出格式不统一,无形中增加了系统维护成本和问题排查的难度。

日志框架

下面针对各个日志框架或日志的实现,通过代码的形式,详细的演示如何来使用各个日志工具。

JUL(Java Util Logging)

JUL是JDK 中自带的log功能。虽然是官方自带的log lib,JUL的使用确不广泛。使用该Log功能的好处是不需要依赖第三方的框架。

如何使用该日志功能,通过下面的例子在进行说明。

package com.mc.logging.jul;

/**
 * @author M.C
 * @description HelloService
 * @date 2019-02-11 17:48
 **/
public interface HelloService {
    /**
     * sayHello
     */
    public abstract void sayHello();
}

package com.mc.logging.jul.impl;

import com.mc.logging.jul.HelloService;

import java.util.logging.Logger;

/**
 * @author M.C
 * @description HelloServiceImpl
 * @date 2019-02-11 17:49
 **/
public class HelloServiceImpl implements HelloService {
    /**
     * logger
     */
    private Logger logger = Logger.getLogger(HelloServiceImpl.class.getName());
    /**
     * sayHello
     */
    public void sayHello() {
        logger.info("hello world with java util logging!");
    }

    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        helloService.sayHello();
    }
}

JUL的输出如下

二月 12, 2019 11:02:31 上午 com.mc.logging.jul.impl.HelloServiceImpl sayHello
信息: hello world with java util logging!

JCL(Apache Common Logging)

JCL是Apache下面的项目。JCL 是一个Log Facade,只提供 Log API,不提供实现,然后有 Adapter 来使用 Log4j 或者 JUL 作为Log Implementation。

如何使用该日志功能,通过下面的例子在进行说明。

  <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-jcl</artifactId>

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

</project>
package com.mc.logging.jcl;

/**
 * @author M.C
 * @description HelloService
 * @date 2019-02-11 17:48
 **/
public interface HelloService {
    /**
     * sayHello
     */
    public abstract void sayHello();
}


package com.mc.logging.jcl.impl;

import com.mc.logging.jcl.HelloService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author M.C
 * @description HelloServiceImpl
 * @date 2019-02-11 17:49
 **/
public class HelloServiceImpl implements HelloService {
    /**
     * logger
     */
    private Log logger = LogFactory.getLog(HelloServiceImpl.class);
    /**
     * sayHello
     */
    public void sayHello() {
        logger.info("hello world with apache common logging!");
    }

    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        helloService.sayHello();
    }
}

JCL的输出如下

二月 12, 2019 11:07:29 上午 com.mc.logging.jcl.impl.HelloServiceImpl sayHello
信息: hello world with apache common logging!

Log4j

Log4j 是在 Logback 出现之前被广泛使用的 Log Lib, 由 Gülcü 于2001年发布,后来成为Apache 基金会的顶级项目。Log4j 在设计上非常优秀,对后续的 Java Log 框架有长久而深远的影响,也产生了Log4c, Log4s, Log4perl 等到其他语言的移植。Log4j 的短板在于性能,在Logback 和 Log4j2 出来之后,Log4j的使用也减少了。

如何使用该日志功能,通过下面的例子在进行说明。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-log4j</artifactId>

    <dependencies>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

</project>
package com.mc.logging.log4j;

/**
 * @author M.C
 * @description HelloService
 * @date 2019-02-11 17:48
 **/
public interface HelloService {
    /**
     * sayHello
     */
    public abstract void sayHello();
}

package com.mc.logging.log4j.impl;

import com.mc.logging.log4j.HelloService;
import org.apache.log4j.Logger;

/**
 * @author M.C
 * @description HelloServiceImpl
 * @date 2019-02-11 17:49
 **/
public class HelloServiceImpl implements HelloService {
    /**
     * logger
     */
    private Logger logger = Logger.getLogger(HelloServiceImpl.class);
    /**
     * sayHello
     */
    public void sayHello() {
        logger.info("hello world with apache log4j!");
    }

    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        helloService.sayHello();
    }
}

log4j.properties的配置如下

### #配置根Logger ###
log4j.rootLogger=debug,stdout

### 输出到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyy-MM-dd HH\:mm\:ss} %5p %c{1}\:%L - %m%n

Log4j的输出如下

2019-02-12 11:14:40  INFO HelloServiceImpl:20 - hello world with apache log4j!

SLF4J/Logback

SLF4J(The Simple Logging Facade for Java) 和 Logback 也是Gülcü 创立的项目,其创立主要是为了提供更高性能的实现。其中,SLF4j 是类似于JCL 的Log Facade,Logback 是类似于Log4j 的 Log Implementation。

如何使用该日志功能,通过下面的例子在进行说明。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-logback</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</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>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>
</project>

logback.xml 配置信息

<?xml version="1.0" encoding="UTF-8"?>

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoder的默认实现类是ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- name值可以是包名或具体的类名:该包(包括子包)下的类或该类将采用此logger -->
    <logger name="com.mc.logging.slf4j.logback" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>

    <!-- root的默认level是DEBUG -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

演示代码

package com.mc.logging.slf4j.logback;

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

/**
 * 15:36:42.170 [main] DEBUG c.m.l.s.l.Slf4jLogback - slf4j bound logback implementation
 * 15:36:42.170 [main] DEBUG c.m.l.s.l.Slf4jLogback - slf4j bound logback implementation
 *
 * @author M.C
 * @description slf4j logback
 * @date 2019-02-11 15:09
 **/
public class Slf4jLogback {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jLogback.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.debug("slf4j bound logback implementation");
    }
}

SLF4J/Simple

slf4j-simple包是slf4j提供的一个简单实现。

如何使用该日志功能,通过下面的例子在进行说明。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-simple</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

</project>

simplelogger.properties配置

# SLF4J's SimpleLogger configuration file
# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.

# Default logging detail level for all instances of SimpleLogger.
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, defaults to "info".
#org.slf4j.simpleLogger.defaultLogLevel=info

# Logging detail level for a SimpleLogger instance named "xxxxx".
# Must be one of ("trace", "debug", "info", "warn", or "error").
# If not specified, the default logging detail level is used.
#org.slf4j.simpleLogger.log.xxxxx=

# Set to true if you want the current date and time to be included in output messages.
# Default is false, and will output the number of milliseconds elapsed since startup.
#org.slf4j.simpleLogger.showDateTime=false

# The date and time format to be used in the output messages.
# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
# If the format is not specified or is invalid, the default format is used.
# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z

# Set to true if you want to output the current thread name.
# Defaults to true.
#org.slf4j.simpleLogger.showThreadName=true

# Set to true if you want the Logger instance name to be included in output messages.
# Defaults to true.
#org.slf4j.simpleLogger.showLogName=true

# Set to true if you want the last component of the name to be included in output messages.
# Defaults to false.
#org.slf4j.simpleLogger.showShortLogName=false

演示代码

package com.mc.logging.slf4j.simple;

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

/**
 * [main] INFO com.mc.logging.slf4j.simple.Slf4jSimple - slf4j bound with slf4j simple implementation
 * @author M.C
 * @description slf4j simple
 * @date 2019-02-11 15:09
 **/
public class Slf4jSimple {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jSimple.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.info("slf4j bound with slf4j simple implementation");
    }
}

SLF4J/JUL

使用JUL作为Slf4j的实现来进行日志的记录。

如何使用该日志功能,通过下面的例子在进行说明。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-jul</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

</project>

jul-log.properties配置信息

handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$s] %5$s %n

演示代码

package com.mc.logging.slf4j.jul;

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

/**
 * 二月 11, 2019 4:05:19 下午 com.mc.logging.slf4j.jul.Slf4jJul main
 * 信息: slf4j bound java util logging implementation
 *
 * @author M.C
 * @description slf4j jul
 * @date 2019-02-11 15:09
 **/
public class Slf4jJul {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jJul.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.info("slf4j bound java util logging implementation");
    }
}

SLF4J/Log4j

使用Log4j作为Slf4j的实现来进行日志的记录。

如何使用该日志功能,通过下面的例子在进行说明。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-log4j</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
</project>

log4j.properties配置信息

### #配置根Logger ###
log4j.rootLogger=debug,stdout

### 输出到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyy-MM-dd HH\:mm\:ss} %5p %c{1}\:%L - %m%n

演示代码

package com.mc.logging.slf4j.log4j;

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

/**
 * 未配置log4j.properies的日志输出
 * log4j:WARN No appenders could be found for logger (com.mc.logging.slf4j.log4j.Slf4jLog4j).
 * log4j:WARN Please initialize the log4j system properly.
 * log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
 *
 * 配置后的输出
 * 2019-02-11 15:49:46 DEBUG Slf4jLog4j:26 - slf4j bound log4j implementation
 *
 * @author M.C
 * @description slf4j log4j
 * @date 2019-02-11 15:09
 **/
public class Slf4jLog4j {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jLog4j.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.debug("slf4j bound log4j implementation");
    }
}

SLF4J/Log4j2

使用Log4j2作为Slf4j的实现来进行日志的记录。

如何使用该日志功能,通过下面的例子在进行说明。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-log4j2</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.11.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.1</version>
        </dependency>
    </dependencies>

</project>

log4j2.xml配置信息

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

演示代码

package com.mc.logging.slf4j.log4j2;

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

/**
 * 17:05:08.180 [main] INFO  com.mc.logging.slf4j.log4j2.Slf4jLog4j2 - slf4j bound log4j2 implementation
 *
 * @author M.C
 * @description slf4j log4j2
 * @date 2019-02-11 15:09
 **/
public class Slf4jLog4j2 {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jLog4j2.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.info("slf4j bound log4j2 implementation");
    }
}

SLF4J/Unbound

没有使用任何日志实现的情况下,会遇到的日志输出问题通过如下代码进行演示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-unbound</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>
</project>

演示代码

package com.mc.logging.unbound;

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

/**
 * SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
 * SLF4J: Defaulting to no-operation (NOP) logger implementation
 * SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
 *
 * @author M.C
 * @description slf4j unbound
 * @date 2019-02-11 15:09
 **/
public class Slf4jUnbound {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jUnbound.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.trace("slf4j without bound any implementation");
    }
}

SLF4J/NOP

使用slf4j时,如果不想输出任何的日志,可以使用slf4j-nop。具体的代码演示和配置如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-nop</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-nop</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>
</project>

演示代码

package com.mc.logging.slf4j.nop;

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

/**
 * NOP means "no operation". Its purpose is not to do anything and thus not log anything.
 *
 * @author M.C
 * @description slf4j nop
 * @date 2019-02-11 15:09
 **/
public class Slf4jNop {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jNop.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.info("slf4j bound with slf4j no operation implementation");
    }
}

SLF4J/Logback&Log4j2

slf4j的日志具体实现中既存在logback也有log4j2。默认使用logback作为日志输出实现。

maven依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-multiple-bound</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!--logback-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--log4j2-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.11.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.1</version>
        </dependency>
    </dependencies>
</project>

log4j2.xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

logback.xml的配置

<?xml version="1.0" encoding="UTF-8"?>

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoder的默认实现类是ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- name值可以是包名或具体的类名:该包(包括子包)下的类或该类将采用此logger -->
    <logger name="com.mc.logging.slf4j.logback" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>

    <!-- root的默认level是DEBUG -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

代码实现

package com.mc.logging.slf4j.multiple.bound;

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

/**
 * SLF4J: Class path contains multiple SLF4J bindings.
 * SLF4J: Found binding in [jar:file:/D:/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
 * SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.11.1/log4j-slf4j-impl-2.11.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
 * SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
 * SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
 * 17:15:05.229 [main] INFO  c.m.l.s.m.b.Slf4jMultipleBound - slf4j bound with logback and log4j2 implementation
 *
 * @author M.C
 * @description slf4j multiple bound
 * @date 2019-02-11 15:09
 **/
public class Slf4jMultipleBound {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jMultipleBound.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.info("slf4j bound with logback and log4j2 implementation");
    }
}

SLF4J/Log4j&Log4j2

slf4j的日志具体实现中既存在log4j也有log4j2。默认使用log4j作为日志输出实现。

maven依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-multiple-bound2</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!--log4j-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--log4j2-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.11.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.1</version>
        </dependency>
    </dependencies>

</project>

log4j.properties的配置

### #配置根Logger ###
log4j.rootLogger=debug,stdout

### 输出到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyy-MM-dd HH\:mm\:ss} %5p %c{1}\:%L - %m%n

log4j2.xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

代码实现

package com.mc.logging.slf4j.multiple.bound2;

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

/**
 * SLF4J: Class path contains multiple SLF4J bindings.
 * SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
 * SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.11.1/log4j-slf4j-impl-2.11.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
 * SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
 * SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
 * 2019-02-11 17:35:29  INFO Slf4jMultipleBound2:28 - slf4j bound with log4j and log4j2 implementation
 *
 * @author M.C
 * @description slf4j multiple bound
 * @date 2019-02-11 15:09
 **/
public class Slf4jMultipleBound2 {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jMultipleBound2.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.info("slf4j bound with log4j and log4j2 implementation");
    }
}

SLF4J/Logback重定向log4j/JUC/JCL的日志

开发系统使用的是slf4j和logback,依赖的第三方jar分别使用到了log4j,juc和jcl做为日志工具,此时为了统一的日志管理,应该将三者的日志重定向到slf4j/logback。

maven依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-logback-redirection</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</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>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>com.mc.logging</groupId>
            <artifactId>mc-logging-jcl</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>com.mc.logging</groupId>
            <artifactId>mc-logging-jul</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jul-to-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>com.mc.logging</groupId>
            <artifactId>mc-logging-log4j</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>
</project>

logback.xml的配置

<?xml version="1.0" encoding="UTF-8"?>

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoder的默认实现类是ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- name值可以是包名或具体的类名:该包(包括子包)下的类或该类将采用此logger -->
    <logger name="com.mc.logging.slf4j.logback.redirection" level="DEBUG">
        <appender-ref ref="STDOUT" />
    </logger>

    <!-- root的默认level是DEBUG -->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

演示代码

package com.mc.logging.slf4j.logback.redirection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

/**
 * 不进行重定向的日志
 * 18:25:55.231 [main] DEBUG c.m.l.s.l.r.Slf4jLogbackRedirection - slf4j bound logback redirection implementation
 * 18:25:55.231 [main] DEBUG c.m.l.s.l.r.Slf4jLogbackRedirection - slf4j bound logback redirection implementation
 * 二月 11, 2019 6:25:55 下午 com.mc.logging.jul.impl.HelloServiceImpl sayHello
 * 信息: hello world with java util logging!
 * 2019-02-11 18:25:55  INFO HelloServiceImpl:21 - hello world with apache common logging!
 * 2019-02-11 18:25:55  INFO HelloServiceImpl:20 - hello world with apache log4j!
 *
 *
 * jcl重定向后的日志
 * 18:31:29.754 [main] DEBUG c.m.l.s.l.r.Slf4jLogbackRedirection - slf4j bound logback redirection implementation
 * 18:31:29.754 [main] DEBUG c.m.l.s.l.r.Slf4jLogbackRedirection - slf4j bound logback redirection implementation
 * 二月 11, 2019 6:31:29 下午 com.mc.logging.jul.impl.HelloServiceImpl sayHello
 * 信息: hello world with java util logging!
 * 18:31:29.773 [main] INFO  c.m.l.j.i.HelloServiceImpl - hello world with apache common logging!
 * 2019-02-11 18:31:29  INFO HelloServiceImpl:20 - hello world with apache log4j!
 *
 *
 * log4j重定向后的日志
 * 18:34:21.280 [main] DEBUG c.m.l.s.l.r.Slf4jLogbackRedirection - slf4j bound logback redirection implementation
 * 18:34:21.280 [main] DEBUG c.m.l.s.l.r.Slf4jLogbackRedirection - slf4j bound logback redirection implementation
 * 二月 11, 2019 6:34:21 下午 com.mc.logging.jul.impl.HelloServiceImpl sayHello
 * 信息: hello world with java util logging!
 * 18:34:21.297 [main] INFO  c.m.l.j.i.HelloServiceImpl - hello world with apache common logging!
 * 18:34:21.301 [main] INFO  c.m.l.l.i.HelloServiceImpl - hello world with apache log4j!
 *
 *jul重定向后的日志
 * 09:20:22.643 [main] DEBUG c.m.l.s.l.r.Slf4jLogbackRedirection - slf4j bound logback redirection implementation
 * 09:20:22.643 [main] DEBUG c.m.l.s.l.r.Slf4jLogbackRedirection - slf4j bound logback redirection implementation
 * 09:20:22.654 [main] INFO  c.m.l.j.i.HelloServiceImpl - hello world with java util logging!
 * 09:20:22.657 [main] INFO  c.m.l.j.i.HelloServiceImpl - hello world with apache common logging!
 * 09:20:22.661 [main] INFO  c.m.l.l.i.HelloServiceImpl - hello world with apache log4j!
 *
 *
 * @author M.C
 * @description slf4j logback redirection
 * @date 2019-02-11 15:09
 **/
public class Slf4jLogbackRedirection {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jLogbackRedirection.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.debug("slf4j bound logback redirection implementation");

        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();

        com.mc.logging.jul.HelloService helloServiceJul = new com.mc.logging.jul.impl.HelloServiceImpl();
        helloServiceJul.sayHello();

        com.mc.logging.jcl.HelloService helloServiceJcl = new com.mc.logging.jcl.impl.HelloServiceImpl();
        helloServiceJcl.sayHello();

        com.mc.logging.log4j.HelloService helloServiceLog4j = new com.mc.logging.log4j.impl.HelloServiceImpl();
        helloServiceLog4j.sayHello();
    }
}

SLF4J/Log4j重定向JUC/JCL的日志

开发系统使用的是slf4j和log4j,依赖的第三方jar分别使用到了juc和jcl做为日志工具,此时为了统一的日志管理,应该将第三方依赖的日志重定向到slf4j/log4j。

maven依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-log4j-redirection</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>com.mc.logging</groupId>
            <artifactId>mc-logging-jcl</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>com.mc.logging</groupId>
            <artifactId>mc-logging-jul</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jul-to-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

</project>

log4j.properties的配置

### #配置根Logger ###
log4j.rootLogger=debug,stdout

### 输出到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyy-MM-dd HH\:mm\:ss} %5p %c{1}\:%L - %m%n

演示代码

package com.mc.logging.slf4j.log4j.redirection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

/**
 * 2019-02-12 09:32:27 DEBUG Slf4jLog4jRedirection:54 - slf4j bound logback redirection implementation
 * 2019-02-12 09:32:27  INFO HelloServiceImpl:21 - hello world with java util logging!
 * 2019-02-12 09:32:27  INFO HelloServiceImpl:21 - hello world with apache common logging!
 *
 * @author M.C
 * @description slf4j log4j redirection
 * @date 2019-02-11 15:09
 **/
public class Slf4jLog4jRedirection {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jLog4jRedirection.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.debug("slf4j bound logback redirection implementation");

        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();

        com.mc.logging.jul.HelloService helloServiceJul = new com.mc.logging.jul.impl.HelloServiceImpl();
        helloServiceJul.sayHello();

        com.mc.logging.jcl.HelloService helloServiceJcl = new com.mc.logging.jcl.impl.HelloServiceImpl();
        helloServiceJcl.sayHello();
    }
}

SLF4J/JUL重定向Log4j/JCL的日志

开发系统使用的是slf4j和JUL,依赖的第三方jar分别使用到了log4j和jcl做为日志工具,此时为了统一的日志管理,应该将第三方依赖的日志重定向到slf4j/JUL。

maven依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mc-logging</artifactId>
        <groupId>com.mc.logging</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mc-logging-slf4j-jul-redirection</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>com.mc.logging</groupId>
            <artifactId>mc-logging-jcl</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>com.mc.logging</groupId>
            <artifactId>mc-logging-log4j</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

</project>

jul-log.properties的配置

handlers= java.util.logging.ConsoleHandler
.level= INFO
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$s] %5$s %n

演示代码

package com.mc.logging.slf4j.jul.redirection;

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

/**
 * 二月 12, 2019 9:41:58 上午 com.mc.logging.jcl.impl.HelloServiceImpl sayHello
 * 信息: hello world with apache common logging!
 * 二月 12, 2019 9:41:58 上午 com.mc.logging.log4j.impl.HelloServiceImpl sayHello
 * 信息: hello world with apache log4j!
 *
 * @author M.C
 * @description slf4j jul redirection
 * @date 2019-02-11 15:09
 **/
public class Slf4jJulRedirection {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(Slf4jJulRedirection.class);
    /**
     * main
     * @param args
     */
    public static void main(String[] args) {
        logger.debug("slf4j bound logback redirection implementation");

        com.mc.logging.jcl.HelloService helloServiceJcl = new com.mc.logging.jcl.impl.HelloServiceImpl();
        helloServiceJcl.sayHello();

        com.mc.logging.log4j.HelloService helloServiceLog4j = new com.mc.logging.log4j.impl.HelloServiceImpl();
        helloServiceLog4j.sayHello();
    }
}

演示项目结构

上述代码演示用到的项目整体结构如下图所示

实际项目日志框架分析与问题排查

通过上面详细的代码演示,我想各位应该对java的日志框架有了一个比较清楚的认识,下面我们就结合着具体的项目来进行日志框架的分析。

spring-boot-starter-logging

我们打开spring-boot-starter-logging的源代码,然后通过dependency analyzer来进行该项目的日志依赖分析可以得知该项目使用的日志记录框架为slf4j/logback重定向JUC,JCL和log4j日志的方式进行日志管理。

具体的依赖关系如下图所示

总结

到此,通过实际的项目和代码演示的形式,基本上把java项目开发中常用到的日志工具和使用介绍完了。剩下的就是在实际工作中灵活运用这些日志框架,提高我们编程和问题解决的效率。

猜你喜欢

转载自blog.csdn.net/liuxiao723846/article/details/109812833