系统讲解Java如何优雅地打印日志
前言 先说说开发中最常见的一个问题:为什么生产环境的代码还在用 System.out.println?再比如,日志格式混乱——没有时间戳,不知道是谁打印的;日志级别使用不当,错误信息混在普通信息里。这些都是实打实的痛点。 今天咱们系统聊聊如何优雅地打印Ja va日志,彻底告别 System.out.
前言
先说说开发中最常见的一个问题:为什么生产环境的代码还在用 System.out.println?再比如,日志格式混乱——没有时间戳,不知道是谁打印的;日志级别使用不当,错误信息混在普通信息里。这些都是实打实的痛点。

今天咱们系统聊聊如何优雅地打印Ja va日志,彻底告别 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 Ma ven依赖
org.springframework.boot spring-boot-starter-web ch.qos.logback logback-classic 1.4.14 ch.qos.logback logback-access 1.4.14
3.2 基础配置
${LOG_PATTERN} UTF-8 ${LOG_PATH}/app.log ${LOG_PATTERN} UTF-8 ${LOG_PATH}/app.%d{yyyy-MM-dd}.log 30 ${LOG_PATH}/error.log ${LOG_PATTERN} UTF-8 ERROR ACCEPT DENY ${LOG_PATH}/error.%d{yyyy-MM-dd}.log 30 512 0
3.3 完整配置(推荐)
${CONSOLE_LOG_PATTERN} UTF-8 ${LOG_HOME}/${APP_NAME}-all.log ${FILE_LOG_PATTERN} UTF-8 ${LOG_HOME}/${APP_NAME}-all.%d{yyyy-MM-dd}.%i.log 100MB 30 3GB ${LOG_HOME}/${APP_NAME}-error.log ${FILE_LOG_PATTERN} UTF-8 ERROR ${LOG_HOME}/${APP_NAME}-error.%d{yyyy-MM-dd}.%i.log 50MB 60 5GB 1024 0 512 0
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 Ma ven依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-log4j2 com.lmax disruptor 3.4.4
4.2 基础配置
%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n logs
4.3 完整配置(推荐)
logs myapp %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{50} - %msg%n%ex
五、代码中使用日志
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);
// 业务逻辑
sa veUser(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:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger{36} - %msg%n
六、日志格式说明
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 推荐的日志格式
%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 %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n%ex %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%X{userId}] %-5level %logger{36} - %msg%n
七、日志最佳实践
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 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级别
- ❌ 异常日志不包含堆栈
- ❌ 在循环中打印大量日志
- ❌ 记录敏感信息
正确做法:
- ✅ 使用日志框架
- ✅ 使用占位符{}
- ✅ 合理使用日志级别
- ✅ 异常日志包含堆栈和关键信息
- ✅ 批量记录或记录摘要
- ✅ 脱敏处理敏感信息
记住:日志是系统的重要组成部分,良好的日志记录能够帮助你快速定位问题、分析系统状态。选择合适的日志框架,合理配置日志级别和格式,遵循最佳实践,让你的日志更有价值!
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















