| package client | |
| import ( | |
| "context" | |
| "fmt" | |
| "io" | |
| "net/url" | |
| "time" | |
| "github.com/moby/moby/client/internal/timestamp" | |
| ) | |
| // ContainerLogsOptions holds parameters to filter logs with. | |
| type ContainerLogsOptions struct { | |
| ShowStdout bool | |
| ShowStderr bool | |
| Since string | |
| Until string | |
| Timestamps bool | |
| Follow bool | |
| Tail string | |
| Details bool | |
| } | |
| // ContainerLogsResult is the result of a container logs operation. | |
| type ContainerLogsResult interface { | |
| io.ReadCloser | |
| } | |
| // ContainerLogs returns the logs generated by a container in an [io.ReadCloser]. | |
| // It's up to the caller to close the stream. | |
| // | |
| // The underlying [io.ReadCloser] is automatically closed if the context is canceled, | |
| // | |
| // The stream format on the response uses one of two formats: | |
| // | |
| // - If the container is using a TTY, there is only a single stream (stdout) | |
| // and data is copied directly from the container output stream, no extra | |
| // multiplexing or headers. | |
| // - If the container is *not* using a TTY, streams for stdout and stderr are | |
| // multiplexed. | |
| // | |
| // The format of the multiplexed stream is defined in the [stdcopy] package, | |
| // and as follows: | |
| // | |
| // [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}[]byte{OUTPUT} | |
| // | |
| // STREAM_TYPE can be 1 for [Stdout] and 2 for [Stderr]. Refer to [stdcopy.StdType] | |
| // for details. SIZE1, SIZE2, SIZE3, and SIZE4 are four bytes of uint32 encoded | |
| // as big endian, this is the size of OUTPUT. You can use [stdcopy.StdCopy] | |
| // to demultiplex this stream. | |
| // | |
| // [stdcopy]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy | |
| // [stdcopy.StdCopy]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdCopy | |
| // [stdcopy.StdType]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdType | |
| // [Stdout]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stdout | |
| // [Stderr]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stderr | |
| func (cli *Client) ContainerLogs(ctx context.Context, containerID string, options ContainerLogsOptions) (ContainerLogsResult, error) { | |
| containerID, err := trimID("container", containerID) | |
| if err != nil { | |
| return nil, err | |
| } | |
| query := url.Values{} | |
| if options.ShowStdout { | |
| query.Set("stdout", "1") | |
| } | |
| if options.ShowStderr { | |
| query.Set("stderr", "1") | |
| } | |
| if options.Since != "" { | |
| ts, err := timestamp.GetTimestamp(options.Since, time.Now()) | |
| if err != nil { | |
| return nil, fmt.Errorf(`invalid value for "since": %w`, err) | |
| } | |
| query.Set("since", ts) | |
| } | |
| if options.Until != "" { | |
| ts, err := timestamp.GetTimestamp(options.Until, time.Now()) | |
| if err != nil { | |
| return nil, fmt.Errorf(`invalid value for "until": %w`, err) | |
| } | |
| query.Set("until", ts) | |
| } | |
| if options.Timestamps { | |
| query.Set("timestamps", "1") | |
| } | |
| if options.Details { | |
| query.Set("details", "1") | |
| } | |
| if options.Follow { | |
| query.Set("follow", "1") | |
| } | |
| switch options.Tail { | |
| case "", "all": | |
| // don't send option; default is to show all logs. | |
| // | |
| // The default on the daemon-side is to show all logs; account for | |
| // some special values. The CLI may set a magic "all" value that's | |
| // used as default. | |
| // | |
| // Given that the default is to show all logs, we can ignore these | |
| // values, and don't send "tail". | |
| // | |
| // see https://github.com/moby/moby/blob/0df791cb72b568eeadba2267fe9a5040d12b0487/daemon/logs.go#L75-L78 | |
| // see https://github.com/moby/moby/blob/4d20b6fe56dfb2b06f4a5dd1f32913215a9c317b/daemon/cluster/services.go#L425-L449 | |
| default: | |
| query.Set("tail", options.Tail) | |
| } | |
| resp, err := cli.get(ctx, "/containers/"+containerID+"/logs", query, nil) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return &containerLogsResult{ | |
| ReadCloser: newCancelReadCloser(ctx, resp.Body), | |
| }, nil | |
| } | |
| type containerLogsResult struct { | |
| io.ReadCloser | |
| } | |
| var ( | |
| _ io.ReadCloser = (*containerLogsResult)(nil) | |
| _ ContainerLogsResult = (*containerLogsResult)(nil) | |
| ) |