系统讲解Java如何优雅地打印日志

 更新时间:2026年05月30日 10:13:50   作者:xiaoyu❅  
本文将介绍如何优雅地打印日志,告别 System.out.println,使用专业的日志框架(Logback/Log4j2)来管理日志,让你的日志更专业、更规范、更易于排查问题

前言

“为什么生产环境的代码还在用 System.out.println?”、“日志格式混乱,没有时间戳,不知道是谁打印的”、“日志级别使用不当,错误信息混在普通信息中”。

这些都是开发中常见的日志问题。本文将介绍如何优雅地打印日志,告别 System.out.println,使用专业的日志框架(Logback/Log4j2)来管理日志,让你的日志更专业、更规范、更易于排查问题。

一、为什么要使用日志框架

1.1 System.out.println 的问题

System.out.println 的主要问题:

1.性能问题

  • 同步输出,会阻塞线程
  • 无法关闭,即使生产环境也会输出
  • 无法按级别控制输出

2.功能缺失

  • 没有日志级别(DEBUG、INFO、WARN、ERROR)
  • 没有时间戳、线程信息、类名等关键信息
  • 没有上下文信息

3.管理困难

  • 无法按级别过滤日志
  • 无法按日期分割日志文件
  • 无法分类存储不同类型的日志
  • 无法远程传输日志

1.2 日志框架的优势

日志框架的优势:

1.性能优化

  • 支持异步输出,不阻塞主线程
  • 支持日志级别控制,生产环境只输出WARN和ERROR
  • 支持按包路径控制日志级别

2.功能丰富

  • 支持多种日志级别(DEBUG、INFO、WARN、ERROR)
  • 支持自定义格式(时间、线程、类名、行号等)
  • 支持MDC(Mapped Diagnostic Context)上下文信息

3.管理便捷

  • 支持按日期、大小分割日志文件
  • 支持按类型分类存储日志
  • 支持异步写入数据库或消息队列
  • 支持结构化日志(JSON格式)

二、日志级别

2.1 日志级别定义

/**
 * 日志级别从低到高
 * TRACE < DEBUG < INFO < WARN < ERROR
 */

// TRACE:追踪信息,最详细的日志信息
// 使用场景:调试时追踪程序执行流程
logger.trace("用户登录,用户ID: {}", userId);

// DEBUG:调试信息,开发调试时使用
// 使用场景:开发阶段,记录变量值、方法调用等
logger.debug("查询用户,用户ID: {}", userId);

// INFO:重要信息,记录关键业务流程
// 使用场景:记录重要操作、系统状态等
logger.info("用户创建成功,用户ID: {}", userId);

// WARN:警告信息,潜在的问题
// 使用场景:参数校验失败、配置警告等
logger.warn("用户余额不足,用户ID: {}, 余额: {}", userId, balance);

// ERROR:错误信息,需要立即处理
// 使用场景:异常捕获、系统错误等
logger.error("订单创建失败,用户ID: {}", userId, exception);

2.2 日志级别使用原则

使用建议:

级别使用场景生产环境
TRACE追踪详细执行流程关闭
DEBUG调试信息、变量值关闭
INFO重要操作、业务流程开启
WARN警告、潜在问题开启
ERROR错误、异常开启

三、Logback 配置

3.1 Maven依赖

<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot Starter Web (已包含logback) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Logback Classic -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.14</version>
    </dependency>
    <!-- Logback Access -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-access</artifactId>
        <version>1.4.14</version>
    </dependency>
</dependencies>

3.2 基础配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义日志输出格式 -->
    <property name="LOG_PATTERN" 
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
    <!-- 定义日志文件路径 -->
    <property name="LOG_PATH" value="logs"/>
    <!-- 控制台输出 -->
    <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}/app.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 滚动策略:按日期滚动 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    <!-- 错误日志单独输出 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/error.log</file>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    <!-- 异步日志 -->
    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="FILE"/>
        <queueSize>512</queueSize>
        <discardingThreshold>0</discardingThreshold>
    </appender>
    <!-- 根日志器 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="ASYNC_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
