微服务日志需结构化输出至stdout,由fluent-bit采集并提取trace_id等字段为loki标签,禁用本地文件写入、嵌套字段和非utc时间戳,确保service_name等label精确匹配且顶层存在。

为什么 log 包直接写文件不适用于微服务日志聚合
微服务部署后,日志分散在多个实例、容器甚至节点上,log.Printf 或 logrus.WithFields 写本地文件会导致:日志无法集中检索、丢失时间上下文、无法按 traceID 关联请求链路、难以做错误频次统计。这不是“写得不够多”的问题,而是输出目标和结构不匹配。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 禁用所有直接写
/var/log/或当前目录的os.File日志 handler - 统一使用结构化日志(JSON 格式),必须包含
service_name、trace_id、span_id、level、timestamp字段 - 日志输出到
stdout(不是stderr),由容器运行时或 sidecar(如 fluent-bit)接管采集 - 避免在日志中拼接敏感字段(如 token、password),用
redact逻辑提前过滤
如何用 zerolog 输出符合 Loki / Grafana 查询要求的 JSON 日志
Loki 不索引日志内容,只索引 label(比如 {service="auth", level="error"}),所以 zerolog 必须把关键维度作为 log event 的 top-level 字段,而非嵌套在 fields 里,否则会被 Loki 当作纯文本丢进 logfmt 流中,失去标签能力。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 初始化时用
zerolog.New(os.Stdout).With().Str("service_name", "user-api").Str("env", os.Getenv("ENV")).Logger()注入静态 label - HTTP 中间件里动态注入
trace_id:logger.With().Str("trace_id", getTraceID(r)).Logger() - 禁止调用
.Caller()或.Stack()—— 这些会破坏 JSON 结构,Loki 解析失败时整行日志被丢弃 - 错误日志必须用
Err(err)方法,zerolog 会自动序列化为"err": {"message":"...","stack":...},Grafana Explore 才能展开堆栈
如何让 fluent-bit 正确提取 Go 微服务的 trace_id 并转发到 Loki
默认配置下,fluent-bit 把整行 JSON 当作一个字符串字段,trace_id 不会成为 Loki 的 label。必须显式启用 Parser 并配置 Key_Name 提取字段。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 在 fluent-bit 的
INPUT段加Parser json,并确保Path指向容器 stdout 日志路径(如/var/log/containers/*_user-api-*.log) - 定义 parser 配置:
[PARSER] Name json Format json Time_Key timestamp Time_Format %Y-%m-%dT%H:%M:%S.%LZ - 在
FILTER段用Modify插件把trace_id提升为 tag:Rule key_exists trace_id trace_id true - OUTPUT 到 Loki 时,
Labels必须显式列出:Labels service_name=$service_name,env=$env,trace_id=$trace_id
为什么在 Grafana 里搜 {service_name="order"} |~ "timeout" 查不到结果
常见原因不是正则写错,而是日志没进 Loki 的正确 stream。Loki 的 label 匹配是精确的,如果 service_name 实际写成了 service 或 SERVICE_NAME,或者大小写不一致(如 "Order" vs "order"),整个 stream 就不会被选中。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 先查
{job="fluent-bit"}看原始流是否存在,确认service_namelabel 是否出现、值是否拼写一致 - 用
json解析器在 Grafana Explore 中点开单条日志,确认trace_id字段是否在顶层,而不是藏在fields.trace_id里 - 避免在 Go 代码里用
strings.Title处理服务名 —— 它会把payment-service变成Payment-Service,导致 label 不匹配 - 上线前用
curl -s http://loki:3100/loki/api/v1/labels?start=1h | jq '.values'直接检查 label 枚举值
entry too old、日志时间戳比系统时间早几秒——这些都不是配置漏了,而是 Go 进程启动时没同步 NTP,或容器镜像里没装 tzdata 导致 time.Now() 返回本地时区时间,Loki 强制按 UTC 解析就错位了。这个点很容易被忽略,但修复成本最低:Dockerfile 里加一行 RUN apt-get update && apt-get install -y tzdata,再设 ENV TZ=UTC。










