# 日志

# 概述

  • 用来记录程序运行过程中的信息,并可以进行永久存储

# 意义

  • 可以将日志信息选择性的记录到指定位置(控制台、文件、数据库、日志中间件等)
  • 支持以开关的方式进行控制是否记录,无需修改源码

# 常见日志框架

# Java Util Logging(原生)

# 使用示例

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author automannn
 * @Date 2022/8/31
 */
public class JULTest {
    public static void main(String[] args) {
        Logger log = Logger.getLogger(JULTest.class.getName());
        log.info("jul log info");
        log.log(Level.WARNING,"fine msg");
        log.log(Level.SEVERE,"severe msg");
        log.config("config msg");
        log.fine("fine msg");
        log.finer("finer msg");
        log.finest("finest msg");
    }
}
jul日志

# 日志级别

public class Level implements java.io.Serializable {
    
    //xxx:严重
    public static final Level SEVERE = new Level("SEVERE",1000, defaultBundle);
    
    //xxx:警告
    public static final Level WARNING = new Level("WARNING", 900, defaultBundle);

    //xxx:消息
    public static final Level INFO = new Level("INFO", 800, defaultBundle);

    //xxx:配置
    public static final Level CONFIG = new Level("CONFIG", 700, defaultBundle);

    //xxx:详细
    public static final Level FINE = new Level("FINE", 500, defaultBundle);
    //xxx:较详细
    public static final Level FINER = new Level("FINER", 400, defaultBundle);
    //xxx: 非常详细
    public static final Level FINEST = new Level("FINEST", 300, defaultBundle);
    
    //xxx:全部记录
    public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle);

    //xxx:不记录
    public static final Level OFF = new Level("OFF",Integer.MAX_VALUE, defaultBundle);
}

# 默认配置文件(jdk/jre/lib/logging.properties)

# xxx:默认将日志输出到控制台
handlers= java.util.logging.ConsoleHandler

# xxx: 默认的全局日志级别为 消息(消息,警告,严重会被打印)
.level= INFO

# xxx: 文件日志记录器 的默认配置
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

# xxx: 控制台日志记录器 的默认配置
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# xxx: 可以对其它日志记录器 进行配置,如下方 配置名称为 com.xyz.foo日志记录器的记录级别
com.xyz.foo.level = SEVERE

# 自定义配置

class Config{
    public void customConfig(){
        //xxx: 自定义配置文件
        InputStream ins = this.getClass().getClassLoader().getResourceAsStream("logging.properties");
        //xxx: LogManager 是一个单例对象
        LogManager logManager = LogManager.getLogManager();
        logManager.readConfiguration(ins);
    }    
}

# Log4J(apache)

# 使用示例

<dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
</dependency>
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

/**
 * @author automannn
 * @Date 2022/8/31
 */
public class Log4jTest {
    public static void main(String[] args) {
        Logger log = Logger.getLogger(Log4jTest.class);
        //配置日志信息
        BasicConfigurator.configure();

        log.fatal("fatal msg");
        log.error("error msg");
        log.warn("warn msg");
        log.info("info msg");
        log.debug("debug msg"); //默认级别
        log.trace("trace msg");

    }
}
log4j日志

# 日志级别

public class Level extends Priority implements Serializable {
    //xxx:不输出
    public static final Level OFF = new Level(2147483647, "OFF", 0);
    //xxx:致命
    public static final Level FATAL = new Level(50000, "FATAL", 0);
    //xxx:错误
    public static final Level ERROR = new Level(40000, "ERROR", 3);
    //xxx:警告
    public static final Level WARN = new Level(30000, "WARN", 4);
    //xxx:消息
    public static final Level INFO = new Level(20000, "INFO", 6);
    //xxx:调试
    public static final Level DEBUG = new Level(10000, "DEBUG", 7);
    //xxx:详细
    public static final Level TRACE = new Level(5000, "TRACE", 7);
    //xxx:所有输出
    public static final Level ALL = new Level(-2147483648, "ALL", 7);
}

# 配置(有多种配置方式)

  • BasicConfigurator可用于辅助快捷配置
  • org.apache.log4j.spi.Configurator可通过SPI接口对配置器进行扩展
    • PropertyConfigurator自带
    • DOMConfigurator自带
    • 自己扩展

# Appender(输出端)组件分类

名称 作用
ConsoleAppender 日志输出到控制台
FileAppender 日志输出到文件
DailyRollingFileAppender 日志输出到文件,并且每天输出到一个新的文件
RollingFileAppender 日志输出到文件,并指定大小,大小到达阈值后,将文件改名,并产生一个新文件
JDBCAppender 日志输出到数据库中

# Layouts(格式)占位符说明

形式 含义
%m 输出代码中指定的日志信息
%p 日志级别
%n 换行符
%r 应用启动到输出该信息耗费的毫秒数
%c 打印语句所属类的全名
%t 产生该日志的线程全名
%d 服务器当前时间
%l 日志发送的位置,包括类名、线程、及代码中的行数(【不推荐】影响性能)
%F 日志消息产生时,所在的文件名称(【不推荐】影响性能)
%L 代码中的行号(【不推荐】影响性能)
  • 最小宽度,最大宽度,对其方式配置
    形式 含义
    %5c 最小宽度5,小于5时默认情况下右对齐
    %-5c 最小宽度5,小于5时 ‘-’ 指定左对齐
    %.5c 最大宽度5,大于5时,将左边多出的字符截掉
    %20.30c 小于20补空格且右对齐,大于30就将左边超出的字符截掉

