系统讲解Java如何优雅地打印日志
作者: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打印日志的资料请关注脚本之家其它相关文章!
