You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Golang统一Docker容器日志为指定JSON格式的方案咨询

统一Docker容器日志格式的Golang实现方案

问题根源

你遇到的格式差异来自三个核心原因:

  • Docker容器日志驱动配置:若容器使用json-file驱动,日志文件本身是结构化JSON,但直接读取API原始流会丢失元数据;
  • 容器应用输出格式:部分应用输出纯文本或自定义键值对格式,而非Docker标准JSON结构;
  • 当前代码未处理Docker API日志流的头部元数据:每个日志条目前有8字节头部(4字节内容长度+4字节流标识),导致无法区分stdout/stderr,也无法正确解析结构化内容。

实现步骤

要统一成目标JSON格式,需完成以下操作:

  1. 解析Docker API返回的日志流头部,区分stdout/stderr并提取原始日志内容;
  2. 针对不同日志格式,统一解析为包含log、stream、time的结构化条目;
  3. 将所有条目封装到指定的输出结构中,序列化为标准JSON。

完整代码示例

package main

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/client"
)

// 统一日志条目结构
type LogEntry struct {
	Log    string `json:"log"`
	Stream string `json:"stream"`
	Time   string `json:"time"`
}

// 最终输出结构
type ContainerLogResult struct {
	ContainerID string     `json:"containerID"`
	Logs        []LogEntry `json:"logs"`
}

// 解析Docker日志流头部,返回流类型和内容长度
func parseLogHeader(reader *bufio.Reader) (stream string, contentLength int, err error) {
	header := make([]byte, 8)
	_, err = io.ReadFull(reader, header)
	if err != nil {
		return "", 0, err
	}

	// 第5字节标识流类型:1=stdout,2=stderr
	switch header[4] {
	case 1:
		stream = "stdout"
	case 2:
		stream = "stderr"
	default:
		stream = "unknown"
	}

	// 前4字节为内容长度(大端序)
	contentLength = int(header[0])<<24 | int(header[1])<<16 | int(header[2])<<8 | int(header[3])
	return stream, contentLength, nil
}

// 解析单条日志内容,生成统一格式的LogEntry
func parseLogContent(content string, stream string) LogEntry {
	// 优先尝试解析为Docker标准JSON日志
	var dockerStdLog struct {
		Log    string `json:"log"`
		Stream string `json:"stream"`
		Time   string `json:"time"`
	}
	if err := json.Unmarshal([]byte(content), &dockerStdLog); err == nil && dockerStdLog.Log != "" {
		return LogEntry{
			Log:    dockerStdLog.Log,
			Stream: dockerStdLog.Stream,
			Time:   dockerStdLog.Time,
		}
	}

	// 处理自定义键值对格式日志(如time="..." msg="...")
	logEntry := LogEntry{
		Log:    content,
		Stream: stream,
		Time:   time.Now().Format(time.RFC3339Nano),
	}

	// 提取日志中的时间字段
	if timeIdx := strings.Index(content, `time="`); timeIdx != -1 {
		if endTimeIdx := strings.Index(content[timeIdx+6:], `"`); endTimeIdx != -1 {
			logEntry.Time = content[timeIdx+6 : timeIdx+6+endTimeIdx]
		}
	}

	// 提取日志中的msg字段作为主要内容
	if msgIdx := strings.Index(content, `msg="`); msgIdx != -1 {
		if endMsgIdx := strings.Index(content[msgIdx+5:], `"`); endMsgIdx != -1 {
			logEntry.Log = content[msgIdx+5 : msgIdx+5+endMsgIdx]
		}
	}

	return logEntry
}

func main() {
	ctx := context.Background()
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		panic(err)
	}
	defer cli.Close()

	containerID := "bcd693465a62"
	options := types.ContainerLogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Since:      "",
		Until:      "",
		Timestamps: false, // 关闭API自带时间戳,自行解析或生成
		Follow:     true,
		Tail:       "",
		Details:    false, // 关闭额外细节,降低解析复杂度
	}

	out, err := cli.ContainerLogs(ctx, containerID, options)
	if err != nil {
		panic(err)
	}
	defer out.Close()

	reader := bufio.NewReader(out)
	result := ContainerLogResult{
		ContainerID: containerID,
		Logs:        []LogEntry{},
	}

	for {
		stream, contentLen, err := parseLogHeader(reader)
		if err != nil {
			if err == io.EOF {
				break
			}
			fmt.Fprintf(os.Stderr, "解析日志头部失败: %v\n", err)
			continue
		}

		content := make([]byte, contentLen)
		_, err = io.ReadFull(reader, content)
		if err != nil {
			fmt.Fprintf(os.Stderr, "读取日志内容失败: %v\n", err)
			continue
		}

		logEntry := parseLogContent(string(content), stream)
		result.Logs = append(result.Logs, logEntry)

		// 实时输出统一格式的JSON(可替换为写入文件/数据库逻辑)
		jsonResult, _ := json.MarshalIndent(result, "", "    ")
		fmt.Println(string(jsonResult))
	}
}

关键逻辑说明

  1. 日志头部解析:通过parseLogHeader函数读取Docker API返回的8字节头部,区分日志来自stdout还是stderr,并获取日志内容的实际长度;
  2. 多格式兼容:
    • 优先解析Docker标准JSON日志,直接复用其log、stream、time字段;
    • 对键值对格式日志,提取time和msg字段填充到统一结构;
    • 纯文本日志直接使用原始内容作为log,流类型来自头部解析结果,时间默认使用当前时间;
  3. 实时处理:代码在每次解析完日志条目后,会序列化并输出完整结果,可根据需求修改为写入文件或存储到数据库。

内容的提问来源于stack exchange,提问作者Shri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 05:00:58