Read docker containers logs in JSON format - Golang - docker

I have a requirement to fetch docker container's logs, I'm using below code to fetch docker logs,
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Since: "",
Until: "",
Timestamps: false,
Follow: true,
Tail: "",
Details: true,
}
out, err := cli.ContainerLogs(ctx, "bcd693465a62", options)
if err != nil {
panic(err)
}
buf := new(strings.Builder)
_, err = io.Copy(buf, out)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%s", buf)
My problem is I'm getting docker logs in two different format, for some containers it's just plain text
exec /entrypoint.sh: no such file or directory
but in containerID-json.log file it's showing in below format
{"log":"exec /entrypoint.sh: no such file or directory\n","stream":"stderr","time":"2022-09-06T12:23:57.741316145Z"}
and for some containers it's showing in dynamic format/schema like
time="2022-09-05T02:13:44Z" level=debug msg="cleanup aborting ingest" ref="buildkit/1/layer-sha256:2774afd0c4d3ded992c58f3b5e5939d091bd26f40e507c6dc21dcbd8b7ff486f"
time="2022-09-05T02:13:44Z" level=debug msg="content garbage collected" d=2.971574ms
How I can collect all docker container's logs in same schema/format so it can be stored in below JSON format?
{
"containerID": "bcd693465a62",
"logs": [
{"log":"exec /entrypoint.sh: no such file or directory\n","stream":"stderr","time":"2022-09-06T12:23:57.741316145Z"},
{"log":"cleanup aborting ingest","stream":"stdout","time":"2022-09-05T02:13:44Z"}
]
}
Any help is appreciated

I wrote below code with the help of code reference by #BrianWagner to fulfill my requirements (It works as expected), please suggest if it can be done better way, or if any improvements.
package main
import (
"context"
"encoding/binary"
"fmt"
"io"
"log"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
type DockerContainerLog struct {
ContainerID string `json:"containerID"`
DockerLogs []DockerLog `json:"dockerLogs"`
}
type DockerLog struct {
Time string `json:"time"`
Stream string `json:"stream"`
Log string `json:"log"`
}
func main() {
logs := getContainerLogs("cd3d8362ba45")
fmt.Print(logs)
}
func getContainerLogs(cid string) DockerContainerLog {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Since: "",
Until: "",
Timestamps: true,
Follow: true,
Tail: "",
Details: false,
}
reader, err := cli.ContainerLogs(context.Background(), cid, options)
if err != nil {
log.Fatal(err)
}
defer reader.Close()
dockerLogs := DockerContainerLog{ContainerID: cid}
hdr := make([]byte, 8)
for {
var docLog DockerLog
_, err := reader.Read(hdr)
if err != nil {
if err == io.EOF {
return dockerLogs
}
log.Fatal(err)
}
count := binary.BigEndian.Uint32(hdr[4:])
dat := make([]byte, count)
_, err = reader.Read(dat)
if err != nil && err != io.EOF {
log.Fatal(err)
}
time, log, found := strings.Cut(string(dat), " ")
if found {
docLog.Time = time
docLog.Log = log
switch hdr[0] {
case 1:
docLog.Stream = "Stdout"
default:
docLog.Stream = "Stderr"
}
dockerLogs.DockerLogs = append(dockerLogs.DockerLogs, docLog)
}
}
}

Related

How to pass `--gpus all` option to Docker with Go SDK?

I have seen how to do some basic commands such as running a container, pulling images, listing images, etc from the SDK examples.
I am working on a project where I need to use the GPU from within the container.
My system has GPU, I have installed the drivers, and I have also installed the nvidia-container-runtime.
If we remove Go SDK from the scene for a moment, I can run the following command to get the nvidia-smi output on my host system:
docker run -it --rm --gpus all nvidia/cuda:10.0-base nvidia-smi
I have to do this via the SDK. Here is the code to start with. This code prints "hello world". But in actual I will be running nvidia-smi command at that place:
package main
import (
"context"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
)
func main() {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
panic(err)
}
RunContainer(ctx, cli)
}
func RunContainer(ctx context.Context, cli *client.Client) {
reader, err := cli.ImagePull(ctx, "nvidia/cuda:10.0-base", types.ImagePullOptions{})
if err != nil {
panic(err)
}
defer reader.Close()
// io.Copy(os.Stdout, reader)
resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: "nvidia/cuda:10.0-base",
Cmd: []string{"echo", "hello world"},
// Tty: false,
}, nil, nil, nil, "")
if err != nil {
panic(err)
}
if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
panic(err)
}
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
select {
case err := <-errCh:
if err != nil {
panic(err)
}
case <-statusCh:
}
out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true})
if err != nil {
panic(err)
}
stdcopy.StdCopy(os.Stdout, os.Stderr, out)
}
see: https://github.com/docker/cli/blob/9ac8584acfd501c3f4da0e845e3a40ed15c85041/cli/command/container/opts.go#L594
import "github.com/docker/cli/opts"
// ...
gpuOpts := opts.GpuOpts{}
gpuOpts.Set("all")
resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: "nvidia/cuda:10.0-base",
Cmd: []string{"echo", "hello world"},
// Tty: false,
}, &container.HostConfig{Resources: container.Resources{DeviceRequests: gpuOpts.Value()}}, nil, nil, "")

