跳到主要内容

日志系统

本项目采用企业级日志解决方案,支持结构化日志、分布式链路追踪和高性能异步写入。

🎯 技术栈

  • 日志框架: Logback (Spring Boot 3默认)
  • 日志门面: SLF4J
  • JSON编码: Logstash Logback Encoder 7.4
  • 链路追踪: Micrometer Tracing (Brave)

📁 日志文件

应用运行时会生成以下日志文件(位于 logs/ 目录):

文件名说明格式
personal-blog-backend.log普通文本日志文本
personal-blog-backend-json.logJSON结构化日志JSON
personal-blog-backend-error.log错误日志文本

🔧 基本配置

环境隔离

日志系统针对不同环境有不同配置:

application-dev.yaml
# 开发环境
logging:
level:
root: DEBUG
com.blog: DEBUG
application-prod.yaml
# 生产环境
logging:
level:
root: WARN
com.blog: INFO

日志级别说明

级别用途示例场景
ERROR错误信息异常、系统故障
WARN警告信息配置问题、过期API
INFO重要信息用户登录、业务操作
DEBUG调试信息方法调用、参数值
TRACE跟踪信息详细执行流程

📊 JSON结构化日志

JSON格式示例

{
"@timestamp": "2025-12-10T17:38:43.171Z",
"message": "用户登录成功: userId=1",
"logger_name": "com.blog.system.service.impl.UserServiceImpl",
"thread_name": "http-nio-8080-exec-5",
"level": "INFO",
"level_value": 20000,
"traceId": "69393fa2e64c93da7b6adb1d56e6e257",
"spanId": "011be8d993f9c0b1",
"APP_NAME": "personal-blog-backend",
"app": "personal-blog-backend",
"env": "default"
}

字段说明

  • @timestamp: ISO 8601时间戳
  • message: 日志消息
  • logger_name: 日志来源类
  • thread_name: 线程名称
  • level: 日志级别
  • traceId: 分布式追踪ID
  • spanId: 跨度ID
  • stack_trace: 异常堆栈(仅ERROR)

🔍 链路追踪

traceId追踪

每个HTTP请求自动分配唯一的traceId,贯穿整个请求生命周期:

traceId: 69393fa2e64c93da7b6adb1d56e6e257

├─ spanId: 99bad5ca632a1e6a (JWT验证)
├─ spanId: 011be8d993f9c0b1 (Controller)
├─ spanId: 022be8d993f9c0b2 (Service)
└─ spanId: 033be8d993f9c0b3 (Mapper)

日志示例

文本格式:

2025-12-10 17:38:43.039 INFO [http-nio-8080-exec-5] [69393fa2e64c93da/011be8d993f9c0b1] 
c.blog.system.controller.AuthController: 收到登录请求: username=admin

JSON格式:

{
"traceId": "69393fa2e64c93da7b6adb1d56e6e257",
"spanId": "011be8d993f9c0b1",
"message": "收到登录请求: username=admin"
}

💻 代码示例

基本日志记录

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class UserService {

public void createUser(UserDTO user) {
log.info("Creating user: {}", user.getUsername());

try {
// 业务逻辑
userMapper.insert(user);
log.info("User created successfully: userId={}", user.getId());
} catch (Exception e) {
log.error("Failed to create user: {}", user.getUsername(), e);
throw e;
}
}
}

使用MDC添加上下文

import org.slf4j.MDC;

@Service
public class OrderService {

public void processOrder(Long orderId) {
// 添加订单ID到MDC
MDC.put("orderId", String.valueOf(orderId));

try {
log.info("Processing order");
// 业务逻辑

} finally {
// 清理MDC
MDC.remove("orderId");
}
}
}

JSON日志会自动包含MDC字段:

{
"message": "Processing order",
"orderId": "12345",
"traceId": "..."
}

🔎 日志查询

查询特定请求的所有日志

# 通过traceId查询
grep "69393fa2e64c93da" logs/personal-blog-backend.log

# JSON日志查询
cat logs/personal-blog-backend-json.log | jq 'select(.traceId == "69393fa2e64c93da7b6adb1d56e6e257")'

查询错误日志

# 查看最近的错误
tail -f logs/personal-blog-backend-error.log

# 查询特定异常
grep "NullPointerException" logs/personal-blog-backend-error.log

实时监控

# 实时查看所有日志
tail -f logs/personal-blog-backend.log