</configuration>

3.3 完整配置(推荐)

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 定义常量 -->
    <property name="LOG_HOME" value="logs"/>
    <property name="APP_NAME" value="myapp"/>
    <!-- 日志格式 -->
    <property name="CONSOLE_LOG_PATTERN" 
              value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
    <property name="FILE_LOG_PATTERN" 
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- 文件输出 - 所有日志 -->
    <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${APP_NAME}-all.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/${APP_NAME}-all.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <!-- 文件输出 - 错误日志 -->
    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${APP_NAME}-error.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/${APP_NAME}-error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <maxHistory>60</maxHistory>
            <totalSizeCap>5GB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <!-- 异步日志 - 提升性能 -->
    <appender name="ASYNC_FILE_ALL" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="FILE_ALL"/>
        <queueSize>1024</queueSize>
        <discardingThreshold>0</discardingThreshold>
    </appender>
    <appender name="ASYNC_FILE_ERROR" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="FILE_ERROR"/>
        <queueSize>512</queueSize>
        <discardingThreshold>0</discardingThreshold>
    </appender>
    <!-- 开发环境 -->
    <springProfile name="dev">
        <logger name="com.example" level="DEBUG"/>
        <root level="DEBUG">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="ASYNC_FILE_ALL"/>
            <appender-ref ref="ASYNC_FILE_ERROR"/>
        </root>
    </springProfile>
    <!-- 测试环境 -->
    <springProfile name="test">
        <logger name="com.example" level="INFO"/>
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="ASYNC_FILE_ALL"/>
            <appender-ref ref="ASYNC_FILE_ERROR"/>
        </root>
    </springProfile>
    <!-- 生产环境 -->
    <springProfile name="prod">
        <logger name="com.example" level="INFO"/>
        <root level="INFO">
            <appender-ref ref="ASYNC_FILE_ALL"/>
            <appender-ref ref="ASYNC_FILE_ERROR"/>
        </root>
    </springProfile>
</configuration>

3.4 配置说明

# application.yml
logging:
  config: classpath:logback-spring.xml
  level:
    root: INFO
    com.example: DEBUG
    org.springframework: INFO
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE

四、Log4j2 配置

4.1 Maven依赖

<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <!-- 排除默认的logback -->
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- Log4j2 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    <!-- 异步日志支持 -->
    <dependency>
        <groupId>com.lmax</groupId>
        <artifactId>disruptor</artifactId>
        <version>3.4.4</version>
    </dependency>
</dependencies>

4.2 基础配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <!-- 定义日志格式 -->
    <Properties>
        <Property name="LOG_PATTERN">
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
        </Property>
        <Property name="LOG_PATH">logs</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">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
        <!-- 文件输出 - 错误日志 -->
        <RollingFile name="ErrorFile" fileName="${LOG_PATH}/error.log"
                     filePattern="${LOG_PATH}/error.%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="50 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="60"/>
        </RollingFile>
        <!-- 异步日志 -->
        <AsyncLogger name="AsyncRollingFile" level="info" additivity="false">
            <AppenderRef ref="RollingFile"/>
        </AsyncLogger>
        <AsyncLogger name="AsyncErrorFile" level="error" additivity="false">
            <AppenderRef ref="ErrorFile"/>
        </AsyncLogger>
    </Appenders>
    <!-- 日志器配置 -->
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="AsyncRollingFile"/>
            <AppenderRef ref="AsyncErrorFile"/>
        </Root>
    </Loggers>
</Configuration>

