github.com/yuyangjack/moby@v0.0.0-20190814082046-288be08dc2ec/daemon/logs.go (about)

     1  package daemon
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"strconv"
     7  	"time"
     8  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/sirupsen/logrus"
    12  	"github.com/docker/docker/api/types/backend"
    13  	containertypes "github.com/docker/docker/api/types/container"
    14  	timetypes "github.com/docker/docker/api/types/time"
    15  	"github.com/docker/docker/container"
    16  	"github.com/docker/docker/daemon/logger"
    17  	"github.com/docker/docker/pkg/ioutils"
    18  	"github.com/docker/docker/pkg/stdcopy"
    19  )
    20  
    21  // ContainerLogs hooks up a container's stdout and stderr streams
    22  // configured with the given struct.
    23  func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *backend.ContainerLogsConfig, started chan struct{}) error {
    24  	container, err := daemon.GetContainer(containerName)
    25  	if err != nil {
    26  		return err
    27  	}
    28  
    29  	if !(config.ShowStdout || config.ShowStderr) {
    30  		return fmt.Errorf("You must choose at least one stream")
    31  	}
    32  
    33  	cLog, err := daemon.getLogger(container)
    34  	if err != nil {
    35  		return err
    36  	}
    37  	logReader, ok := cLog.(logger.LogReader)
    38  	if !ok {
    39  		return logger.ErrReadLogsNotSupported
    40  	}
    41  
    42  	follow := config.Follow && container.IsRunning()
    43  	tailLines, err := strconv.Atoi(config.Tail)
    44  	if err != nil {
    45  		tailLines = -1
    46  	}
    47  
    48  	logrus.Debug("logs: begin stream")
    49  
    50  	var since time.Time
    51  	if config.Since != "" {
    52  		s, n, err := timetypes.ParseTimestamps(config.Since, 0)
    53  		if err != nil {
    54  			return err
    55  		}
    56  		since = time.Unix(s, n)
    57  	}
    58  	readConfig := logger.ReadConfig{
    59  		Since:  since,
    60  		Tail:   tailLines,
    61  		Follow: follow,
    62  	}
    63  	logs := logReader.ReadLogs(readConfig)
    64  
    65  	wf := ioutils.NewWriteFlusher(config.OutStream)
    66  	defer wf.Close()
    67  	close(started)
    68  	wf.Flush()
    69  
    70  	var outStream io.Writer
    71  	outStream = wf
    72  	errStream := outStream
    73  	if !container.Config.Tty {
    74  		errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
    75  		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
    76  	}
    77  
    78  	for {
    79  		select {
    80  		case err := <-logs.Err:
    81  			logrus.Errorf("Error streaming logs: %v", err)
    82  			return nil
    83  		case <-ctx.Done():
    84  			logs.Close()
    85  			return nil
    86  		case msg, ok := <-logs.Msg:
    87  			if !ok {
    88  				logrus.Debug("logs: end stream")
    89  				logs.Close()
    90  				if cLog != container.LogDriver {
    91  					// Since the logger isn't cached in the container, which occurs if it is running, it
    92  					// must get explicitly closed here to avoid leaking it and any file handles it has.
    93  					if err := cLog.Close(); err != nil {
    94  						logrus.Errorf("Error closing logger: %v", err)
    95  					}
    96  				}
    97  				return nil
    98  			}
    99  			logLine := msg.Line
   100  			if config.Details {
   101  				logLine = append([]byte(msg.Attrs.String()+" "), logLine...)
   102  			}
   103  			if config.Timestamps {
   104  				logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...)
   105  			}
   106  			if msg.Source == "stdout" && config.ShowStdout {
   107  				outStream.Write(logLine)
   108  			}
   109  			if msg.Source == "stderr" && config.ShowStderr {
   110  				errStream.Write(logLine)
   111  			}
   112  		}
   113  	}
   114  }
   115  
   116  func (daemon *Daemon) getLogger(container *container.Container) (logger.Logger, error) {
   117  	if container.LogDriver != nil && container.IsRunning() {
   118  		return container.LogDriver, nil
   119  	}
   120  	return container.StartLogger(container.HostConfig.LogConfig)
   121  }
   122  
   123  // mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified.
   124  func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error {
   125  	if cfg.Type == "" {
   126  		cfg.Type = daemon.defaultLogConfig.Type
   127  	}
   128  
   129  	if cfg.Config == nil {
   130  		cfg.Config = make(map[string]string)
   131  	}
   132  
   133  	if cfg.Type == daemon.defaultLogConfig.Type {
   134  		for k, v := range daemon.defaultLogConfig.Config {
   135  			if _, ok := cfg.Config[k]; !ok {
   136  				cfg.Config[k] = v
   137  			}
   138  		}
   139  	}
   140  
   141  	return logger.ValidateLogOpts(cfg.Type, cfg.Config)
   142  }