Beego需输出JSON日志才能接入ELK:通过自定义JSONWriter重写Write方法,序列化含timestamp(RFC3339)、level、message及app_name等字段的map;Filebeat需配置multiline合并多行日志;Logstash须先json解包再date解析;Kibana索引模式必须选@timestamp为时间字段。

Beego 默认日志输出是纯文本、无结构的,直接接入 ELK 会导致 Logstash 解析失败或字段缺失——必须先让 Beego 输出 JSON 格式日志,再配合 Filebeat 或 Logstash 的 json codec 才能真正打通。
Beego 如何输出结构化 JSON 日志
Beego 自带的 logs.BeeLogger 不支持原生 JSON 输出,也不能直接替换为 logrus 或 zap(因其内部日志调用链深度耦合)。可行路径只有一条:用 logs.SetLogger 注册自定义 writer,并在写入前序列化为 JSON。
- 不要试图修改
beego.BeeApp.Log的底层实现,它不暴露 Formatter 接口 - 必须重写
Write方法,在写入前把msg和level等元信息拼成 map,再用json.Marshal转换 - 注意时间字段必须用 RFC3339 格式(
time.Now().Format(time.RFC3339)),否则Logstash date插件无法解析 - 推荐额外注入
app_name、host、request_id(若已集成 middleware)等字段,便于 Kibana 多维筛选
示例关键片段:
type JSONWriter struct {
writer io.Writer
}
func (j *JSONWriter) Write(b []byte) (int, error) {
entry := map[string]interface{}{
"timestamp": time.Now().Format(time.RFC3339),
"level": "info", // 实际需从日志上下文提取
"message": strings.TrimSpace(string(b)),
"app_name": "my-beego-app",
"host": os.Getenv("HOSTNAME"),
}
data, _ := json.Marshal(entry)
return j.writer.Write(append(data, '\n'))
}
// 启用
logs.SetLogger(logs.AdapterConsole, `{"writer":"`+jsonWriter+`"}`)
Filebeat 配置要点(替代 Logstash input tcp)
用 Filebeat 替代直接 TCP 接入 Logstash 是生产首选——资源占用低、启动快、支持背压和断点续传。但 Beego 日志文件若未按行严格分隔(比如 panic 堆栈跨多行),Filebeat 默认会切碎日志。
- 必须在
filebeat.yml中启用multiline.pattern匹配开头时间戳,例如^{"timestamp" -
multiline.negate: true和multiline.match: after组合才能正确合并 JSON 日志块 - 输出目标建议直连 Elasticsearch(跳过 Logstash),除非你有强需求做 grok 解析或字段 enrichment
- 务必设置
fields.app_name: my-beego-app,这样 Kibana 可以统一按应用过滤,无需依赖日志内容提取
错误配置后果:单条 panic 日志被拆成 5 行,每行都作为独立文档入库,@timestamp 全部错乱,Kibana Discover 查不到完整堆栈。
Logstash pipeline 中 Beego 日志的常见 filter 陷阱
如果你仍选择走 Logstash(比如需要补全 IP 地理位置、脱敏敏感字段),filter 段不能直接对 message 做 grok ——Beego JSON 日志的 message 字段本身已是结构化字符串,grok 会失败。
- 第一步必须用
json { source => "message" }把整行 JSON 解包到事件顶层 - 第二步再用
date { match => ["timestamp", "ISO8601"] }覆盖@timestamp,否则默认用 Logstash 接收时间 - 如果 Beego 日志里混有非 JSON 行(如启动 banner、panic 前的 warning),要加
if ! [timestamp] { drop {} }过滤脏数据 - 避免在
output里硬编码index => "beego-logs-%{+YYYY.MM.dd}",应改用index => "%{[app_name]}-logs-%{+YYYY.MM.dd}",否则多个 Beego 服务日志会挤进同一个索引
Kibana 中 Beego 日志的索引模式与时间字段确认
即使日志成功入库,Kibana 里查不到数据,大概率是 Index Pattern 时间字段选错了。Beego JSON 日志里的 timestamp 是字符串,Elasticsearch 不会自动识别为时间类型。
- 创建 Index Pattern 时,必须手动指定
Time field为@timestamp,而不是timestamp - 如果误选了
timestamp,Kibana 会提示 “No results found”,且时间范围控件灰掉 - 验证方式:在 Dev Tools 控制台执行
GET /beego-logs-*/_mapping,检查@timestamp字段 type 是否为date,timestamp字段 type 是否为keyword(说明没被映射为 date) - 补救方法:删掉旧索引,修正 Logstash 的
datefilter 后重发;或用 Reindex API 将旧数据转存到新索引并重新映射
最易忽略的一点:Beego 日志中没有 request_id 字段时,排查单次 HTTP 请求的完整链路(含中间件、DB 查询、第三方调用)几乎不可能——这个字段必须在全局 middleware 中生成并注入到 logger context,不是可选项。


