Copy a directory recursively from a container to host using docker client go library

I want to recursively copy a directory from a container on the host.
I'm new to golang and fully aware about docker cp as an alternative but i need to do it via client go library of docker
I was able to copy a file from a container to host, but unable to do so for directories.
Here's what i have till now
package main
import (
"archive/tar"
"context"
"fmt"
"io"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
)
func main() {
sourcePath := "/test/file.txt"
destinationPath := "/home/user/"
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
myContainer, err := cli.ContainerCreate(context.Background(), &container.Config{
Image: "alpine:test",
}, nil, nil, nil,"")
if err != nil {
fmt.Println(err.Error(), "error occurred")
}
tarStream, _, err := cli.CopyFromContainer(context.Background(), myContainer.ID, sourcePath)
if err != nil {
fmt.Println("something went wrong", err)
}
tr := tar.NewReader(tarStream)
if _, err := tr.Next(); err != nil {
panic(err)
}
dst, err := os.Create(destinationPath + "file.txt")
if err != nil {
fmt.Println("error occurred", err)
}
defer dst.Close()
_, err = io.Copy(dst, tr)
if err != nil {
fmt.Println("error", err)
}
cli.ContainerRemove(context.Background(), myContainer.ID, types.ContainerRemoveOptions{})
}
But actually i want to copy the whole directory inside the container /test/ recursively on the host.

Docker exec command from golang api

Need help.I have code to exec command from docker container. Need corrently get stdout from exec command.
execConfig:= types.ExecConfig{Tty:false,AttachStdout:true,AttachStderr:false,Cmd:command}
respIdExecCreate,err := cli.ContainerExecCreate(context.Background(),dockerName,execConfig)
if err != nil {
fmt.Println(err)
}
respId,err:=cli.ContainerExecAttach(context.Background(),respIdExecCreate.ID,types.ExecStartCheck{})
if err != nil {
fmt.Println(err)
}
scanner := bufio.NewScanner(respId.Reader)
for scanner.Scan() {
fmt.Println(output)
}
From output i see interesting situation:
Screen from gyazo
How corrently remove bytes ?
I send simply command := []string{"echo","-n", "hello word"}
I've faced with same issue, this is how stderr and stdout looks for me:
StdOut: "\x01\x00\x00\x00\x00\x00\x00\thello world\n"
StdErr: "\x01\x00\x00\x00\x00\x00\x00fError: Exec command has already run\r\n"
I've cheched docker source code and found answer here:
https://github.com/moby/moby/blob/8e610b2b55bfd1bfa9436ab110d311f5e8a74dcb/integration/internal/container/exec.go#L38
looks like this leading bytes used especially for marking stdout and stderr bytes.
And there is a library "github.com/docker/docker/pkg/stdcopy" which can split stdout and stderr from stream reader:
type ExecResult struct {
StdOut string
StdErr string
ExitCode int
}
func Exec(ctx context.Context, containerID string, command []string) (types.IDResponse, error) {
docker, err := client.NewEnvClient()
if err != nil {
return types.IDResponse{}, err
}
defer closer(docker)
config := types.ExecConfig{
AttachStderr: true,
AttachStdout: true,
Cmd: command,
}
return docker.ContainerExecCreate(ctx, containerID, config)
}
func InspectExecResp(ctx context.Context, id string) (ExecResult, error) {
var execResult ExecResult
docker, err := client.NewEnvClient()
if err != nil {
return execResult, err
}
defer closer(docker)
resp, err := docker.ContainerExecAttach(ctx, id, types.ExecConfig{})
if err != nil {
return execResult, err
}
defer resp.Close()
// read the output
var outBuf, errBuf bytes.Buffer
outputDone := make(chan error)
go func() {
// StdCopy demultiplexes the stream into two buffers
_, err = stdcopy.StdCopy(&outBuf, &errBuf, resp.Reader)
outputDone <- err
}()
select {
case err := <-outputDone:
if err != nil {
return execResult, err
}
break
case <-ctx.Done():
return execResult, ctx.Err()
}
stdout, err := ioutil.ReadAll(&outBuf)
if err != nil {
return execResult, err
}
stderr, err := ioutil.ReadAll(&errBuf)
if err != nil {
return execResult, err
}
res, err := docker.ContainerExecInspect(ctx, id)
if err != nil {
return execResult, err
}
execResult.ExitCode = res.ExitCode
execResult.StdOut = string(stdout)
execResult.StdErr = string(stderr)
return execResult, nil
}

