Java 日志演进:一文读懂主流框架

目录

一、引入

二、核心概念:日志门面与日志实现

三、日志框架的发展历程

[3.1 Log4j](#3.1 Log4j)

[3.1.1 日志级别](#3.1.1 日志级别)

[3.1.2 使用方式](#3.1.2 使用方式)

1)添加依赖

2)配置文件

3) 在代码中使用 在代码中使用)

[3.1.3 定位](#3.1.3 定位)

[3.2 JUL(简单了解)](#3.2 JUL(简单了解))

[3.2.1 日志级别](#3.2.1 日志级别)

[3.2.2 使用方式](#3.2.2 使用方式)

[3.2.3 定位](#3.2.3 定位)

[3.3 JCL(简单了解)](#3.3 JCL(简单了解))

[3.3.1 日志级别](#3.3.1 日志级别)

[3.3.2 使用方式](#3.3.2 使用方式)

1)导入依赖

2)代码中使用

[3.4 SLF4J(⭐)](#3.4 SLF4J(⭐))

[3.3.1 日志级别](#3.3.1 日志级别)

[3.3.2 使用方式](#3.3.2 使用方式)

[3.5 Logback-slf4j(⭐)](#3.5 Logback-slf4j(⭐))

[3.6 Log4j2-slf4j(⭐)](#3.6 Log4j2-slf4j(⭐))

[3.6.1 日志级别](#3.6.1 日志级别)

[3.6.2 使用方式](#3.6.2 使用方式)

1)配置文件

2)代码中使用

今日份冷笑话


本文主要向大家介绍下Java开发中一个很熟悉的伙伴--日志。
日志究竟是怎么延申发展起来的呢,它又为我们的日常开发究竟带来了多少便利?
下面我将通过日志发展历程顺序来逐一讲解这些日志的使用方式 ദ്ദി˶ー̀֊ー́ )✧

一、引入

💡假设你开发了一个简单的用户注册功能,并且在代码中通过硬编码打印了一些基础的语句信息。
这时用户反馈 "注册失败",你去看控制台,只显示 "保存失败",但不知道是数据库连接超时?还是手机号重复?完全无法定位问题。
于是聪明的你就想到了用 日志框架替代硬编码打印,并在注册代码里按级别加了日志,以后排查问题就能看到具体原因,甚至生产环境没有控制台,也能通过日志文件追溯到问题。

二、核心概念:日志门面与日志实现

在讨论具体技术之前,必须要理解现代Java日志体系的核心思想:" 门面模式"。它将日志API(接口)和日志(具体)实现分离。

  • 日志门面 :提供一套统一的API,你的应用程序代码只依赖这些接口。这样做的好处就是:即使将来需要更换底层的日志实现,你的业务代码也完全不需要修改。
  • 日志实现 :具体的日志库,负责实现前面提到的门面接口,完成实际的日志输出工作。
    可以看到日志框架这么设计,本质还是为了灵活和可维护。

三、日志框架的发展历程

3.1 Log4j

Log4j作为日志框架的第一阶段,这也是Java社区第一个广泛使用、功能完善的日志框架,不过这个1.x已经在2015年停止维护了。

3.1.1 日志级别

Log4j定义了以下级别,级别从低到高依此为:
all→trace→debug→INFO→WARN→ERROR→fatal→off
规则:Logger只输出 高于或等于其配置级别的日志。

比如,如果配置为 INFO,则会输出 INFO, WARN, ERROR, FATAL 级别的日志,而不会输出 DEBUG, TRACE

3.1.2 使用方式

log4j我就简单演示一下使用,因为这个目前已经不推荐使用了。

1)添加依赖
XML 复制代码
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
2)配置文件

log4j的核心就是配置,在配置中定义了日志输出的目的地和格式。

bash 复制代码
# 根日志器:级别DEBUG,输出到控制台和全局文件(所有未单独配置的日志都会走这里)
log4j.rootLogger = DEBUG, CONSOLE, GLOBAL_FILE

# ========================= 公共输出源配置 =========================
# 1. 控制台输出(所有日志共享)
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target = System.out
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

# 2. 全局文件输出(记录所有日志,含未单独配置的模块)
log4j.appender.GLOBAL_FILE = org.apache.log4j.RollingFileAppender
log4j.appender.GLOBAL_FILE.File = ./logs/global.log  # 全局日志文件
log4j.appender.GLOBAL_FILE.MaxFileSize = 10MB
log4j.appender.GLOBAL_FILE.MaxBackupIndex = 10
log4j.appender.GLOBAL_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.GLOBAL_FILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

# ========================= 模块专属日志配置 =========================
# 1. service模块:单独输出到service.log,不继承根日志的文件输出(避免重复)
# 定义service模块的日志器
log4j.logger.com.example.service = DEBUG, SERVICE_FILE
log4j.additivity.com.example.service = false  # 禁止继承根日志的输出源(只走自己的配置)

# 配置service模块的文件输出源
log4j.appender.SERVICE_FILE = org.apache.log4j.RollingFileAppender
log4j.appender.SERVICE_FILE.File = ./logs/service.log  # service模块专属日志
log4j.appender.SERVICE_FILE.MaxFileSize = 10MB
log4j.appender.SERVICE_FILE.MaxBackupIndex = 10
log4j.appender.SERVICE_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.SERVICE_FILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

# 2. dao模块:单独输出到dao.log,同样不继承根日志的文件输出
# 定义dao模块的日志器
log4j.logger.com.example.dao = DEBUG, DAO_FILE
log4j.additivity.com.example.dao = false  # 禁止继承根日志的输出源

# 配置dao模块的文件输出源
log4j.appender.DAO_FILE = org.apache.log4j.RollingFileAppender
log4j.appender.DAO_FILE.File = ./logs/dao.log  # dao模块专属日志
log4j.appender.DAO_FILE.MaxFileSize = 10MB
log4j.appender.DAO_FILE.MaxBackupIndex = 10
log4j.appender.DAO_FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.DAO_FILE.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
3) 在代码中使用
java 复制代码
// 注意:直接导入Log4j的类(使用Log4j自带的API)
import org.apache.log4j.Logger;

public class MyClass {
  // 1. 获取Logger实例
  private static final Logger logger = Logger.getLogger(MyClass.class);

  public void doSomething() {
    // 2. 使用不同级别记录日志
    logger.debug("这是一个Debug信息,用于详细调试.");
    logger.info("业务逻辑正常执行的信息.");
    logger.warn("这是一个警告,表示潜在问题.");
    logger.error("这是一个错误,但应用还能运行.", new Exception("错误示例"));
    logger.fatal("这是一个致命错误,可能导致应用退出.");
  }
}

3.1.3 定位

Log4j是具体的日志实现,在这个时候还没有门面的概念,所以应用程序代码是直接依赖Log4j的API。

3.2 JUL(简单了解)

全名叫Java Util Logging,企业中应用很少,简单了解就行。

3.2.1 日志级别

和Log4j类似,名字有些不一样。
ALL→FINEST (类似TRACE)→FINER→FINE (类似DEBUG)→CONFIG→INFO→WARNING (类似WARN)→SEVERE (类似ERROR)
规则都是一样的。

3.2.2 使用方式

使用方式和log4j极为相似,也可以通过配置文件进行管理,但是配置就不如Log4j灵活了,所以应用很少,这里就不演示了。

3.2.3 定位

是JDK自带的日志实现。

3.3 JCL(简单了解)

全名叫Jakarta Commons Logging,这个也是第一个主流的 日志门面
也就是说只依赖JCL的接口,JCL在运行时会通过类路径动态查找并绑定到具体的日志实现(就比如Log4j, JUL)

3.3.1 日志级别

日志级别基本上和log4j一样,这里就不重复罗列了。

3.3.2 使用方式

使用方式也很简单,就是导入依赖,然后直接在代码中引用。

1)导入依赖
XML 复制代码
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>
2)代码中使用
java 复制代码
// 注意:导入的是commons-logging的类
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class MyClass {
  // 获取Log实例
  private static final Log log = LogFactory.getLog(MyClass.class);

  public void doSomething() {
    // JCL的级别方法比较简单
    log.debug("Debug消息");
    log.info("Info消息");
    log.warn("Warn消息");
    log.error("Error消息");
    log.fatal("Fatal消息"); 
    
    // 记录异常
    log.error("业务处理出错", new RuntimeException("异常"));
  }
}

3.4 SLF4J(⭐)

slf4j定位也是一个日志门面,并且是比JCL性能更好的门面。

3.3.1 日志级别

SLF4J的级别与Log4j基本一致: TRACE→DEBUG→INFO→WARN→ERROR

3.3.2 使用方式

单说slf4j的使用的话,门面的使用无非就是导入依赖,代码中引用,所以slf4j的使用演示我会放到后面和logback和log4j2这两个日志实现一块使用。

3.5 Logback-slf4j(⭐)

Logback是SLF4J的 原生实现,是SLF4J的作者亲自设计,为了替代Log4j,性能更好,功能也更强(比如如自动热加载配置、更强大的Filter等)。
这边代码不再详细罗列,我会放到代码仓中,大家可以自行查看我主页的代码仓,或是直接访问这个链接: https://gitcode.com/m0_74977981/logDemo/tree/master/logsDemo
这里只说一点,logback自定义日志输出和log4j的语法不太一样,和log4j2倒是很相似。

3.6 Log4j2-slf4j(⭐)

Log4j2是对Log4j 1.x的彻底重写,它不仅是日志实现,也提供了自己的API。

3.6.1 日志级别

Log4j2的日志级别和slj4j/Logback一致:
TRACE→DEBUG→INFO→WARN→ERROR

3.6.2 使用方式

前面已经提到了,Log4j2提供了自己的API,所以这里理应是有两套使用方式的(使用自己原生的API以及使用其他门面),但是直接使用它原生的API会造成耦合,不推荐使用。
所以下面我将直接演示Log4j2-slf4j的使用。
使用很类似logback+slf4j:

1)配置文件
XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!--
  目标:
  - 使用 SLF4J API + Log4j2 实现
  - com.example.logsdemo.service1 -> logs/log1.log
  - com.example.logsdemo.service2 -> logs/log2.log
-->
<Configuration status="WARN">
    <Properties>
        <Property name="LOG_DIR">logs</Property>
        <Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %c{1.}.%M:%L - %m%ex{full}%n</Property>
    </Properties>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </Console>

        <!-- service1 独立文件 -->
        <RollingFile name="Log1File"
                     fileName="${LOG_DIR}/log1.log"
                     filePattern="${LOG_DIR}/log1-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>

        <!-- service2 独立文件 -->
        <RollingFile name="Log2File"
                     fileName="${LOG_DIR}/log2.log"
                     filePattern="${LOG_DIR}/log2-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <!-- 精确匹配包,关闭向上继承,避免写入到 Root 的其他 Appender -->
        <Logger name="com.example.logsdemo.service1" level="debug" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="Log1File"/>
        </Logger>

        <Logger name="com.example.logsdemo.service2" level="debug" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="Log2File"/>
        </Logger>

        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

如上配置,service1目录下的文件日志会输出到log1.log中,service2目录下的文件日志会输出到log2.log中。

2)代码中使用


可以看见:

今日份冷笑话

有一天,Java 程序员去咖啡店点单,
服务员问:"您喝什么?"
程序员答:"给我来一杯 Java。"
服务员愣了一下:"先生,我们这儿只有 爪哇 咖啡,没有 加瓦 咖啡。"
程序员叹了口气:"唉,原来你们还没升级到 JDK( 咖啡开发工具包)......"

相关推荐
我叫张小白。16 分钟前
Spring Boot拦截器详解:实现统一的JWT认证
java·spring boot·web·jwt·拦截器·interceptor
Gerardisite2 小时前
如何在微信个人号开发中有效管理API接口?
java·开发语言·python·微信·php
闲人编程3 小时前
Python的导入系统:模块查找、加载和缓存机制
java·python·缓存·加载器·codecapsule·查找器
故渊ZY3 小时前
Java 代理模式:从原理到实战的全方位解析
java·开发语言·架构
匿者 衍3 小时前
POI读取 excel 嵌入式图片(支持wps 和 office)
java·excel
一个尚在学习的计算机小白4 小时前
java集合
java·开发语言
IUGEI4 小时前
synchronized的工作机制是怎样的?深入解析synchronized底层原理
java·开发语言·后端·c#
q***13614 小时前
Windows操作系统部署Tomcat详细讲解
java·windows·tomcat
z***I3944 小时前
Java桌面应用案例
java·开发语言
r***12384 小时前
SpringBoot最佳实践之 - 使用AOP记录操作日志
java·spring boot·后端