Go应用日志进ELK的关键是输出结构化JSON,须用zap或logrus而非标准库;Filebeat需启用json.keys_under_root: true;Logstash应直接用json插件解析;Kibana索引模式须识别@timestamp字段。

Go 应用要让日志进 ELK,关键不是“能不能连上 Logstash”,而是日志输出本身是否具备机器可解析性。只要 Go 进程输出的是结构化 JSON(字段明确、无嵌套乱码、时间格式统一),后续链路就基本不会卡在解析环节。
Go 输出 JSON 日志必须用 zap 或 logrus,别碰 log 标准库
标准库 log 只能拼字符串,比如 log.Printf("user %d login from %s", uid, ip),这种日志 Logstash 用 grok 解析极不稳定,字段缺失、顺序错位、转义混乱全是常态。生产环境必须换结构化日志库。
-
zap性能更高,适合高 QPS 服务;logrus更易上手,插件生态丰富(如logrus-prefixed-formatter) - 必须显式设置 JSON 编码器:
zap.NewProductionEncoderConfig()或logrus.SetFormatter(&logrus.JSONFormatter{}) - 禁止手动拼接 JSON 字符串(如
fmt.Sprintf(`{"msg":"%s"}`, msg)),容易引入非法字符或未转义引号 - 确保所有业务日志都走结构化入口,比如
logger.Info("order created", "order_id", id, "amount", amt),而不是混用fmt.Println或log.Print
Filebeat 配置里 json.keys_under_root: true 是硬性要求
Filebeat 默认把整行当字符串塞给 Logstash 或 ES,即使 Go 输出了 JSON,它也只当普通文本。必须启用 JSON 解析,并把字段提到根层级,否则 Kibana 里看到的还是一个叫 message 的大字符串字段。
- 在 Filebeat 的
filebeat.yml中对应日志路径配置段加入:input: - type: filestream paths: - /var/log/app/*.log parsers: - json: keys_under_root: true overwrite_keys: true add_error_key: true -
overwrite_keys: true防止 Go 日志中已有level、time字段被 Filebeat 自带字段覆盖 - 如果日志文件有权限问题(如 Go 写入时是
root,Filebeat 运行用户是filebeat),会静默跳过采集——检查filebeat logs是否报permission denied
Logstash filter 里不用 grok,优先用 json 插件
很多教程一上来就教怎么写 grok 正则匹配时间戳和状态码,但那是给非结构化日志兜底的。Go 已经输出 JSON,再用 grok 属于“把 JSON 解成字符串再重新 parse”,纯属增加失败点和 CPU 开销。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- Logstash 配置中直接用
json插件即可:filter { json { source => "message" } } - 如果 Go 日志里没带
service、env等字段,才需要在 Logstash 里补:mutate { add_field => { "service" => "payment-api" } } mutate { add_field => { "env" => "prod" } } - 避免在 Logstash 里做复杂计算(如耗时单位转换、IP 地理位置查询),这些应该在 Go 层完成或交给 Kibana 的 Lens 表达式处理
Kibana 中索引模式匹配不上?先查 @timestamp 字段是否存在
Kibana 创建索引模式时,必须识别出一个时间字段才能启用时间过滤。Go 输出的 JSON 如果用的是 time、ts、timestamp 这类自定义名,而没映射到 @timestamp,Kibana 就会提示 “No time field found”。这不是权限或网络问题,是字段名不匹配。
- 最稳妥做法:Go 日志中显式写
"@timestamp"字段,值为 RFC3339 格式字符串(time.Now().Format(time.RFC3339)) - 或者在 Logstash output 到 Elasticsearch 前加时间字段:
date { match => ["time", "ISO8601"] target => "@timestamp" } - 确认 ES 中文档确实含
@timestamp:用 Kibana 的Dev Tools执行GET /your-index-name/_search?size=1,看返回结果里有没有该字段
最容易被忽略的是日志轮转后 Filebeat 的 offset 重置问题:Go 用 lumberjack 切日志,Filebeat 却还在读旧文件句柄,导致新日志不采集。解决方式不是重启 Filebeat,而是确保 filebeat.yml 中启用了 close_inactive: 5m 和 clean_removed: true。


















