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.