go应用输出单行结构化json到stdout,配合filebeat启用json.keys_under_root:true及multiline处理panic堆栈,再经logstash date过滤器校准时间戳,才能确保日志字段可索引、可聚合。

zap 和 logrus 输出的 JSON 日志,只要格式干净、字段规范,就能稳定进 ELK;关键不在“连不连得上”,而在 Go 进程输出的每行是否是合法、扁平、可索引的 JSON。
Go 应用必须输出单行结构化 JSON 到 stdout 或文件
ELK 不解析日志内容本身,它只认“一行一文档”。非结构化字符串(如 log.Printf("user %d login", uid))会被整个塞进 message 字段,Kibana 里根本没法按 user_id 过滤或聚合。
- 必须用
zap、logrus或 Go 1.21+ 的slog,禁用标准库log - 容器环境一律
SetOutput(os.Stdout);宿主机部署才考虑os.OpenFile写文件 - 字段名用下划线(
user_id✅,userId❌),禁中文、空格、@开头(@timestamp是 ES 保留字段,自己日志里别用) - 时间字段必须是 RFC3339Nano 格式:
zerolog.TimeFieldFormat = time.RFC3339Nano,zap.NewProductionEncoderConfig()默认已设好 - panic 堆栈是多行的,不处理会散成几十条日志——必须靠 Filebeat 的
multiline合并,不能指望 Logstash 补救
Filebeat 配置里 json.keys_under_root: true 是硬性要求
Filebeat 默认把整行当纯文本传给 Logstash 或 ES。即使 Go 输出了 JSON,不启用 JSON 解析,Kibana 里看到的仍是 {"level":"info","msg":"..."} 这样一个大字符串,所有字段都不可点选、不可聚合。
- 在
filebeat.yml对应输入段加json: { keys_under_root: true, overwrite_keys: true, add_error_key: true } -
overwrite_keys: true是为了防止 Go 日志中已有的level、time被 Filebeat 自带字段覆盖 - 路径配置要精确:
/var/log/myapp/*.log比/var/log/**.log更安全,避免误扫归档文件(如.log.1.gz) - 权限问题会静默失败:检查
filebeat logs是否报permission denied,确保 Filebeat 用户能读日志文件
Logstash filter 必须显式做 date 时间戳校准
Go 输出的 "time": "2026-06-17T16:26:00.123Z" 是字符串,ES 存的是 @timestamp 字段(类型为 date)。不转换,Kibana 时间轴错乱、无法按分钟聚合、甚至查不到最近 15 分钟日志。
- filter 段必须写:
date { match => ["time", "ISO8601"] target => "@timestamp" }(假设 Go 日志里时间字段叫time) - 不要用
grok解析 JSON 日志——那是给非结构化日志兜底的,纯属增加失败点和 CPU 开销 - 首次写入前,预建 index template,用
dynamic_templates把所有字符串字段映射为keyword,避免user_id被分词后搜不到精确值 - 字段值类型必须一致:第一次写
"status": 500(int),后面就不能写"status": "500"(string),ES 会拒掉整条
容器场景下别写日志文件,直接输出到 stdout
Docker 默认用 json-file 日志驱动,会把 os.Stdout 的每行自动转成结构化 JSON 并打上容器元数据(container.id、container.name)。手动挂卷写文件反而引入权限、轮转、句柄泄漏等一堆故障点。
- Go 程序里删掉所有
lumberjack或rotatelogs文件轮转逻辑 - Filebeat 改用
dockerinput 类型(type: docker),直接监听 Docker daemon,比filestream更稳 - 如果仍用
filestream,确保容器挂载路径与paths完全一致,且宿主机上 Filebeat 用户有读权限 - 测试时用
filebeat test config和filebeat test output确认配置加载成功,再触发一次 panic 看 ES 是否收到完整堆栈块
multiline 配错了导致 panic 堆栈被切成两半——这些细节在日志量小的时候不暴露,压测或上线后才集中爆发。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











