Java中各种Log的使用

3,698 阅读7分钟

在看Spring源码的时候发现Spring中使用的Log是commons.logging中的Log,而不是我们常用的log4j。实际上commons.logging中的Log只提供一个调用Log的接口,并没有任何具体的实现,当我们调用commons.logging.LogFactory#getLog生成一个Log对象的时候,LogFactory会检查当前环境是否含有其他的Log实现。如果有log4j的话会直接使用log4j中的LogManager来生成Log。如果没有其他的任何Log实现,则会调用jdk提供的Log实现。

commons.logging似乎只适用于Spring这样的框架型应用,使用的时候可以直接利用项目中原有的配置(比如log4j)。在其他常规应用中应该直接使用log4j这样的具体Log实现,而不是commons.logging中的Log。

本文分析了直接使用commons.logging中的Log和配合使用其他Log工具的不同情况,给出了常用Log类的使用方法。

直接使用commons.logging中的Log

先在pom依赖中添加commons.logging:

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

然后在程序中直接调用:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LogExample {
    private static Log logger = LogFactory.getLog(LogExample.class.getName());
    public static void main(String[] args){
        System.out.println(logger.getClass());
        logger.debug("This is a debug information!");
        logger.error("This is an error information!");
        logger.info("This is an info information!");
        logger.warn("This is an warn information!");
        logger.fatal("This is a fatal information!");
    }
}

控制台输出的结果:

class org.apache.commons.logging.impl.Jdk14Logger
十一月 09, 2019 7:08:56 下午 j.LogExample main
严重: This is an error information!
十一月 09, 2019 7:08:56 下午 j.LogExample main
信息: This is an info information!
十一月 09, 2019 7:08:56 下午 j.LogExample main
警告: This is an warn information!
十一月 09, 2019 7:08:56 下午 j.LogExample main
严重: This is a fatal information!

可以看到

  • 默认打印info及以上级别的信息。debug<info<warn<error<fatal。
  • 使用的是Logger类是Jdk14Logger。此时没有配置任何其他Log类,因此使用的是jdk自带的一个Log实现。

配合使用log4j

在pom中添加log4j的依赖:

<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
  <type>bundle</type>
</dependency>

原来的代码不修改,执行结果为:

class org.apache.commons.logging.impl.Log4JLogger
log4j:WARN No appenders could be found for logger (j.LogExample).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

可以看到

  • commons.logging会扫描当前环境的相关依赖,如果有log4j则会直接使用log4j的Log实现。
  • 使用log4j需要先配置appender,如果没有配置则不会有任何输出。一般至少应该配置一个appender用于标准输出(System.out)

添加log4j.properties配置文件

在classpath中创建文件log4j.properties(如果是intellij的话,将该文件创建到被标记了resources的文件夹下)。写入配置信息:

log4j.rootLogger = DEBUG,console,file

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%-5p] %d [%t] %l: %m %n

log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.ImmediateFlush=true
log4j.appender.file.Append=true
log4j.appender.file.File=log.txt
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%-5p] %d [%t] %l: %m %n

可以看到上面的文件中配置了两个appender,一个console用于控制台打印(ConsoleAppender),一个file用于文件输出(FileAppender)。日志输出的最低级别为DEBUG。配置方式具体参考Log4j.properties配置详解

在源文件不修改的情况下再次运行,得到的结果为:

class org.apache.commons.logging.impl.Log4JLogger
[DEBUG] 2019-11-09 20:00:55,427 [main] j.LogExample.main(LogExample.java:10): This is a debug information! 
[ERROR] 2019-11-09 20:00:55,430 [main] j.LogExample.main(LogExample.java:11): This is an error information! 
[INFO ] 2019-11-09 20:00:55,430 [main] j.LogExample.main(LogExample.java:12): This is an info information! 
[WARN ] 2019-11-09 20:00:55,430 [main] j.LogExample.main(LogExample.java:13): This is an warn information! 
[FATAL] 2019-11-09 20:00:55,430 [main] j.LogExample.main(LogExample.java:14): This is a fatal information! 

使用其他文件配置

查看log4j的源码,在LogManager类中看到默认设置了两种文件自动配置的方式,分别为log4j.properties和log4j.xml:

static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";  

默认是先检查log4j.xml,然后再检查log4j.properties。

如果不使用默认的路径,需要修改文件的路径,则可以使用PropertyConfigurator.configure("config/log4j.my.properties");这样的方式来配置。注意,config是项目根目录的一个文件夹,而不是resources目录中的文件夹。

使用PropertyConfigurator.configure配置的时候是根据字符串参数创建一个FileInputStream,因此是直接从文件系统中读取;而默认读取配置文件的方式是使用java.lang.ClassLoader中的getResource方法来读取,读取的位置跟classpath有关,(Maven项目中)通常在target文件夹里面。在Intellij中如果将某个文件夹(比如resources)标记为resource root,则该文件夹下的所有内容都会直接拷贝到target中,也就是classpath目录下。

原来的代码改为:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.PropertyConfigurator;

