Java 日志

2023-02-28 23:09:43 浏览数 (2)

Java 日志

  • 日志门面:提供统一的日志输出接口
  • 日志实现:具体实现日志输出的代码

日志门面与实现框架

  • 使用 日志门面 日志实现框架 的方式,是为了:低耦合,日志的实现与业务代码通过 日志门面连接,在后续修改日志实现时,无需更改业务代码。
  • 这是 门面设计模式(外观设计模式)的典型应用。

日志门面

  • SLF4j(Simple Logging Facade For Java):一个为 Java 程序提供的统一日志输出接口,就是一个接口,
  • JCL(Jaka Commons Logging, Apache Commons Logging):Apache 提供的一个日志门面,提供统一的对外接口。

日志实现框架

  • JUL(Java util Logging)Java 原生的日志框架,使用时不需要引用第三方类库,使用方便
代码语言:txt复制
- 7 个日志级别(从高到低):**SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST**。
代码语言:txt复制
- 同时还有 **OFF、ALL 两个特别的日志级别,用来 关闭/打开 所有的日志**。
  • log4jApache 的一个开源项目
代码语言:txt复制
- 7 个日志级别(从高到低):**OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL**。

日志级别

日志介绍

OFF

最高日志级别,关闭所有日志

FATAL

将会导致引用程序退出的错误

ERROR

发生错误事件,但仍不影响系统的继续运行

WARN

警告,存在潜在的错误

INFO

一般用在粗粒度级别上,强调应用程序的运行全程

DEBUG

一般用在细粒度级别上,用于调试应用程序

ALL

最低日志级别,打开所有日志

  • log4j2log4j 的升级版,参考了 logback 的设计,同时进行了问题修复
代码语言:txt复制
- **异常优化**:提供了一些异常处理机制,来解决在 logback 中,应用无法感知到 Appener 异常。
代码语言:txt复制
- **性能提升**:相较于 log4j 和 logback,性能都有明显的提升。
代码语言:txt复制
- **自动重载配置**:参考 logback 的参数修改自动更新机制,提供自动刷新参数的设置。
代码语言:txt复制
- **无垃圾机制**:可以使用其设计的一套无垃圾机制(对象重用、内存缓冲),避免频繁的日志记录导致 JVM gc 压力过大。
  • logbackSpringBoot 默认的日志框架
代码语言:txt复制
- 由三个模块组成:
代码语言:txt复制
    - logback-core:logback 核心包,开发人员可以以次为基础搭建自身模块。
代码语言:txt复制
    - logback-classic:**logback 对于 SLF4j 的实现,其中依赖了 logback-core 包**。
代码语言:txt复制
    - logback-access:集成 Servlet 容器,实现 HTTP 访问日志的功能。
代码语言:txt复制
- **可以输出日志到文件、数据库、控制台中,还可以将日志文件进行压缩,功能很丰富**。
代码语言:txt复制
- 日志级别(从高到低):**FATAL、ERROR、WARNING、INFO、DEBUG、TRACE**。

日志级别

日志介绍

TRACE

在线调试,默认不输出到控制台和文件

DEBUG

在线调试、终端查看,默认输出到控制台,用于开发者查看日志流水

INFO

报告程序进度、查看程序状态,用于跟踪程序进展

WARNING

警告,程序出现错误,但是程序可以恢复,程序仍是正常状态

ERROR

错误,程序发生错误后还可以运行,但是程序极有可能处于非正常状态,功能可能无法全部完成

FATAL

致命错误,程序必须马上终止

总结

  • 日志门面和实现框架的面世时间(从早到晚):Log4j -> JUL -> JCL -> SLF4j -> Logback -> Log4j2
  • JCL 门面优先寻找 Log4j 实现,退而求次则是 JUL 实现,最后才会使用内部提供的 SimpleLog 实现
  • 而,SLF4j 门面是作为一个纯粹的日志门面,提供了 SLF4j 桥接器将 SLF4j 桥接到 log4j、JUL 实现去做日志输出
  • 后续 log4j 无法满足高性能要求后,SLF4j 制作者根据 SLF4j 接口写出了 logback 日志实现框架。
  • log4j2 是 Apache 全面借鉴 SLF4j Logback 后推出的,添加了很多新的特性,还做了分离式设计。
  • 推荐使用 SLF4j logback 的方式去做 Java 的日志输出
