Go服务必须输出单行合规JSON日志,采集端需正确处理多行panic和文件轮转。使用zerolog/zap/slog,字段用下划线命名、时间用RFC3339Nano,容器输出到stdout,K8s场景优先用Fluent Bit而非Filebeat或Logstash。

Go服务必须输出结构化JSON到stdout或文件,不能拼字符串
ELK无法解析log.Printf("user %d login", uid)这类日志——整行会被塞进message字段,查user_id、做聚合、按状态码统计全失效。真正能进Kibana做筛选的,只有字段明确、格式合法的JSON。
- 用
zerolog、zap或log/slog(Go 1.21+)替代标准log包 - 字段名必须是下划线命名:
"user_id"✅,"userId"❌(ES 7.0+默认禁用驼峰字段动态映射) - 时间字段必须设为
RFC3339Nano:zerolog.TimeFieldFormat = time.RFC3339Nano,否则Logstash的datefilter对不齐时间戳 - 容器内一律
SetOutput(os.Stdout);宿主机部署才考虑os.OpenFile写文件,且必须带os.O_APPEND | os.O_CREATE
Filebeat是唯一推荐的日志采集器,别碰Logstash file input
Logstash的file input在生产环境是反模式:轮转时丢日志、多实例竞争句柄、切文件瞬间漏行;而且ES 7.0+默认关闭_type,旧配置里if [type] == "go-app"会静默失效。
-
paths必须精确匹配日志路径,比如/var/log/myapp/*.log,注意宿主机上filebeat用户要有读权限 - panic堆栈是多行的,必须启用
multiline:multiline.pattern: '^panic:'+multiline.negate: true+multiline.match: before(用after会吞掉第一行) - 用
lumberjack轮转时,必须设force_close_files: true,否则轮转后Filebeat还盯着旧inode读,日志“消失” -
scan_frequency建议调成1s(默认10s),但别更低;配合close_inactive: 5m和clean_removed: false防删太快
Logstash配置里最容易错的三处filter和output
Logstash不是万能解析器,尤其面对Go输出的嵌套JSON日志:json filter默认只解一层,req.user.id会变成字符串字面量,而不是真正嵌套对象。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
jsonfilter必须显式指定source => "message",且Go日志必须已序列化为单行JSON(zerolog.NewConsoleWriter()默认带换行,要关掉) -
output { elasticsearch { hosts => ["http://es:9200"] } }必须用hosts(复数),写host(单数)在ES v8+会静默失败 - 如果用了
if [type] == "go-app"做条件路由,注意Logstash v7.0+默认禁用type字段,得在input里手动加type => "go-app"
Kubernetes场景下直接走stdout + Fluent Bit更稳
如果你的Go服务跑在K8s里,硬写文件再挂卷、配权限、处理轮转,纯属徒增故障点。Docker默认捕获stdout/stderr,经json-file驱动存为结构化JSON,Fluent Bit作为DaemonSet自动采集,比Filebeat更省心。
立即学习“go语言免费学习笔记(深入)”;
- Go侧只需初始化
zerolog.New(os.Stdout)或zap.NewStdLogAt(, zapcore.InfoLevel) - 中间件注入
trace_id和service字段,Fluent Bit会自动打标并转发 - Fluent Bit的
[INPUT]插件支持Parser和Filter,比Logstash轻量,资源占用低,且原生适配K8s元数据 - 不用改Go代码,也不用维护Filebeat配置;日志丢失风险远低于文件采集链路

















