本文目录导读:

我将为您提供一个完整的Java日志实操案例,涵盖主流日志框架的使用。
项目结构
java-logging-demo/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/logging/
│ │ │ ├── MainApp.java
│ │ │ └── service/
│ │ │ └── UserService.java
│ │ └── resources/
│ │ ├── log4j2.xml
│ │ ├── logback.xml
│ │ └── logging.properties
│ └── test/
│ └── java/
│ └── com/example/logging/
│ └── LogTest.java
Maven依赖配置(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>java-logging-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- SLF4J核心API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<!-- Logback实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
<!-- Log4j2实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.20.0</version>
</dependency>
<!-- 桥接包(将其他日志框架转向SLF4J) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>2.0.9</version>
</dependency>
<!-- Lombok(可选,用于简化日志声明) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Logback配置(logback.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 定义属性 -->
<property name="LOG_PATH" value="logs"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 滚动文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天滚动 -->
<fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 保留30天 -->
<maxHistory>30</maxHistory>
<!-- 每个日志文件最大10MB -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 错误日志单独输出 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/error.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>90</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 异步日志(提升性能) -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志,默认如果队列满会丢弃 -->
<discardingThreshold>0</discardingThreshold>
<!-- 队列大小 -->
<queueSize>1024</queueSize>
<appender-ref ref="FILE"/>
</appender>
<!-- 设置日志级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<!-- 特定包设置不同的日志级别 -->
<logger name="com.example.logging" level="DEBUG"/>
<logger name="org.springframework" level="WARN"/>
</configuration>
Log4j2配置(log4j2.xml)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="LOG_PATH">logs</Property>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %c{1.} - %msg%n</Property>
</Properties>
<Appenders>
<!-- 控制台输出 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<!-- 滚动文件输出 -->
<RollingFile name="RollingFile"
fileName="${LOG_PATH}/app.log"
filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<!-- 异步日志 -->
<Async name="Async">
<AppenderRef ref="RollingFile"/>
</Async>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="Async"/>
</Root>
<Logger name="com.example.logging" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Logger>
</Loggers>
</Configuration>
Java日志实现
1 主应用类
package com.example.logging;
import lombok.extern.slf4j.Slf4j;
import com.example.logging.service.UserService;
@Slf4j // Lombok自动生成log对象
public class MainApp {
// 不使用Lombok的传统写法
// private static final Logger log = LoggerFactory.getLogger(MainApp.class);
public static void main(String[] args) {
log.info("应用程序启动...");
// 测试不同日志级别
log.debug("调试信息:参数初始化完成");
log.info("业务日志:用户登录成功");
log.warn("警告信息:磁盘空间不足");
log.error("错误信息:数据库连接失败", new RuntimeException("连接超时"));
// 参数化日志(避免字符串拼接)
String username = "张三";
int age = 25;
log.info("用户信息:姓名={}, 年龄={}", username, age);
// 创建服务对象并调用
UserService userService = new UserService();
userService.createUser("张三");
log.info("应用程序正常关闭");
}
}
2 业务服务类
package com.example.logging.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public void createUser(String username) {
log.debug("开始创建用户,用户名:{}", username);
try {
// 模拟业务逻辑
validateUsername(username);
saveToDatabase(username);
sendNotification(username);
log.info("用户创建成功:{}", username);
} catch (Exception e) {
log.error("创建用户失败:{}", username, e);
throw new RuntimeException("创建用户失败", e);
}
}
private void validateUsername(String username) {
if (username == null || username.trim().isEmpty()) {
log.warn("用户名为空,验证失败");
throw new IllegalArgumentException("用户名不能为空");
}
log.debug("用户名验证通过");
}
private void saveToDatabase(String username) {
// 模拟数据库操作
log.debug("保存用户信息到数据库:{}", username);
// 模拟耗时操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.debug("数据库保存完成");
}
private void sendNotification(String username) {
log.debug("发送用户创建通知,用户:{}", username);
// 模拟发送通知
log.debug("通知发送完成");
}
}
测试类
package com.example.logging;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
public class LogTest {
private static final Logger log = LoggerFactory.getLogger(LogTest.class);
@Test
public void testLogLevels() {
log.trace("这是TRACE级别日志");
log.debug("这是DEBUG级别日志");
log.info("这是INFO级别日志");
log.warn("这是WARN级别日志");
log.error("这是ERROR级别日志");
}
@Test
public void testDynamicLogLevel() {
// 动态修改日志级别
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(
ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME
);
rootLogger.setLevel(Level.DEBUG);
log.debug("日志级别已更改为DEBUG");
rootLogger.setLevel(Level.ERROR);
log.debug("这条日志不会输出"); // 不会输出
log.error("只能输出ERROR级别日志");
}
@Test
public void testMdcLogging() {
// MDC (Mapped Diagnostic Context) 使用
org.slf4j.MDC.put("userId", "1001");
org.slf4j.MDC.put("requestId", "REQ-001");
log.info("用户请求处理中...");
// 使用时需要添加MDC转换器到日志配置
// <pattern>%d{...} [%X{userId}] [%X{requestId}] %msg%n</pattern>
org.slf4j.MDC.clear();
}
}
日志最佳实践示例
package com.example.logging;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingBestPractices {
private static final Logger log = LoggerFactory.getLogger(LoggingBestPractices.class);
// 1. 使用参数化日志(避免字符串拼接)
public void parameterizedLogging(String username, int age) {
// 推荐:使用占位符
log.info("用户登录:{},年龄:{}", username, age);
// 不推荐:字符串拼接(即使日志级别不输出,也会执行拼接)
// log.info("用户登录:" + username + ",年龄:" + age);
}
// 2. 条件日志(避免不必要的对象创建)
public void conditionalLogging(ComplexObject obj) {
// 推荐:使用占位符,SLF4J内部会判断
log.debug("复杂对象处理:{}", obj);
// 或者使用Lambda表达式(SLF4J 2.0+)
log.atDebug()
.setMessage("复杂对象处理:{}")
.addArgument(obj)
.log();
}
// 3. 异常日志完整记录
public void exceptionLogging() {
try {
// 业务代码
throw new RuntimeException("业务异常");
} catch (Exception e) {
// 推荐:记录完整的异常堆栈
log.error("业务处理异常:{}", e.getMessage(), e);
// 不推荐:只记录异常消息,丢失堆栈信息
// log.error("业务处理异常:" + e.getMessage());
}
}
// 4. 日志级别使用指南
public void logLevelGuide() {
// TRACE: 详细的执行流程,仅在开发调试时使用
log.trace("进入方法,参数:{}", getParameters());
// DEBUG: 调试信息,分析问题使用
log.debug("数据库查询结果:{},影响行数:{}", getResult(), getRowCount());
// INFO: 关键业务节点
log.info("用户[{}]下单成功,订单号:{}", getUserId(), getOrderId());
// WARN: 潜在问题
log.warn("磁盘使用率达到{}%,请关注", getDiskUsage());
// ERROR: 错误事件,需要人工处理
log.error("支付服务调用失败,错误码:{}", getErrorCode());
}
// 辅助方法
private String getParameters() { return "test"; }
private String getResult() { return "success"; }
private int getRowCount() { return 10; }
private String getUserId() { return "1001"; }
private String getOrderId() { return "ORDER-001"; }
private double getDiskUsage() { return 85.5; }
private String getErrorCode() { return "E1001"; }
static class ComplexObject {
@Override
public String toString() {
return "ComplexObject{}";
}
}
}
运行说明
- 选择日志框架:移除不需要的依赖,保留一个实现即可
- 配置文件位置:放在
resources目录下 - 运行主类:执行
MainApp.main()方法 - 查看日志:
- 控制台实时输出
logs/目录下生成日志文件- 错误日志单独记录在
error.log
常见问题
- 日志不输出:检查配置文件位置和日志级别设置
- 性能问题:使用异步日志(AsyncAppender)
- 日志混乱:统一使用SLF4J门面,避免混用不同框架
- 重复日志:检查
additivity属性配置
这个案例涵盖了Java日志的主流实践,您可以根据实际需求选择使用。