4.3 完整配置(推荐)

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
    <!-- 定义变量 -->
    <Properties>
        <Property name="LOG_HOME">logs</Property>
        <Property name="APP_NAME">myapp</Property>
        <Property name="LOG_PATTERN">
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{50} - %msg%n%ex
        </Property>
    </Properties>
    <!-- Appenders -->
    <Appenders>
        <!-- 控制台输出 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </Console>
        <!-- 文件输出 - 所有日志 -->
        <RollingRandomAccessFile name="RollingFile"
                              fileName="${LOG_HOME}/${APP_NAME}.log"
                              filePattern="${LOG_HOME}/${APP_NAME}-%d{yyyy-MM-dd}-%i.log"
                              immediateFlush="false"
                              append="true">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingRandomAccessFile>
        <!-- 文件输出 - 错误日志 -->
        <RollingRandomAccessFile name="ErrorFile"
                              fileName="${LOG_HOME}/${APP_NAME}-error.log"
                              filePattern="${LOG_HOME}/${APP_NAME}-error-%d{yyyy-MM-dd}-%i.log"
                              immediateFlush="false"
                              append="true">
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="50 MB"/>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="60"/>
        </RollingRandomAccessFile>
        <!-- 异步日志 -->
        <AsyncLogger name="AsyncRollingFile" level="info" additivity="false">
            <AppenderRef ref="RollingFile"/>
        </AsyncLogger>
        <AsyncLogger name="AsyncErrorFile" level="error" additivity="false">
            <AppenderRef ref="ErrorFile"/>
        </AsyncLogger>
    </Appenders>
    <!-- Loggers -->
    <Loggers>
        <!-- Spring框架日志级别 -->
        <Logger name="org.springframework" level="INFO"/>
        <Logger name="org.hibernate" level="INFO"/>
        <Logger name="org.apache" level="INFO"/>
        <!-- 应用日志级别 -->
        <Logger name="com.example" level="DEBUG"/>
        <!-- Root Logger -->
        <Root level="INFO">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="AsyncRollingFile"/>
            <AppenderRef ref="AsyncErrorFile"/>
        </Root>
    </Loggers>
</Configuration>

五、代码中使用日志

5.1 基本使用

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

/**
 * 日志使用示例
 */
public class UserService {

    // 获取Logger实例
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    /**
     * 创建用户
     */
    public void createUser(User user) {
        // TRACE:追踪信息
        logger.trace("开始创建用户,用户名: {}", user.getUsername());

        try {
            // DEBUG:调试信息
            logger.debug("用户信息: {}", user);

            // 业务逻辑
            saveUser(user);

            // INFO:重要信息
            logger.info("用户创建成功,用户ID: {}", user.getId());

        } catch (Exception e) {
            // ERROR:错误信息(包含异常堆栈)
            logger.error("用户创建失败,用户名: {}", user.getUsername(), e);
        }
    }

    /**
     * 参数校验失败
     */
    public void validateUser(User user) {
        if (user.getUsername() == null || user.getUsername().isEmpty()) {
            // WARN:警告信息
            logger.warn("用户名为空,用户ID: {}", user.getId());
        }
    }
}

5.2 使用占位符

/**
 * 日志占位符使用示例
 */
public class OrderService {

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

    /**
     * 创建订单
     */
    public void createOrder(Order order) {
        // 使用占位符{},避免字符串拼接
        // 错误做法:logger.info("订单创建成功,订单ID: " + order.getId());
        // 正确做法:
        logger.info("订单创建成功,订单ID: {}", order.getId());

        // 多个占位符
        logger.info("订单信息: ID={}, 用户ID={}, 金额={}", 
                   order.getId(), order.getUserId(), order.getAmount());

        // 占位符数量不匹配时,会使用toString()
        logger.info("订单创建: {}", order);
    }
}

5.3 异常日志

/**
 * 异常日志示例
 */
public class PaymentService {

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

    /**
     * 支付处理
     */
    public void processPayment(Payment payment) {
        try {
            // 业务逻辑
            process(payment);

        } catch (InsufficientBalanceException e) {
            // 带异常的日志
            logger.error("余额不足,用户ID: {}, 订单ID: {}", 
                        payment.getUserId(), payment.getOrderId(), e);

        } catch (PaymentException e) {
            // 错误日志
            logger.error("支付处理失败,订单ID: {}", payment.getOrderId(), e);

        } catch (Exception e) {
            // 未预期的异常
            logger.error("系统异常,订单ID: {}", payment.getOrderId(), e);
        }
    }
}