代码语言:txt复制
- 优点一:**logback 中实现 SLF4j 门面,在 Java 程序中直接引入 logback-classic 的依赖即可**。
代码语言:txt复制
- 优点二:**SpringBoot 使用 logback 作为默认的日志实现**,在 SpringBoot 项目中可以直接使用。

SLF4j Logback 的实现

第一步:添加配置文件

  • Logback 框架可以自动识别 */classes/ 下的 logback.xml 文件。
代码语言:txt复制
- 在 SpringBoot 框架下,Logback 框架还可以自动识别 */classes/ 下的 ``logback-spring.xml`` 文件。
  • 同样,Log4j/Log4j2 框架可以自动识别 */classes/ 下的 log4j.xml/log4j2.xml 文件。
代码语言:txt复制
- 在 SpringBoot 框架下,Log4j2 框架还可以自动识别 */classes/ 下的 ``log4j2-spring.xml`` 文件。
  • JUL 框架可以自动识别 */classes/ 下的 logging.properties 文件。
  • 将配置文件放在 src/main/resources 下,项目构建时,文件就会加载到 */classes/ 下了。
logback.xml
代码语言:html复制
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="10000">
    <!-- scan 属性:当此属性设置为 true 时,配置文件如果发生改变,将会被重新加载,默认值为 true -->
    <!--
        scanPeriod属性:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
        当 scan 为 true 时,此属性生效。默认的时间间隔为 1 分钟。
    -->
    <!-- debug 属性:当此属性设置为 true 时,将打印出 logback 内部日志信息,实时查看 logback 运行状态。默认值为 false。 -->

    <!--
          日志格式设置标识符:
          %n:换行符;
          %level:日志级别;
          %-5level:日志级别(使用5个字符,并靠左对齐);
          %msg、%m:日志消息(简写是:%m);
          %logger、%M:日志输出者(就是哪个类做这个日志输出,这个应用是 Log 类);
          %c:日志输出者输出日志时使用的方法;
          %L:日志输出者输出日志时使用的方法中具体的行数;
          %d{yyyy-MM-dd HH:mm:ss}:时间,大括号内的是时间格式,默认格式是 2023-02-27 15:15:01,877;
          %thread:输出日志的进程名字;
      -->

    <!-- 日志 输出格式 1 -->
    <property name="layout1" value="[%level] -- %m -- [%d{yyyy-MM-dd HH:mm:ss}] %c %M %L [%thread] %n"/>

    <!-- 日志 输出格式 2 -->
    <property name="layout2"
              value="[%-5level] - %d{yyyy-MM-dd HH:mm:ss} - %c:%M:%L %n[%-5level] - [%thread] - %msg %n%n"/>

    <!-- 输出格式 3(输出到 *.html 文件,不需要间隔) -->
    <property name="layout3"
              value="%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%msg"/>

    <!-- 文件输出时,文件的路径 -->
    <property name="filePath" value="D:/file/projects/files/Java/journal/logback/"/>

    <!-- 输出颜色: 红 -->
    <property name="red" value="System.err"/>
    <!-- 输出颜色: 黑 -->
    <property name="black" value="System.out"/>

    <!--  每天的日期 String 串,用来生成文件夹  -->
    <timestamp key="datetime" datePattern="yyyy-MM-dd"/>

    <!--
          配置 输出控制器:consoleAppender,控制台输出
      -->
    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 颜色 -->
        <target>
            ${red}
        </target>
        <!-- 输出格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                ${layout1}
            </pattern>
        </encoder>
    </appender>

    <!--
        配置 输出控制器:consoleAppender1,控制台输出
    -->
    <appender name="consoleAppender1" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 颜色 -->
        <target>
            ${red}
        </target>
        <!-- 输出格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                ${layout2}
            </pattern>
            <!-- 设置字符集 -->
            <charset>
                UTF-8
            </charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        配置 输出控制器:consoleFilterAppender,控制台输出
        在 consoleAppender 的基础上 添加 过滤器
    -->
    <appender name="consoleFilterAppender" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 颜色 -->
        <target>
            <!-- ${red} -->
            ${black}
        </target>
        <!-- 输出格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                ${layout2}
            </pattern>
        </encoder>
        <!-- 配置过滤器 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 设置 级别 -->
            <level>DEBUG</level>

            <!--
                功能是:仅记录 设定好的级别的日志,可以用来设置 不同的日志输出到不同的 日志文件中;
                当然,将配置值进行 对调后,就可以反向屏蔽(屏蔽指定级别的日志)
            -->

            <!-- 设置 级别 与 设置的级别 匹配时 就 打印 -->
            <onMatch>ACCEPT</onMatch>
            <!-- 设置 级别 与 设置的级别 不匹配时 就 屏蔽 -->
            <onMismatch>DENY</onMismatch>
        </filter>

    </appender>

    <!--
        配置 输出控制器:htmlFileAppender,输出到 .html 文件
    -->
    <appender name="htmlFileAppender" class="ch.qos.logback.core.FileAppender">
        <!-- 文件位置 -->
        <file>
            ${filePath}/${datetime}/logback-html.html
        </file>
        <!-- 输出格式 -->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>
                    ${layout3}
                </pattern>
            </layout>
        </encoder>
    </appender>

    <!--
        配置 输出控制器:fileAppender,输出到 .log 文件
    -->
    <appender name="fileAppender" class="ch.qos.logback.core.FileAppender">
        <!-- 文件位置 -->
        <file>
            ${filePath}/${datetime}/logback-file.log
        </file>
        <!-- 输出格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                ${layout2}
            </pattern>
        </encoder>
    </appender>

    <!--
            配置 输出控制器:rollingFileAppender,输出到 .log 文件,可以 拆分 和 压缩
    -->
    <appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--
            文件位置
            ${filePath}:是文件存放目录路径,即:D:/file/projects/files/Java/journal/logback/
            ${datetime}:是在文件存放目录路径下的文件夹名称,即:yyyy-MM-dd
            logback-rollingFile.log:文件名(包括后缀)
        -->
        <file>
            ${filePath}/${datetime}/logback-rollingFile.log
        </file>
        <!-- 输出格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                ${layout2}
            </pattern>
            <!-- 设置字符集 -->
            <charset>
                UTF-8
            </charset>
        </encoder>
        <!-- 指定拆分规则 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 默认格式:gz,-->

            <!-- 声明文件名:按照 时间和格式 来 -->
            <fileNamePattern>
                <!-- %d{yyyy-MM-dd}是时间,%i 是防止同一天的文件重名(而 压缩包 有 *.gz、*.zip 等等) -->
                ${filePath}/${datetime}/logback-rollingFile%i.%d{yyyy-MM-dd}.log.gz
            </fileNamePattern>

            <!-- 单个文件最大的的大小:按照 文件大小 来拆分文件 -->
            <maxFileSize>
                10MB
            </maxFileSize>

            <!-- 文件数目(单位:个):大于 文件数目后 将会覆盖更早的日志压缩文件(不建议添加) -->
            <!--            <minIndex>1</minIndex>-->
            <!--            <maxIndex>3</maxIndex>-->

            <!-- 文件保存最长时间(单位:天):过早的日志压缩文件将会删除 -->
            <maxHistory>30</maxHistory>

        </rollingPolicy>
    </appender>

    <!-- 配置异步 appender-->
    <appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 导入实际的 Appender -->
        <appender-ref ref="consoleAppender"/>
        <!--
            注意:这两个属性不能乱配置
                1、<discardingThreshold></discardingThreshold>
                阈值,
                当队列剩余容量小于阈值时,trace debug info 这三个级别的日志将被抛弃,
                默认值是:-1
                2、<queueSize></queueSize>
                队列的深度
                默认值是:256
        -->
    </appender>

    <!--
        配置 日志记录器:root Logger
    -->
    <root level="ALL">
        <!-- 引入 Appender -->
        <appender-ref ref="asyncAppender"/>
        <appender-ref ref="fileAppender"/>
    </root>

    <!-- 自定义 Logger -->
    <logger name="com.domain.LogTool" level="trace" additivity="false">
        <!--
            additivity="false"  表示 不继承 rootLogger
        -->
        <!-- 引入 Appender -->
        <appender-ref ref="consoleAppender1"/>
        <appender-ref ref="htmlFileAppender"/>
        <appender-ref ref="rollingFileAppender"/>
    </logger>

