Java Java日志案例如何实操

wen java案例 25

本文目录导读:

Java Java日志案例如何实操

  1. 项目结构
  2. Maven依赖配置(pom.xml)
  3. Logback配置(logback.xml)
  4. Log4j2配置(log4j2.xml)
  5. Java日志实现
  6. 测试类
  7. 日志最佳实践示例
  8. 运行说明
  9. 常见问题

我将为您提供一个完整的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{}";
        }
    }
}

运行说明

  1. 选择日志框架:移除不需要的依赖,保留一个实现即可
  2. 配置文件位置:放在resources目录下
  3. 运行主类:执行MainApp.main()方法
  4. 查看日志
    • 控制台实时输出
    • logs/目录下生成日志文件
    • 错误日志单独记录在error.log

常见问题

  1. 日志不输出:检查配置文件位置和日志级别设置
  2. 性能问题:使用异步日志(AsyncAppender)
  3. 日志混乱:统一使用SLF4J门面,避免混用不同框架
  4. 重复日志:检查additivity属性配置

这个案例涵盖了Java日志的主流实践,您可以根据实际需求选择使用。

抱歉,评论功能暂时关闭!