Java logging 日志概述

日志可以帮助开发者理解和调试程序,日志数据可以很好地还原代码执行场景,便于问题的定位和分析。

本文将讨论 Java 中流行的日志框架 Log4j 、Log4j2 、Logback等。

日志框架引入的必要性

在编写程序时,使用System.out.println输出似乎是最简单、最容易的选择。无需将额外的库添加到类路径中,也无需进行其他配置。

但是使用System.out和System.err有一些局限性:

  • 文件日志:系统输出日志无法直接存储到文件,需要额外的工具支持。
  • 日志文件管理:无法控制日志文件的大小,导致文件不断增长,难以管理。
  • 日志级别:内置的日志级别有限,只有标准输出和错误输出。
  • 日志集成:将日志输出到日志分析系统(如Kafka Stream、Elasticsearch)存在困难。
  • 日志定制:自定义日志信息(如方法名、时间、线程号等)需要大量手动工作。
  • 异常栈跟踪:使用printStackTrace输出异常信息时,会产生大量冗余信息,消耗内存。

日志框架引入步骤

在项目内部启用日志记录遵循三个常见步骤:

  • 添加依赖:在项目的pom.xml文件中添加所需的日志框架依赖。
  • 配置:根据所选的日志框架进行配置,如Log4j2的log4j2.xml或Logback的logback.xml。
  • 编写日志代码:在Java代码中使用相应的日志API进行日志记录。

日志框架介绍

Log4J

Log4j是早期的Java日志框架,提供了基本的日志记录功能。随着时间的推移,Log4j已经逐渐被Log4j2所取代。

Log4j依赖库

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

log4j 最新版已经是2012年发布的,如果项目中还在使用应该尽快升级到 log4j2了。

Log4j配置

<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd" >
<log4j:configuration debug="false">

    <!--Console appender-->
    <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" 
              value="%d{yyyy-MM-dd HH:mm:ss} %p %m%n" />
        </layout>
    </appender>

    <root>
        <level value="INFO" />
        <appender-ref ref="stdout" />
    </root>

</log4j:configuration>

代码使用

import org.apache.log4j.Logger;

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

    public static void main(String[] args) {
        logger.debug("Debug log 日志");
        logger.info("Info log 日志");
        logger.error("Error log 日志");
    }
}
2024-03-16 21:32:54 INFO Info log 日志
2024-03-16 21:32:54 ERROR Error log 日志

Log4j2

Log4j2是Log4j的升级版本,引入了许多新特性,如异步日志记录支持。Log4j2的性能也比Log4j有显著提升。

Log4j2依赖库

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.23.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.23.1</version>
</dependency>

最新版本可以在mvn中央仓库查看https://mvnrepository.com

Log4j2配置

配置Log4j 2是基于主配置​​log4j2.xml文件,首先要配置的是Appender。

这些决定了日志消息将被路由到的位置,目的地可以是控制台Console、文件File、套接字Socket等。

<Configuration status="warn" name="mapull" packages="">
    <Appenders>
        <Console name="STDOUT" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %p %m%n"/>
        </Console>
    </Appenders>
</Configuration>

你可以为每个Appender设置一个名称;例如,使用名称CONSOLE而不是STDOUT。

请注意PatternLayout元素,这决定了消息的格式。
上面示例中,样式是根据pattern参数设置的,其中:

  • %d 确定日期格式
  • %p 日志级别输出
  • %m 记录消息的输出
  • %n 添加新行符号

有关样式的更多信息可以在官方Log4j 2 Layouts页面上找到。

最后,要启用一个(或多个)Appender,只需要将其添加到部分:

<Loggers> 
    <Root level="error">
        <AppenderRef ref="STDOUT"/>
    </Root>
</Loggers>

记录到文件

有时,我们需要将日志记录到文件,因此我们将 File Appender添加到配置中:

  <Appenders>
    <File name="MyFile" fileName="logs/app.log" append="true">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
    </File>
  </Appenders>
  • fileName 表示日志文件的文件名
  • append 参数的默认值为 true,这意味着默认情况下,File Appender将追加到现有文件而不是覆盖原始内容。

启用File Appender,您需要将其添加到部分:

<Root level="INFO">
    <AppenderRef ref="STDOUT" />
    <AppenderRef ref="MyFile"/>
</Root>

完整的配置文件 ​​log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="mapull" packages="">
  <Appenders>
    <File name="MyFile" fileName="logs/app.log" append="true">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
    </File>
    <Console name="STDOUT" target="SYSTEM_OUT">
        <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %p %m%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
        <AppenderRef ref="STDOUT" />
        <AppenderRef ref="MyFile"/>
    </Root>
  </Loggers>