</configuration>
  • 注意:自定义的 Logger 中 name="com.domain.LogTool",那么 LoggerFactory.getLogger(LogTool.class); 中传入 LogTool.java 时,才会使用自定义的 Logger
  • 其他情况都是使用默认的 Logger(RootLogger)
  • 当然,自定义的 Logger 中 name="com.domain" 时,LoggerFactory.getLogger(LogTool.class); 中传入 com.domain 内的类 时,也会使用自定义的 Logger

LogTool.java 工具类(非必要)

代码语言:java复制
public class LogTool {

    private static Logger LOGGER = LoggerFactory.getLogger(Log.class);

    /**
     * 添加自定义的 Logger 包下的 类,使用自定义的 Logger
     *
     * @param c   类的 Class 对象
     * @param <T> 泛型
     */
    public static <T> void setClassC(Class<T> c) {
        if (c != null) {
            LOGGER = LoggerFactory.getLogger(c);
        }
    }

    /**
     * 使用自定义的 Logger
     *
     * @param LOGGER Logger
     */
    public static void setLOGGER(Logger LOGGER) {
        Log.LOGGER = LOGGER;
    }

    /**
     * 输出 日志信息(类对象版)
     *
     * @param object   类对象
     * @param logLevel 日志级别
     */
    public static void outputLog(Object object, LogLevel logLevel) {
        switch (logLevel) {
            case Trace:
                LOGGER.trace("追踪 消息 - {}", object);
                break;
            case Debug:
                LOGGER.debug("详细 消息 - {}", object);
                break;
            case Info:
                LOGGER.info("关键 消息 - {}", object);
                break;
            case Warn:
                LOGGER.warn("警告 消息 - {}", object);
                break;
            case Error:
                LOGGER.error("错误 消息 - {}", object);
                break;
            default:
                break;
        }
    }

