gopkg.in/docker/docker.v20@v20.10.27/integration-cli/docker_api_logs_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/docker/docker/api/types"
    16  	"github.com/docker/docker/client"
    17  	"github.com/docker/docker/pkg/stdcopy"
    18  	"github.com/docker/docker/testutil/request"
    19  	"gotest.tools/v3/assert"
    20  )
    21  
    22  func (s *DockerSuite) TestLogsAPIWithStdout(c *testing.T) {
    23  	out, _ := dockerCmd(c, "run", "-d", "-t", "busybox", "/bin/sh", "-c", "while true; do echo hello; sleep 1; done")
    24  	id := strings.TrimSpace(out)
    25  	assert.NilError(c, waitRun(id))
    26  
    27  	type logOut struct {
    28  		out string
    29  		err error
    30  	}
    31  
    32  	chLog := make(chan logOut, 1)
    33  	res, body, err := request.Get(fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&timestamps=1", id))
    34  	assert.NilError(c, err)
    35  	assert.Equal(c, res.StatusCode, http.StatusOK)
    36  
    37  	go func() {
    38  		defer body.Close()
    39  		out, err := bufio.NewReader(body).ReadString('\n')
    40  		if err != nil {
    41  			chLog <- logOut{"", err}
    42  			return
    43  		}
    44  		chLog <- logOut{strings.TrimSpace(out), err}
    45  	}()
    46  
    47  	select {
    48  	case l := <-chLog:
    49  		assert.NilError(c, l.err)
    50  		if !strings.HasSuffix(l.out, "hello") {
    51  			c.Fatalf("expected log output to container 'hello', but it does not")
    52  		}
    53  	case <-time.After(30 * time.Second):
    54  		c.Fatal("timeout waiting for logs to exit")
    55  	}
    56  }
    57  
    58  func (s *DockerSuite) TestLogsAPINoStdoutNorStderr(c *testing.T) {
    59  	name := "logs_test"
    60  	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
    61  	cli, err := client.NewClientWithOpts(client.FromEnv)
    62  	assert.NilError(c, err)
    63  	defer cli.Close()
    64  
    65  	_, err = cli.ContainerLogs(context.Background(), name, types.ContainerLogsOptions{})
    66  	assert.ErrorContains(c, err, "Bad parameters: you must choose at least one stream")
    67  }
    68  
    69  // Regression test for #12704
    70  func (s *DockerSuite) TestLogsAPIFollowEmptyOutput(c *testing.T) {
    71  	name := "logs_test"
    72  	t0 := time.Now()
    73  	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "sleep", "10")
    74  
    75  	_, body, err := request.Get(fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&stderr=1&tail=all", name))
    76  	t1 := time.Now()
    77  	assert.NilError(c, err)
    78  	body.Close()
    79  	elapsed := t1.Sub(t0).Seconds()
    80  	if elapsed > 20.0 {
    81  		c.Fatalf("HTTP response was not immediate (elapsed %.1fs)", elapsed)
    82  	}
    83  }
    84  
    85  func (s *DockerSuite) TestLogsAPIContainerNotFound(c *testing.T) {
    86  	name := "nonExistentContainer"
    87  	resp, _, err := request.Get(fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&stderr=1&tail=all", name))
    88  	assert.NilError(c, err)
    89  	assert.Equal(c, resp.StatusCode, http.StatusNotFound)
    90  }
    91  
    92  func (s *DockerSuite) TestLogsAPIUntilFutureFollow(c *testing.T) {
    93  	testRequires(c, DaemonIsLinux)
    94  	name := "logsuntilfuturefollow"
    95  	dockerCmd(c, "run", "-d", "--name", name, "busybox", "/bin/sh", "-c", "while true; do date +%s; sleep 1; done")
    96  	assert.NilError(c, waitRun(name))
    97  
    98  	untilSecs := 5
    99  	untilDur, err := time.ParseDuration(fmt.Sprintf("%ds", untilSecs))
   100  	assert.NilError(c, err)
   101  	until := daemonTime(c).Add(untilDur)
   102  
   103  	client, err := client.NewClientWithOpts(client.FromEnv)
   104  	if err != nil {
   105  		c.Fatal(err)
   106  	}
   107  
   108  	cfg := types.ContainerLogsOptions{Until: until.Format(time.RFC3339Nano), Follow: true, ShowStdout: true, Timestamps: true}
   109  	reader, err := client.ContainerLogs(context.Background(), name, cfg)
   110  	assert.NilError(c, err)
   111  
   112  	type logOut struct {
   113  		out string
   114  		err error
   115  	}
   116  
   117  	chLog := make(chan logOut)
   118  	stop := make(chan struct{})
   119  	defer close(stop)
   120  
   121  	go func() {
   122  		bufReader := bufio.NewReader(reader)
   123  		defer reader.Close()
   124  		for i := 0; i < untilSecs; i++ {
   125  			out, _, err := bufReader.ReadLine()
   126  			if err != nil {
   127  				if err == io.EOF {
   128  					return
   129  				}
   130  				select {
   131  				case <-stop:
   132  					return
   133  				case chLog <- logOut{"", err}:
   134  				}
   135  
   136  				return
   137  			}
   138  
   139  			select {
   140  			case <-stop:
   141  				return
   142  			case chLog <- logOut{strings.TrimSpace(string(out)), err}:
   143  			}
   144  		}
   145  	}()
   146  
   147  	for i := 0; i < untilSecs; i++ {
   148  		select {
   149  		case l := <-chLog:
   150  			assert.NilError(c, l.err)
   151  			i, err := strconv.ParseInt(strings.Split(l.out, " ")[1], 10, 64)
   152  			assert.NilError(c, err)
   153  			assert.Assert(c, time.Unix(i, 0).UnixNano() <= until.UnixNano())
   154  		case <-time.After(20 * time.Second):
   155  			c.Fatal("timeout waiting for logs to exit")
   156  		}
   157  	}
   158  }
   159  
   160  func (s *DockerSuite) TestLogsAPIUntil(c *testing.T) {
   161  	testRequires(c, MinimumAPIVersion("1.34"))
   162  	name := "logsuntil"
   163  	dockerCmd(c, "run", "--name", name, "busybox", "/bin/sh", "-c", "for i in $(seq 1 3); do echo log$i; sleep 1; done")
   164  
   165  	client, err := client.NewClientWithOpts(client.FromEnv)
   166  	if err != nil {
   167  		c.Fatal(err)
   168  	}
   169  
   170  	extractBody := func(c *testing.T, cfg types.ContainerLogsOptions) []string {
   171  		reader, err := client.ContainerLogs(context.Background(), name, cfg)
   172  		assert.NilError(c, err)
   173  
   174  		actualStdout := new(bytes.Buffer)
   175  		actualStderr := io.Discard
   176  		_, err = stdcopy.StdCopy(actualStdout, actualStderr, reader)
   177  		assert.NilError(c, err)
   178  
   179  		return strings.Split(actualStdout.String(), "\n")
   180  	}
   181  
   182  	// Get timestamp of second log line
   183  	allLogs := extractBody(c, types.ContainerLogsOptions{Timestamps: true, ShowStdout: true})
   184  	assert.Assert(c, len(allLogs) >= 3)
   185  
   186  	t, err := time.Parse(time.RFC3339Nano, strings.Split(allLogs[1], " ")[0])
   187  	assert.NilError(c, err)
   188  	until := t.Format(time.RFC3339Nano)
   189  
   190  	// Get logs until the timestamp of second line, i.e. first two lines
   191  	logs := extractBody(c, types.ContainerLogsOptions{Timestamps: true, ShowStdout: true, Until: until})
   192  
   193  	// Ensure log lines after cut-off are excluded
   194  	logsString := strings.Join(logs, "\n")
   195  	assert.Assert(c, !strings.Contains(logsString, "log3"), "unexpected log message returned, until=%v", until)
   196  }
   197  
   198  func (s *DockerSuite) TestLogsAPIUntilDefaultValue(c *testing.T) {
   199  	name := "logsuntildefaultval"
   200  	dockerCmd(c, "run", "--name", name, "busybox", "/bin/sh", "-c", "for i in $(seq 1 3); do echo log$i; done")
   201  
   202  	client, err := client.NewClientWithOpts(client.FromEnv)
   203  	if err != nil {
   204  		c.Fatal(err)
   205  	}
   206  
   207  	extractBody := func(c *testing.T, cfg types.ContainerLogsOptions) []string {
   208  		reader, err := client.ContainerLogs(context.Background(), name, cfg)
   209  		assert.NilError(c, err)
   210  
   211  		actualStdout := new(bytes.Buffer)
   212  		actualStderr := io.Discard
   213  		_, err = stdcopy.StdCopy(actualStdout, actualStderr, reader)
   214  		assert.NilError(c, err)
   215  
   216  		return strings.Split(actualStdout.String(), "\n")
   217  	}
   218  
   219  	// Get timestamp of second log line
   220  	allLogs := extractBody(c, types.ContainerLogsOptions{Timestamps: true, ShowStdout: true})
   221  
   222  	// Test with default value specified and parameter omitted
   223  	defaultLogs := extractBody(c, types.ContainerLogsOptions{Timestamps: true, ShowStdout: true, Until: "0"})
   224  	assert.DeepEqual(c, defaultLogs, allLogs)
   225  }