go服务日志必须写本地文件再由filebeat采集,禁直连elk;需用os.o_append|os.o_create打开文件、单行json格式、filebeat配置json.keys_under_root:true/json.overwrite_keys:true/multiline捕获panic,es中trace_id须设为keyword类型。

Go服务日志必须写文件,别直连ELK
Go本身没有内置日志转发能力,zap、logrus这些库只负责格式化和写入,不带HTTP或Logstash输出模块。硬接ELK容易丢日志、卡goroutine,甚至触发panic后日志全丢。
正确做法是让Go进程把结构化日志写到本地文件,再由Filebeat读取——这是生产环境唯一稳定路径。
- 用
os.OpenFile打开文件时必须加os.O_APPEND | os.O_CREATE标志,避免多协程写冲突 - 单个
*os.File可并发安全写,但别套io.MultiWriter往多个文件句柄里塞 - 日志行末尾只保留一个
\n,用fmt.Fprintf(f, "%s\n", msg)比fmt.Fprintln更可控 - 如果用lumberjack做轮转,务必开启
force_close_files: true,否则Filebeat可能读不到新文件
JSON日志要“单行”,否则Filebeat解析失败
Filebeat的json解码器只认严格单行JSON:每条日志必须是一个完整、无换行、无前导空格的JSON object。任何格式偏差都会导致整条日志被当纯文本吞掉,@timestamp错乱、trace_id进message字段里出不来。
- zap启用
zap.NewProduction()或logrus设&logrus.JSONFormatter{},确保输出是JSON - 禁用任何自定义换行、缩进、空格拼接——比如不要在
msg字段里手动加\n - panic堆栈是多行的,必须靠Filebeat的
multiline处理,不能靠Go端“格式化成一行” - 验证方式:
filebeat -e -d "publish"看实际发出的event结构,确认trace_id是平级字段而非嵌套
Filebeat配置里这三项不能漏
哪怕Go日志格式完全正确,Filebeat没配对,Kibana里照样搜不到trace_id。最常踩的坑就在这三个配置项上。
-
json.keys_under_root: true——否则trace_id会藏在json.trace_id下,Kibana查trace_id:根本匹配不到 -
json.overwrite_keys: true——避免同名字段被忽略,比如Go日志里有level,Filebeat默认字段也有level,不覆盖就丢 -
multiline.pattern: '^panic:'+multiline.negate: true+multiline.match: before——这是唯一能完整捕获panic堆栈的方式;用after会吞掉第一行,ES里看不到panic:关键词
ES索引模板里trace_id必须是keyword类型
Kibana搜trace_id: "abc123"没结果?大概率不是日志没发过来,而是ES字段映射错了。text类型会被分词,精确匹配直接失效。
必须在Elasticsearch索引模板里显式声明:
{
"mappings": {
"properties": {
"trace_id": { "type": "keyword" },
"span_id": { "type": "keyword" }
}
}
}
另外注意:trace_id.keyword这种写法只在字段已定义fields子属性时才有效;没定义的话,直接查trace_id就行,前提是它确实是keyword类型。
真正难调的是时间戳对齐——Go日志里的time字段如果没被Filebeat或Logstash校准成@timestamp,Kibana按时间轴查就会偏移。别依赖Go库自动格式,老老实实配processors做时间字段提取和转换。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