    /**
     * 输出 日志信息(信息版)
     *
     * @param message  消息
     * @param logLevel 日志级别
     */
    public static void outputLog(String message, LogLevel logLevel) {
        switch (logLevel) {
            case Trace:
                LOGGER.trace("追踪 消息 - { "   message   " }");
                break;
            case Debug:
                LOGGER.debug("详细 消息 - { "   message   " }");
                break;
            case Info:
                LOGGER.info("关键 消息 - { "   message   " }");
                break;
            case Warn:
                LOGGER.warn("警告 消息 - { "   message   " }");
                break;
            case Error:
                LOGGER.error("错误 消息 - { "   message   " }");
                break;
            default:
                break;
        }
    }


    /**
     * Log 级别 枚举
     */
    public enum LogLevel {
        /**
         * 追踪
         */
        Trace,
        /**
         * 调试、详细
         */
        Debug,
        /**
         * 关键、消息
         */
        Info,
        /**
         * 警告
         */
        Warn,
        /**
         * 错误
         */
        Error
    }

}

依赖导入

Java 程序
  • Logback-classic 依赖包含了 SLF4j 和 Logback-core 的依赖,导入 Logback-classic 一个就可以了。
代码语言:html复制
<dependencies>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>
SpringBoot 项目
  • spring-boot-starter 依赖中包含了 SLF4j 和 Logback,无需导入,当然也可以导入新的 logback 覆盖默认的版本。
代码语言:html复制
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.4.5</version>
    </dependency>

    <!--Spring boot Web容器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.4.5</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>2.4.5</version>
        <scope>test</scope>
    </dependency>
</dependencies>

测试

代码语言:html复制
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>RELEASE</version>
        <scope>test</scope>
    </dependency>
</dependencies>
代码语言:java复制
public class LogbackTest {

    /**
     * 测试 工具类 Log
     */
    @Test
    public void test() {

        /**
         * root Logger
         */
        Log.setLOGGER(LoggerFactory.getLogger(LogbackTest.class));
        for (int i = 0; i < 100; i  ) {
            Log.outputLog("trace", Log.LogLevel.Trace);
            Log.outputLog("debug", Log.LogLevel.Debug);
            Log.outputLog("info", Log.LogLevel.Info);
            Log.outputLog("warn", Log.LogLevel.Warn);
            Log.outputLog("error", Log.LogLevel.Error);
        }
    }

    /**
     * 测试 工具类 Log
     */
    @Test
    public void test1() {

        /**
         * 自定义 Logger
         */
        for (int i = 0; i < 100; i  ) {
            Log.outputLog("trace", Log.LogLevel.Trace);
            Log.outputLog("debug", Log.LogLevel.Debug);
            Log.outputLog("info", Log.LogLevel.Info);
            Log.outputLog("warn", Log.LogLevel.Warn);
            Log.outputLog("error", Log.LogLevel.Error);
        }
    }
}

其他的日志门面与实现框架

  • 第二建议使用的日志框架是:SLF4j Log4j2。
  • 相应的源码可以到 Gitee 上查看,地址:LJM/journal

0 人点赞