容器中 log.Println 日志不可见,因 Go 默认输出到 os.Stderr,需用 log.SetOutput(os.Stdout) 统一输出至 stdout/stderr 才能被 Docker/K8s 日志机制捕获并采集。
log.Println 的日志看不到?因为 Go 默认把日志写到 os.Stderr,而容器运行时(如 Docker)会把 stderr 和 stdout 重定向为流式输出。如果没配置日志驱动或没挂载日志收集器,这些日志就只是“飘过”,既不落盘
也不转发。
json-file 驱动,日志存在宿主机 /var/lib/docker/containers//-json.log ,但不可读、无结构、难过滤kubelet 读取容器的 stdout/stderr 流,再暴露给 kubectl logs —— 这只是临时查看,不是集中采集os.OpenFile),反而绕过了容器日志机制,导致日志彻底丢失log.SetOutput 统一输出到 os.Stdout 是最简方案别写文件,别开 goroutine 轮询,让容器运行时接管输出流。这是所有日志采集工具(Fluentd / Filebeat / Loki / Datadog Agent)能工作的前提。
package mainimport ( "log" "os" )
func main() { // 关键:强制所有 log.* 输出到 stdout log.SetOutput(os.Stdout) // 可选:加前缀让结构更清晰(但注意:这会让日志变非 JSON,影响解析) log.SetPrefix("[app] ") log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Println("service started")}
log.Printf 混合格式和内容;统一用 log.Print/log.Println,便于后续用正则或 parser 提取字段go.uber.org/zap 并配置 zapcore.Lock(os.Stdout)
纯 log 包做不到上下文透传,必须用支持 context 的日志库,否则每个请求的日志都是孤岛,无法关联分析。
zap + middleware 是主流组合:HTTP handler 中从请求提取 X-Request-ID,注入 context.Context,再传给 zap 的 With 或 Named
log.Printf("req_id=%s, msg=%s", reqID, msg) —— 这种字符串拼接破坏结构,且无法被 Loki 的 logfmt 或 ES 的 dissect 正确解析func LogWithReqID(reqID string, v ...interface{}) {
log.Printf("[req_id=%s] %v", reqID, fmt.Sprint(v...))
}但依然不推荐,缺失字段类型、易被截断、难做聚合统计它们不关心语言,只认输出位置和格式。关键配置点只有两个:源头路径 + 解析规则。
需指向 /var/lib/docker/containers/**/*-json.log,并启用 parse json 插件(Docker 的 json-file 驱动输出是每行一个 JSON 对象)/var/log/containers/*.log,该路径下是软链到 Docker JSON 日志,同样需开启 json.keys_under_root: true
zap 并启用了 zapcore.JSONEncoder,日志已经是合法 JSON 行,无需额外 parse;但如果用了 ConsoleEncoder,就得配 dissect 或 grok 规则,容易出错真正容易被忽略的是时间字段:Docker JSON 日志自带 time 字段(ISO8601 格式),但 Go 的 log.SetFlags(log.LstdFlags) 输出的是本地时区+无毫秒的时间字符串,和采集端时间戳对不上,查问题时序会乱。要么禁用 LstdFlags,要么统一用 UTC+毫秒的 JSON 时间字段。