Go服务日志必须写文件由Filebeat采集,禁用直连ELK;需用os.OpenFile配os.O_APPEND|os.O_CREATE并发安全写入,Filebeat通过multiline合并panic堆栈、force_close_files应对lumberjack轮转,并配置json.keys_under_root与ES keyword类型确保trace_id可查。

Go服务日志必须写文件,别直连ELK
Go程序本身没有内置HTTP或Logstash输出能力,logrus、zap这些库默认只支持写文件或os.Stdout,硬接ES或Logstash会卡协程、丢日志、甚至拖垮服务。生产环境唯一靠谱的路径是:用os.OpenFile打开带os.O_APPEND | os.O_CREATE标志的文件,让Filebeat去读。
关键点:
-
os.File本身并发安全,多个goroutine可直接f.Write(),但别用log.SetOutput塞io.MultiWriter套多个句柄——容易触发竞态 - 日志行末尾必须严格为
"\n",推荐fmt.Fprintf(f, "%s\n", msg),不用fmt.Fprintln(它可能多写一个\r\n在Windows上) - Filebeat用户(如
filebeat系统账户)必须对日志路径有read权限,否则paths: ["/var/log/myapp/*.log"]会静默失败
panic堆栈必须用multiline合并,否则查不到完整上下文
Go的panic输出是多行的,比如第一行panic: runtime error: invalid memory address,后面几十行goroutine 1 [running]:堆栈。Filebeat默认按行切分,每行变一条ES文档,你在Kibana里根本串不起来。
Filebeat配置要加:
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
multiline.pattern: '^panic:'—— 以panic:开头的行作为新块起点 -
multiline.negate: true—— 匹配不以panic:开头的行 -
multiline.match: before—— 把后续非panic行合并到前一个panic行,不是after(会吞掉第一行) - 测试时用
kill -SIGUSR1 $(pidof myapp)触发panic,再看Filebeat发出去的event是否是单条含完整堆栈的JSON
lumberjack轮转后Filebeat丢日志?force_close_files是关键
lumberjack.Logger负责日志轮转,但它切文件时只是重命名旧文件、新建空文件。Filebeat默认靠文件名跟踪,发现app.log还在就继续读,而新日志已写进app.log.2026-06-30-001,导致“日志消失”。
必须在Filebeat输入配置中启用:
-
force_close_files: true—— Filebeat检测到文件被rename或delete,立刻关闭旧inode句柄 -
close_inactive: 5m—— 文件5分钟没新内容就关,避免句柄堆积 -
clean_removed: false—— 不自动清理已删除文件的注册信息,防止轮转太快(MaxBackups: 1)导致旧文件被删但Filebeat还记着它 -
scan_frequency: 1s—— 默认10秒太慢,轮转快时会漏扫;别设低于1秒,频繁stat()伤性能
trace_id搜不到?先看ES字段类型和Filebeat解析方式
Go服务输出了trace_id字段,Kibana里trace_id: "abc123"却没结果,90%不是Go写得不对,而是ES或Filebeat没对上。
三个检查点:
- Filebeat配置必须有
json.keys_under_root: true和json.overwrite_keys: true,否则trace_id会藏在json.trace_id下,Kibana里搜不到 - Elasticsearch索引模板里
trace_id字段类型必须是keyword,不是text——否则被分词,精确匹配失效 - Kibana查询时,如果字段映射没定义
fields: { keyword: { type: keyword } },就得用trace_id.keyword: "abc123",不能只写trace_id: "abc123"
最易忽略的是:Filebeat的json处理器只对原始message字段生效,如果你的日志已经是结构化JSON且直接写入文件,就别在Logstash里再套一层json { source => "message" },重复解析会导致字段嵌套或丢失。

















