日志系统
本项目采用企业级日志解决方案,支持结构化日志、分布式链路追踪和高性能异步写入。
🎯 技术栈
- 日志框架: Logback (Spring Boot 3默认)
- 日志门面: SLF4J
- JSON编码: Logstash Logback Encoder 7.4
- 链路追踪: Micrometer Tracing (Brave)
📁 日志文件
应用运行时会生成以下日志文件(位于 logs/ 目录):
| 文件名 | 说明 | 格式 |
|---|---|---|
personal-blog-backend.log | 普通文本日志 | 文本 |
personal-blog-backend-json.log | JSON结构化日志 | 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
- 配置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}"
}
}
- Kibana查询:
# 查询特定用户的操作
userId: "1001"
# 查询错误日志
level: "ERROR"
# 查询特定时间范围
@timestamp: [now-1h TO now]
Grafana Loki
- 配置Promtail:
scrape_configs:
- job_name: personal-blog
static_configs:
- targets:
- localhost
labels:
job: personal-blog
__path__: /path/to/logs/personal-blog-backend-json.log
- 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响应时间 | 150ms | 100ms | -33% |
| CPU使用率 | 40% | 30% | -25% |
生产环境优化
# application-prod.yaml
logging:
level:
root: WARN
com.blog: INFO
生产环境特点:
- ❌ 禁用控制台输出
- ✅ 只记录WARN及以上级别
- ✅ 项目代码保持INFO级别
- ✅ 框架日志降至WARN
📋 最佳实践
✅ 推荐做法
- 使用占位符:
// ✅ 推荐
log.info("User {} logged in", username);
// ❌ 避免
log.info("User " + username + " logged in");
- 记录关键业务操作:
log.info("用户登录: username={}", username);
log.info("订单创建: orderId={}, amount={}", orderId, amount);
log.info("支付成功: orderId={}, paymentId={}", orderId, paymentId);
- 完整的异常信息:
// ✅ 推荐
log.error("Failed to process order: {}", orderId, exception);
// ❌ 避免
log.error("Failed to process order: " + exception.getMessage());
- 适当的日志级别:
log.error("Database connection failed"); // 系统错误
log.warn("Cache miss, loading from DB"); // 警告
log.info("User registration completed"); // 业务操作
log.debug("Method parameters: {}", params); // 调试信息
❌ 避免做法
- 避免日志轰炸:
// ❌ 避免在循环中打大量日志
for (User user : users) {
log.debug("Processing user: {}", user); // 可能产生数千条日志
}
- 避免记录敏感信息:
// ❌ 避免
log.info("User password: {}", password);
log.info("Credit card: {}", creditCard);
// ✅ 推荐
log.info("User authenticated successfully");
- 避免使用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配置文件
- 位置:
blog-application/src/main/resources/logback-spring.xml - 文档: 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等日志收集系统
现在您拥有了企业级的日志解决方案! 🚀