结构化日志收集
什么是结构化日志?
结构化日志是指将日志数据以键值对或JSON等标准格式存储的日志记录方式。与传统的纯文本日志相比,结构化日志通过明确的字段定义,使得日志更容易被机器解析和人类阅读。例如:
{
"timestamp": "2023-05-20T14:32:11Z",
"level": "ERROR",
"message": "Failed to connect to database",
"service": "auth-service",
"request_id": "abc123"
}
为什么需要结构化日志?
- 高效查询:可直接通过字段过滤(如
level="ERROR"
) - 自动化处理:工具链(如Loki)能直接提取字段
- 一致性:团队统一的日志格式标准
实现方式
1. 代码层结构化
在应用程序中直接输出结构化日志(以Python为例):
import json
import logging
class StructuredLogger:
def __init__(self, name):
self.logger = logging.getLogger(name)
def log(self, level, message, **kwargs):
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"level": level,
"message": message,
**kwargs
}
self.logger.log(level, json.dumps(log_entry))
# 使用示例
logger = StructuredLogger("myapp")
logger.log(logging.ERROR, "DB connection failed",
db_host="db.example.com", attempt=3)
输出结果:
{
"timestamp": "2023-05-20T14:32:11Z",
"level": "ERROR",
"message": "DB connection failed",
"db_host": "db.example.com",
"attempt": 3
}
2. 日志代理处理
通过日志收集工具(如Fluentd)转换非结构化日志:
# Fluentd配置示例
<filter app.logs>
@type parser
key_name log
reserve_data true
<parse>
@type json
</parse>
</filter>
最佳实践
- 始终包含
timestamp
、level
、message
基础字段 - 使用小写蛇形命名(如
user_id
而非userId
) - 避免嵌套过深的JSON结构
与Loki的集成
Grafana Loki 通过 LogQL 直接查询结构化字段:
{app="auth-service"} | json | level="ERROR"
实际案例:电商系统日志
场景:跟踪用户下单流程的异常
{
"timestamp": "2023-05-20T15:01:22Z",
"level": "WARN",
"message": "Inventory check failed",
"service": "order-service",
"user_id": "u789",
"order_id": "ord-xyz456",
"sku": "prod-123",
"reason": "Insufficient stock",
"available": 0,
"requested": 2
}
通过该结构,可以快速:
- 统计缺货最多的商品:
| json | reason="Insufficient stock" | group_by(sku)
- 定位特定用户的订单问题:
| json | user_id="u789"
总结
结构化日志收集通过以下方式提升可观测性:
- 80%+ 的日志查询效率提升
- 减少正则表达式匹配的需求
- 与现代日志系统(如Loki)深度集成
扩展练习
- 将你的应用日志改造成JSON格式
- 尝试用LogQL查询特定字段
- 比较结构化与非结构化日志的查询性能差异