Reading docker container logs with Go

I recently added a new feature to Gnomock: an option to forward docker container logs to a user-provided io.Writer implementation.

In short, Gnomock spins up popular tools inside temporary docker containers and allows Go applications to run tests against them very easily. Each supported 3rd party tool is a Preset, and anybody can implement any preset they want. Hiding complexity behind starting, setting up and taking down containers has a downside for preset development: when things fail, it is unclear what is wrong.

I stumbled upon this issue very early in Gnomock’s life: when implementing MySQL preset. It uses official MySQL docker image, which requires to set MYSQL_ROOT_PASSWORD for the container to work. While working on healthcheck implementation for this preset, I got stuck while trying to open a connection to a fresh database. I saw that the container appeared in docker container ls, but the preset simply didn’t work. I then used docker container logs mysql-container to see the following:

...
You need to specify one of MYSQL_ROOT_PASSWORD,
MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD

If Gnomock could display these logs automatically, on demand, without the need to manually view container logs, that would be great! Docker SDK for Go supported viewing container logs, so the task seemed easy:

reader, err := client.ContainerLogs(
		ctx, "container_id", types.ContainerLogsOptions{}
)
if err != nil {
	log.Fatal(err)
}

_, err = io.Copy(os.Stdout, reader)
if err != nil && err != io.EOF {
	log.Fatal(err)
}

What would be printed to os.Stdout in this case was the logs plus some other bytes that have special meaning to Docker, like which stream do these logs belong to. In my terminal, it looked like logs surrounded by lots of white space.

To resolve the issue, I used stdcopy package, that includes a function to strip these bytes and copy actual logs only:

reader, err := client.ContainerLogs(
		ctx, "container_id", types.ContainerLogsOptions{}
)
if err != nil {
	log.Fatal(err)
}

// StdCopy is very similar to io.Copy
_, err := stdcopy.StdCopy(dst, dst, src)
if err != nil && err != io.EOF {
	log.Fatal(err)
}

After this change, only the actual container logs started showing up.

To summarize, you should use stdcopy.StdCopy function from Docker SDK for Go to get the actual log entries from raw container logs (retrieved by client.ContainerLogs() or other Docker SDK functions). It will strip unnecessary characters from the output, and allow you to view the actual, clean logs from your docker containers.