# 配置文件

public class LogManager {
    //xxx:源码中 已经不推荐使用
  static public final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";

  //xxx: 在 classpath配置,自动读取
  static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
  
  static {
      //xxx: 加载配置,略
    //xxx: 默认通过配置器: PropertyConfigurator
  }
}

# 配置文件示例

#xxx: 第一个参数配置 rootLogger的级别,第二个及以后的参数 配置Appender
log4j.rootLogger = trace,console

# 指定appender
#xxx: 指定的appender,可以指定 Threshold属性,配置输出级别 (注意,不能比rootLogger的级别低,否则以rootLogger为准)
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

# Commons-Logging

# 使用示例

<dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
</dependency>
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.BasicConfigurator;

/**
 * @author automannn
 * @Date 2022/8/31
 */
public class CommonsLoggingTest {
    public static void main(String[] args) {
        //xxx: 默认使用 JUL作为日志底层实现,当导入log4j的时候,自动进行转换适配
        Log log = LogFactory.getLog(CommonsLoggingTest.class);
        BasicConfigurator.configure();
        log.fatal("fatal msg");
        log.error("error msg");
        log.warn("warn msg");
        log.info("info msg");
        log.debug("debug msg");
        log.trace("trace msg");
    }
}

# 原理

final class LogAdapter {
  private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger";
  private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider";
  private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger";
  private static final String SLF4J_API = "org.slf4j.Logger";
  private static final LogAdapter.LogApi logApi;

  /*xxx: 检测日志环境,具有本身的优先级*/
  static {
    if (isPresent("org.apache.logging.log4j.spi.ExtendedLogger")) {
      if (isPresent("org.apache.logging.slf4j.SLF4JProvider") && isPresent("org.slf4j.spi.LocationAwareLogger")) {
        logApi = LogAdapter.LogApi.SLF4J_LAL;
      } else {
        logApi = LogAdapter.LogApi.LOG4J;
      }
    } else if (isPresent("org.slf4j.spi.LocationAwareLogger")) {
      /*xxx: slf4j第一优先级*/
      logApi = LogAdapter.LogApi.SLF4J_LAL;
    } else if (isPresent("org.slf4j.Logger")) {
      /*xxx: log4j第二优先级*/
      logApi = LogAdapter.LogApi.SLF4J;
    } else {
        /*xxx: jul第三优先级*/
      logApi = LogAdapter.LogApi.JUL;
    }

  }
}

# slf4j日志(slf4j, simple logging facade for java)

# 使用案例

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

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
</dependency>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author automannn
 * @Date 2022/8/31
 */
public class Slf4jLogTest {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Slf4jLogTest.class);

        logger.error("error msg");
        logger.warn("warn msg");
        logger.info("info msg"); //默认日志级别
        logger.debug("debug msg");
        logger.trace("trace msg");
    }
}

# 原理

public final class LoggerFactory {
    //xxx: 获取日志实现
  public static ILoggerFactory getILoggerFactory() {
    return StaticLoggerBinder.getSingleton().getLoggerFactory();
  }
}
//xxx: slf4j用什么个实现,主要取决于 import org.slf4j.impl.StaticLoggerBinder; 位于哪个包, logback,slf4j-simple,以及一些桥接包都有该实现
public class StaticLoggerBinder implements LoggerFactoryBinder {
  public ILoggerFactory getLoggerFactory() {
    return this.contextSelectorBinder.getContextSelector().getLoggerContext();
  }
}

存在多个slf4j实现时,不会报错,而是会选择其中一个作为实现

# logback日志(ch.qos)

# 使用案例

<!-- loback本身自带 lobback-core 以及 slf4j-api依赖 -->
<dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
</dependency>
/**
 * @author automannn
 * @Date 2022/8/31
 */
public class LogbackLogTest {
    public static void main(String[] args) {
        //使用方式同 slf4j,略
    }
}

# 配置文件(logback.xml)

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="1 seconds">
 
    <contextName>logback</contextName>
 
    <property name="log.path" value="F:\\logback.log" />
 
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- <filter class="com.example.logback.filter.MyFilter" /> -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
              <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>
 
    <appender name="file"
        class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern>
        </rollingPolicy>
 
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
    </appender>
 
    <root level="debug">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>
 
    <logger name="com.example.logback" level="warn" />
 
</configuration>

# 日志框架在常用框架中的使用

# lombok

  • @Slf4j使用 slf4j,需要依赖至少一个slf4j实现类
  • @Log4j 使用 log4j作为日志实现,直接使用即可
  • @Log4j2 使用 log4j2,直接使用即可

# springboot

默认使用logback进行实现

# springboot配置

logging.level.con.automannn = 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

# 自定义配置

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

日志框架 配置文件
Logback logback-spring.xml,logback.xml
Log4j2 log4j2-spring.xml, log4j2.xml
JUL logging.properties

# 切换至log4j2

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