5.4 MDC 上下文

import org.slf4j.MDC;

/**
 * MDC(Mapped Diagnostic Context)使用示例
 */
public class RequestContext {

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

    /**
     * 设置请求上下文
     */
    public static void setContext(String traceId, String userId) {
        // 设置MDC
        MDC.put("traceId", traceId);
        MDC.put("userId", userId);
        MDC.put("requestTime", String.valueOf(System.currentTimeMillis()));

        logger.info("请求上下文已设置");
    }

    /**
     * 清除请求上下文
     */
    public static void clearContext() {
        MDC.clear();
        logger.info("请求上下文已清除");
    }

    /**
     * 使用示例
     */
    public void handleRequest(String traceId, String userId) {
        try {
            setContext(traceId, userId);

            // 日志会自动包含MDC信息
            logger.info("处理请求");

            // 业务逻辑
            processRequest();

        } finally {
            clearContext();
        }
    }
}

logback-spring.xml 中配置 MDC:

<pattern>
    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger{36} - %msg%n
</pattern>

六、日志格式说明

6.1 常用格式化符号

/**
 * Logback 格式化符号
 */
%logger{36}      // Logger名称,最多36个字符
%date           // 日期和时间
%d{yyyy-MM-dd}  // 日期
%d{HH:mm:ss.SSS} // 时间
%p / %-5level    // 日志级别(5个字符宽度,左对齐)
%thread         // 线程名称
%m / %msg        // 日志消息
%n              // 换行符
%ex             // 异常堆栈
%caller         // 调用者信息
%line           // 行号
%file           // 文件名
%M              // 方法名

/**
 * Log4j2 格式化符号
 */
%d              // 日期和时间
%p / %-5p       // 日志级别
%t              // 线程名称
%c / %-36c      // Logger名称
%m              // 日志消息
%n              // 换行符
%ex             // 异常堆栈
%F              // 文件名
%L              // 行号
%M              // 方法名
%r              // 毫秒数

6.2 推荐的日志格式

<!-- 控制台格式(带颜色) -->
<pattern>
    %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} 
    %clr(%5p) 
    %clr([%15.15t]){faint} 
    %clr(%-40.40logger{39}){cyan} 
    %clr(:){faint} 
    %m%n
</pattern>
<!-- 文件格式 -->
<pattern>
    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n%ex
</pattern>
<!-- 包含MDC的格式 -->
<pattern>
    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger{36} - %msg%n
</pattern>

七、日志最佳实践

7.1 最佳实践示例

/**
 * 日志最佳实践
 */
public class OrderService {

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

    /**
     * 最佳实践1:合理使用日志级别
     */
    public void createOrder(Order order) {
        logger.trace("开始创建订单");  // 追踪信息
        logger.debug("订单详情: {}", order);  // 调试信息

        // 重要操作
        logger.info("订单创建成功,订单ID: {}, 用户ID: {}", 
                   order.getId(), order.getUserId());
    }

    /**
     * 最佳实践2:使用占位符而不是字符串拼接
     */
    public void updateOrder(Order order) {
        // 错误做法
        // logger.info("订单更新: ID=" + order.getId() + ", 状态=" + order.getStatus());

        // 正确做法
        logger.info("订单更新: ID={}, 状态={}", order.getId(), order.getStatus());
    }

    /**
     * 最佳实践3:异常日志包含关键信息
     */
    public void processOrder(Long orderId) {
        try {
            process(orderId);
        } catch (OrderNotFoundException e) {
            // 包含关键业务信息
            logger.error("订单不存在,订单ID: {}", orderId, e);
        } catch (Exception e) {
            // 包含异常堆栈
            logger.error("订单处理失败,订单ID: {}", orderId, e);
        }
    }