public class LogExample {
    private static Log logger = LogFactory.getLog(LogExample.class.getName());
    static{
        PropertyConfigurator.configure("config/log4j.my.properties");
    }
    public static void main(String[] args){
        System.out.println(logger.getClass());
        logger.debug("This is a debug information!");
        logger.error("This is an error information!");
        logger.info("This is an info information!");
        logger.warn("This is an warn information!");
        logger.fatal("This is a fatal information!");
    }
}

单独使用Java代码配置log4j

一般较少使用代码配置。相比于配置文件而言更麻烦且不灵活。

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

import java.io.IOException;

public class LogExampleConfigureByJava {
    private static Logger logger = Logger.getLogger(LogExampleConfigureByJava.class.getName());

    static {
        logger.setLevel(Level.DEBUG);
        logger.addAppender(new ConsoleAppender(new PatternLayout("%-6r [%p] %c - %m%n")));
    }

    public static void main(String[] args) {
        logger.debug("This is a debug information!");
        logger.error("This is an error information!");
        logger.info("This is an info information!");
        logger.warn("This is an warn information!");
        logger.fatal("This is a fatal information!");
    }
}

其他commons.logging支持的Log工具(不适用于spring-jcl-5.1)

在commons.logging.impl.LogFactoryImpl中可以看到commons.logging支持的Log类:

/** Log4JLogger class name */
private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger";
/** Jdk14Logger class name */
private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger";
/** Jdk13LumberjackLogger class name */
private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = "org.apache.commons.logging.impl.Jdk13LumberjackLogger";
/** SimpleLog class name */
private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog";

也就是:

  • Log4JLogger
  • Jdk14Logger
  • Jdk13LumberjackLogger
  • SimpleLog

Use the org.apache.commons.logging.Log system property to identify the requested implementation class. If Log4J is available, return an instance of org.apache.commons.logging.impl.Log4JLogger. If JDK 1.4 or later is available, return an instance of* org.apache.commons.logging.impl.Jdk14Logger. Otherwise, return an instance of org.apache.commons.logging.impl.SimpleLog.

LogFactoryImpl扫描的顺序为:如果log4j可用的话,会使用Log4JLogger;否则,如果jdk版本为1.4以上的话,会使用Jdk14Logger;否则,会使用SimpleLog。

SimpleLog

SimpleLog使用起来很简单,格式也很简单:

import org.apache.commons.logging.impl.SimpleLog;

public class SimpleLogExample {
    private static SimpleLog logger = new SimpleLog(SimpleLogExample.class.getName());

    static {
        logger.setLevel(SimpleLog.LOG_LEVEL_DEBUG);
    }

    public static void main(String[] args) {
        System.out.println(logger.getClass());
        logger.debug("This is a debug information!");
        logger.error("This is an error information!");
        logger.info("This is an info information!");
        logger.warn("This is an warn information!");
        logger.fatal("This is a fatal information!");
    }
}

运行结果:

class org.apache.commons.logging.impl.SimpleLog
[ERROR] SimpleLogExample - This is an error information!
[INFO] SimpleLogExample - This is an info information!
[WARN] SimpleLogExample - This is an warn information!
[FATAL] SimpleLogExample - This is a fatal information!

默认情况下,SimpleLog会将所有log信息(可设置level筛选,默认为info)打印到System.err,输出格式比较简单,不能使用代码配置输出格式。

SimpleLog可以使用配置文件来配置(同样只能输出到System.err),参考官方文档,配置文件地址为simplelog.properties,使用ClassLoader加载,配置项有:

  • org.apache.commons.logging.simplelog.defaultlog - Default logging detail level for all instances of SimpleLog. Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). If not specified, defaults to "info".
  • org.apache.commons.logging.simplelog.log.xxxxx - Logging detail level for a SimpleLog instance named "xxxxx". Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). If not specified, the default logging detail level is used.
  • org.apache.commons.logging.simplelog.showlogname - Set to true if you want the Log instance name to be included in output messages. Defaults to false.
  • org.apache.commons.logging.simplelog.showShortLogname - Set to true if you want the last component of the name to be included in output messages. Defaults to true.
  • org.apache.commons.logging.simplelog.showdatetime - Set to true if you want the current date and time to be included in output messages. Default is false.
  • org.apache.commons.logging.simplelog.dateTimeFormat - 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 zzz.

其他commons.logging支持的Log工具(spring-jcl-5.1)

在Spring中,日志相关的工具放在jcl(Jakarta Commons Logging API)包里面。

JCL会扫描当前系统中的slf4j和log4j两种Log实现,优先考虑slf4j。我在系统中配置好了log4j的各项参数(log4j.properties),使用commons.loggging.Log运行起来后发现跟预期不符,把Logger类名打印出来才发现实际调用的是slf4j。另外,jcl中的SimpleLog和上面提到的SimpleLog是不一样的,jcl中的SimpleLog不包含任何实现,对于任何log信息都不做处理(甚至不打印出来)。

如果要使用log4j的话可以显式调用log4j包里面的Logger#getLogger。

如果是要使用Spring Boot自带的log工具(slf4j或其他自动查找到的Log实现),需要在application.properties中统一配置,比如:

logging.path=logs
logging.file=${logging.path}/log.log
logging.level.root=info
logging.level.com.example=warn
logging.pattern.console=%d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger.%M - %msg%n
logging.pattern.file=%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n

设置之后直接运行SprringBoot程序就能看到日志的变化。

参考