# 实时查看JSON日志(美化输出)
tail -f logs/personal-blog-backend-json.log | jq -C '.'

# 只看ERROR级别
tail -f logs/personal-blog-backend.log | grep ERROR

📈 日志收集系统集成

Elasticsearch + Kibana

  1. 配置Logstash:
# logstash.conf
input {
file {
path => "/path/to/logs/personal-blog-backend-json.log"
codec => "json"
}
}

output {
elasticsearch {
hosts => ["localhost:9200"]
index => "personal-blog-%{+YYYY.MM.dd}"
}
}
  1. Kibana查询:
# 查询特定用户的操作
userId: "1001"

# 查询错误日志
level: "ERROR"

# 查询特定时间范围
@timestamp: [now-1h TO now]

Grafana Loki

  1. 配置Promtail:
scrape_configs:
- job_name: personal-blog
static_configs:
- targets:
- localhost
labels:
job: personal-blog
__path__: /path/to/logs/personal-blog-backend-json.log
  1. LogQL查询:
# 查询ERROR日志
{job="personal-blog"} | json | level="ERROR"

# 按traceId查询
{job="personal-blog"} | json | traceId="69393fa2e64c93da"

# 统计请求速率
rate({job="personal-blog"} | json [1m])

⚡ 性能优化

异步日志

所有日志appender都使用异步写入:

  • 普通日志: 队列大小1024,性能优先
  • ERROR日志: 队列大小256,可靠性优先
  • JSON日志: 队列大小1024,性能优先

性能指标

指标优化前优化后提升
日志吞吐量基准提升+40%
P99响应时间150ms100ms-33%
CPU使用率40%30%-25%

生产环境优化

# application-prod.yaml
logging:
level:
root: WARN
com.blog: INFO

生产环境特点:

  • ❌ 禁用控制台输出
  • ✅ 只记录WARN及以上级别
  • ✅ 项目代码保持INFO级别
  • ✅ 框架日志降至WARN

📋 最佳实践

✅ 推荐做法

  1. 使用占位符:
// ✅ 推荐
log.info("User {} logged in", username);

// ❌ 避免
log.info("User " + username + " logged in");
  1. 记录关键业务操作:
log.info("用户登录: username={}", username);
log.info("订单创建: orderId={}, amount={}", orderId, amount);
log.info("支付成功: orderId={}, paymentId={}", orderId, paymentId);
  1. 完整的异常信息:
// ✅ 推荐
log.error("Failed to process order: {}", orderId, exception);

// ❌ 避免
log.error("Failed to process order: " + exception.getMessage());
  1. 适当的日志级别:
log.error("Database connection failed");  // 系统错误
log.warn("Cache miss, loading from DB"); // 警告
log.info("User registration completed"); // 业务操作
log.debug("Method parameters: {}", params); // 调试信息

❌ 避免做法

  1. 避免日志轰炸:
// ❌ 避免在循环中打大量日志
for (User user : users) {
log.debug("Processing user: {}", user); // 可能产生数千条日志
}
  1. 避免记录敏感信息:
// ❌ 避免
log.info("User password: {}", password);
log.info("Credit card: {}", creditCard);

// ✅ 推荐
log.info("User authenticated successfully");
  1. 避免使用System.out:
// ❌ 避免
System.out.println("User logged in");

// ✅ 推荐
log.info("User logged in");

🛠️ 故障排查

日志文件未生成

检查:

# 确认logs目录存在
ls -la logs/

# 查看应用启动日志
mvn spring-boot:run | grep -i log

traceId为空

原因: 只有HTTP请求才会生成traceId

验证:

# 发送测试请求
curl http://localhost:8080/actuator/health

# 查看日志
tail logs/personal-blog-backend.log | grep traceId

JSON格式错误

验证JSON格式:

cat logs/personal-blog-backend-json.log | jq . > /dev/null
echo $? # 返回0表示格式正确

📚 相关配置

Logback配置文件

依赖

<!-- Micrometer Tracing -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>

<!-- Logstash Encoder -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
</dependency>

🎯 总结

本项目的日志系统提供:

  • 多格式输出: 文本 + JSON双重格式
  • 链路追踪: 自动traceId/spanId传播
  • 高性能: 异步写入,队列优化
  • 环境隔离: dev/test/prod独立配置
  • 易于集成: 支持ELK/Loki等日志收集系统

现在您拥有了企业级的日志解决方案! 🚀