</Configuration>

异步日志

如果想让 Log4j2 异步,需要将 LMAX Disruptor 库添加到pom.xml中。 LMAX Disruptor 是一个无锁线程间通信库。

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

使用 LMAX Disruptor以后,需要在配置中使用<asyncRoot>而不是<Root>

<AsyncRoot level="error">
    <AppenderRef ref="STDOUT" />
    <AppenderRef ref="MyFile"/>
</AsyncRoot>

编写日志代码

以下是一个简单的示例,演示了如何使用 Log4j 进行日志记录:

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class Log4jExample {

    private static Logger logger = LogManager.getLogger(Log4jExample.class);

    public static void main(String[] args) {
        logger.debug("Debug log 日志");
        logger.info("Info log 日志");
        logger.error("Error log 日志");
    }
}

运行后,应用程序会将以下消息记录到控制台和名为 app.log 的文件中:

2024-03-16 22:02:13 INFO Info log 日志
2024-03-16 22:02:13 ERROR Error log 日志

如果将根日志级别提升为ERROR:

    <Root level="ERROR">
        <AppenderRef ref="STDOUT" />
        <AppenderRef ref="MyFile"/>
    </Root>

输出将有所变化:

2024-03-16 22:05:36 ERROR Error log 日志

将日志基本调高,将导致低等级的日志信息丢失掉。

包级别配置

如果我们需要某个特定包中的日志输出有特殊要求,可以在log4j2.xml的<Root>之前添加以下部分:

<Logger name="com.mapull.log.demo" level="debug">
    <AppenderRef ref="stdout"/>
</Logger>

它将启用com.mapull.log.demo包的日志记录,输出将如下所示:

2024-03-16 22:15:28 DEBUG Debug log 日志
2024-03-16 22:15:28 INFO Info log 日志
2024-03-16 22:15:28 ERROR Error log 日志

Logback

Logback 是 Log4j 的改进版本,由开发 Log4j 的同一开发人员开发。

Logback 还具有比 Log4j 更多的功能,其中许多功能也被引入到 Log4j 2 中。

Logback依赖库

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

此依赖项将间接引入另外两个依赖项:logback-core和slf4j-api。

Logback配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
    <contextName>logback</contextName>
    <property name="log.path" value="logs"></property>
    <property name="Console_Pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%logger{50}] - %msg%n"/>

    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>${Console_Pattern}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="RollingFileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/info.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="RollingFileWarn" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/warn.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="RollingFileError" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/error.log</file>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--additivity:是否继承root节点,默认是true继承。默认情况下子Logger会继承父Logger的appender,
    也就是说子Logger会在父Logger的appender里输出。
    若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。-->
    <logger name="org.springframework" level="INFO" additivity="false">
        <appender-ref ref="Console"/>
        <appender-ref ref="RollingFileInfo"/>
    </logger>
    <logger name="org.mybatis" level="INFO"></logger>
    <Logger name="org.apache.catalina" level="info"/>
    <Logger name="org.apache.tomcat.util" level="info"/>
    <!-- 从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF-->
    <root level="ALL">
        <appender-ref ref="Console"/>
        <appender-ref ref="RollingFileWarn"/>
        <appender-ref ref="RollingFileError"/>
    </root>

    <!--生产环境:输出到文件-->
    <springProfile name="pro">
        <root level="INFO">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </springProfile>
</configuration>

Logback使用SLF4J作为接口,因此需要导入SLF4J的Logger和LoggerFactory。

SLF4J

SLF4J 为大多数 Java 日志框架提供了通用接口和抽象。它充当门面并提供标准化 API 用于访问日志记录框架的底层功能。

Logback 的功能就使用到了 SLF4J 的原生 API。

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

public class Log4jExample {

    private static Logger logger = LoggerFactory.getLogger(Log4jExample.class);

    public static void main(String[] args) {
        logger.debug("Debug log 日志");
        logger.info("Info log 日志");
        logger.error("Error log 日志");
    }
}

总结

日志框架对于Java应用程序的开发和维护至关重要。它们提供了灵活的日志级别控制、方便的日志文件管理、易于集成的日志分析功能以及丰富的日志定制选项。Log4j、Log4j2和Logback是Java社区中最流行的三个日志框架,它们各有特点和优势。开发者可以根据项目需求和个人偏好选择合适的日志框架。通过使用日志框架,可以显著提高代码的可读性和可维护性,同时也便于问题的排查和解决。

转载请注明出处:码谱记录 » Java logging 日志概述

相关推荐

  • 暂无文章