Programmatically check if Docker container process ended with non-zero status

I'm working on a Go application which starts some Docker containers using Go Docker SDK. I need to check if containers' processes exit with zero (success) status code.
Here's the minimal working example:
package main
import (
"context"
"io"
"log"
"os"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
)
func main() {
ctx := context.Background()
cli, err := client.NewEnvClient()
if err != nil {
log.Fatal(err)
}
reader, err := cli.ImagePull(
ctx,
"docker.io/library/alpine",
types.ImagePullOptions{},
)
if err != nil {
log.Fatal(err)
}
io.Copy(os.Stdout, reader)
resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: "alpine",
Cmd: []string{"sh", "-c", "echo hello world; return 1"},
Tty: true,
}, nil, nil, "")
if err != nil {
log.Fatal(err)
}
err = cli.ContainerStart(
ctx,
resp.ID,
types.ContainerStartOptions{},
)
if err != nil {
log.Fatal(err)
}
statusCh, errCh := cli.ContainerWait(
ctx,
resp.ID,
container.WaitConditionNotRunning,
)
select {
case err := <-errCh:
if err != nil {
log.Fatal(err)
}
case <-statusCh:
}
out, err := cli.ContainerLogs(
ctx,
resp.ID,
types.ContainerLogsOptions{ShowStdout: true},
)
if err != nil {
log.Fatal(err)
}
io.Copy(os.Stdout, out)
}
As you can see, the process in the container ends with non-zero status (sh -c "echo hello world; return 1"). However, it doesn't log any fatal errors and simply displays hello world when built and executed:
{"status":"Pulling from library/alpine","id":"latest"}
{"status":"Digest: sha256:7043076348bf5040220df6ad703798fd8593a0918d06d3ce30c6c93be117e430"}
{"status":"Status: Image is up to date for alpine:latest"}
hello world
How can I check that container process exited with non-zero status using Docker Go SDK?
I think you should use the status channel to get the exit code. The error channel seems to be used to signal if there was an error while talking to the docker daemon, see https://godoc.org/github.com/docker/docker/client#Client.ContainerWait.
This works for me:
select {
case err := <-errCh:
if err != nil {
log.Fatal(err)
}
case status := <-statusCh:
log.Printf("status.StatusCode: %#+v\n", status.StatusCode)
}

How can I get Docker container output through a websocket?

I am trying to send output from a docker container to the console using fmt, but when trying to do it i get this.
&{0xc0422a65c0 {0 0} false <nil> 0x6415a0 0x641540}
How do I do this? This is my full code.
func main() {
imageName := "hidden/hidden"
ctx := context.Background()
cli, err := client.NewClient("tcp://0.0.0.0:0000", "v0.00", nil, nil)
if err != nil {
panic(err)
}
fmt.Println("Pulling \"" + imageName + "\"")
_, err = cli.ImagePull(ctx, imageName, types.ImagePullOptions{})
if err != nil {
panic(err)
}
containerConfig := &container.Config{
Image: imageName,
Cmd: []string{"./app/start.sh", "--no-wizard"},
}
resp, err := cli.ContainerCreate(ctx, containerConfig, nil, nil, "")
if err != nil {
panic(err)
}
if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
panic(err)
}
timer := time.NewTimer(time.Minute)
go func() {
<-timer.C
if err := cli.ContainerStop(ctx, resp.ID, nil); err != nil {
panic(err)
}
}()
out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true})
if err != nil {
panic(err)
}
io.Copy(os.Stdout, out) // This is what I want to change to print with "fmt".
}
Tried: (but does not display until container is done.)
buf := new(bytes.Buffer)
buf.ReadFrom(out)
fmt.Println(buf.String())
Intention: Allow real-time console output to the web.
This seems to be the answer to my question, I did some searching about scanners as Cerise Limón commented. Anyone else who seems to be having the issue that I did can use this code. Thanks to all that helped.
scanner := bufio.NewScanner(out)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
&{0xc0422a65c0 {0 0} false <nil> 0x6415a0 0x641540} is not a bad output. This is a perfectly fine struct output. I think the the main problem is here just your lack of golang experience.
I'm a beginner as well and I can imagine that when you see an output like above you thought that "I made a mistake"
No you didn't. It's default fmt behavior when u try to print out a struct which contains pointers.
Checkout this instead of your fmt.Println:
fmt.Printf("%+v\n", out)
Well this answer stands on my assumptions but if this is the case just ping me for more info.

Resources