    /**
     * 最佳实践4:避免在循环中打印日志
     */
    public void processBatch(List<Long> orderIds) {
        // 错误做法:在循环中打印日志
        // for (Long orderId : orderIds) {
        //     logger.info("处理订单: {}", orderId);
        // }

        // 正确做法:批量打印
        logger.info("批量处理订单,数量: {}, 订单ID列表: {}", 
                   orderIds.size(), orderIds);

        // 或者只打印摘要
        logger.info("批量处理订单,数量: {}", orderIds.size());
    }

    /**
     * 最佳实践5:使用MDC记录请求上下文
     */
    public void handleRequest(String traceId, Long userId) {
        MDC.put("traceId", traceId);
        MDC.put("userId", String.valueOf(userId));

        try {
            logger.info("开始处理请求");
            // 业务逻辑
        } finally {
            MDC.clear();
        }
    }
}

7.2 性能优化建议

/**
 * 日志性能优化
 */
public class PerformanceOptimization {

    /**
     * 优化1:判断日志级别
     */
    public void expensiveOperation() {
        // 错误做法:即使日志级别不满足,也会执行字符串拼接
        // logger.debug("用户列表: " + getUsers());

        // 正确做法:先判断日志级别
        if (logger.isDebugEnabled()) {
            logger.debug("用户列表: {}", getUsers());
        }
    }

    /**
     * 优化2:使用异步日志
     */
    // 在配置文件中配置异步Appender

    /**
     * 优化3:避免记录敏感信息
     */
    public void logPayment(Payment payment) {
        // 错误做法:记录敏感信息
        // logger.info("支付信息: {}", payment);

        // 正确做法:脱敏处理
        logger.info("支付信息: ID={}, 金额={}, 卡号={}", 
                   payment.getId(), 
                   payment.getAmount(),
                   maskCardNumber(payment.getCardNumber()));
    }

    /**
     * 卡号脱敏
     */
    private String maskCardNumber(String cardNumber) {
        if (cardNumber == null || cardNumber.length() < 16) {
            return "****";
        }
        return cardNumber.substring(0, 4) + "****" + cardNumber.substring(12);
    }
}

八、日志分析工具

8.1 常用日志分析工具

/**
 * 日志分析工具推荐
 */
public class LogAnalysisTools {

    /**
     * 1. ELK Stack
     * - Elasticsearch: 日志存储和搜索
     * - Logstash: 日志收集和处理
     * - Kibana: 日志可视化
     */

    /**
     * 2. Splunk
     * - 商业日志分析平台
     * - 强大的搜索和分析功能
     */

    /**
     * 3. Graylog
     * - 开源日志管理平台
     * - 基于Elasticsearch和MongoDB
     */

    /**
     * 4. Loki
     * - Grafana出品
     * - 轻量级日志聚合系统
     */

    /**
     * 5. Sentry
     * - 错误监控和日志收集
     * - 支持实时告警
     */
}

8.2 日志查询示例

# 使用grep查询日志
grep "ERROR" app.log
grep "用户创建成功" app.log

# 查找特定时间段的日志
grep "2024-01-01" app.log

# 查找包含特定关键词的日志
grep -E "(ERROR|WARN)" app.log

# 统计错误数量
grep "ERROR" app.log | wc -l

# 查找特定用户的日志
grep "userId=123456" app.log

九、Spring Boot 集成

9.1 配置文件配置

# application.yml
logging:
  config: classpath:logback-spring.xml
  level:
    root: INFO
    com.example: DEBUG
    org.springframework: INFO
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
  file:
    name: logs/app.log
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

9.2 不同环境配置

# application-dev.yml(开发环境)
logging:
  level:
    root: DEBUG
    com.example: DEBUG
# application-test.yml(测试环境)
logging:
  level:
    root: INFO
    com.example: INFO
# application-prod.yml(生产环境)
logging:
  level:
    root: WARN
    com.example: INFO
  file:
    name: /var/log/myapp/app.log

十、总结

10.1 最佳实践清单

配置管理:

  • 使用日志框架(Logback/Log4j2)
  • 配置合理的日志格式
  • 配置日志文件滚动策略
  • 配置不同环境的日志级别

代码使用:

  • 使用占位符而不是字符串拼接
  • 合理使用日志级别
  • 异常日志包含关键信息
  • 使用MDC记录请求上下文

性能优化:

  • 使用异步日志
  • 判断日志级别后再记录
  • 避免在循环中打印日志
  • 避免记录敏感信息

日志管理:

  • 使用日志分析工具
  • 定期清理旧日志
  • 配置日志告警
  • 建立日志规范

10.2 避坑指南

常见错误:

  • ❌ 使用 System.out.println
  • ❌ 字符串拼接日志消息
  • ❌ 所有日志都用INFO级别
  • ❌ 异常日志不包含堆栈
  • ❌ 在循环中打印大量日志
  • ❌ 记录敏感信息

正确做法:

  • ✅ 使用日志框架
  • ✅ 使用占位符{}
  • ✅ 合理使用日志级别
  • ✅ 异常日志包含堆栈和关键信息
  • ✅ 批量记录或记录摘要
  • ✅ 脱敏处理敏感信息

记住:日志是系统的重要组成部分,良好的日志记录能够帮助你快速定位问题、分析系统状态。选择合适的日志框架,合理配置日志级别和格式,遵循最佳实践,让你的日志更有价值!

以上就是系统讲解Java如何优雅地打印日志的详细内容,更多关于Java打印日志的资料请关注脚本之家其它相关文章!

相关文章

  • Java语言实现简单FTP软件 FTP软件效果图预览之下载功能(2)

    Java语言实现简单FTP软件 FTP软件效果图预览之下载功能(2)

    这篇文章主要为大家详细介绍了Java语言实现简单FTP软件,FTP软件效果图预览之下载功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • springBoot不同module之间互相依赖的实现

    springBoot不同module之间互相依赖的实现

    本文主要介绍了springBoot不同module之间互相依赖的实现,不同模块之间的依赖通常是通过Maven或Gradle来管理的,下面就来介绍一下如何实现,感兴趣的可以了解一下
    2024-08-08
  • Spring boot打包jar分离lib和resources方法实例

    Spring boot打包jar分离lib和resources方法实例

    这篇文章主要介绍了Spring boot打包jar分离lib和resources方法实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • Spring启动指定时区的两种方法

    Spring启动指定时区的两种方法

    最近项目启动,时间要修改成东七区时间,本文主要介绍了Spring启动指定时区的两种方法,具有一定的参考价值,感兴趣的可以了解一下
    2023-11-11
  • 详细解读Java的Lambda表达式

    详细解读Java的Lambda表达式

    这篇文章主要介绍了详细解读Java的Lambda表达式,lambda 表达式 是Java 8新加入的新特性,它在Java中是引入了函数式编程这一概念,需要的朋友可以参考下
    2023-04-04
  • Java导出Excel通用工具类实例代码

    Java导出Excel通用工具类实例代码

    这篇文章主要给大家介绍了关于Java导出Excel通用工具类的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • JVM中如何做到STW使程序暂停

    JVM中如何做到STW使程序暂停

    STW,即Stop The World,这篇文章来为大家详细介绍了JVM中是如何做到STW使程序暂停的原理分析,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2022-01-01
  • Lombok中@EqualsAndHashCode注解的使用及说明

    Lombok中@EqualsAndHashCode注解的使用及说明

    这篇文章主要介绍了Lombok中@EqualsAndHashCode注解的使用及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • Java使用poi导出ppt文件的实现代码

    Java使用poi导出ppt文件的实现代码

    Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java对Microsoft Office格式档案读和写的功能。本文给大家介绍Java使用poi导出ppt文件的实现代码,需要的朋友参考下吧
    2021-06-06
  • mybatis-plus自定义业务实现方式

    mybatis-plus自定义业务实现方式

    本文介绍了使用MyBatis-Plus(简称MP)进行数据库操作的实现步骤,包括创建实体类、Mapper接口和XML文件、Service接口和实现类,以及测试类,重点强调了业务层参数校验、事务管理以及复杂SQL的处理方式
    2026-03-03

最新评论