github.com/openshift/moby-moby@v1.13.2-0.20170601211448-f5ec1e2936dc/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  	// Close logWatcher on exit
    65  	defer func() {
    66  		logs.Close()
    67  		if cLog != container.LogDriver {
    68  			// Since the logger isn't cached in the container, which
    69  			// occurs if it is running, it must get explicitly closed
    70  			// here to avoid leaking it and any file handles it has.
    71  			if err := cLog.Close(); err != nil {
    72  				logrus.Errorf("Error closing logger: %v", err)
    73  			}
    74  		}
    75  	}()
    76  
    77  	wf := ioutils.NewWriteFlusher(config.OutStream)
    78  	defer wf.Close()
    79  	close(started)
    80  	wf.Flush()
    81  
    82  	var outStream io.Writer
    83  	outStream = wf
    84  	errStream := outStream
    85  	if !container.Config.Tty {
    86  		errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
    87  		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
    88  	}
    89  
    90  	for {
    91  		select {
    92  		case err := <-logs.Err:
    93  			logrus.Errorf("Error streaming logs: %v", err)
    94  			return nil
    95  		case <-ctx.Done():
    96  			logrus.Debugf("logs: end stream, ctx is done: %v", ctx.Err())
    97  			return nil
    98  		case msg, ok := <-logs.Msg:
    99  			if !ok {
   100  				logrus.Debug("logs: end stream")
   101  				return nil
   102  			}
   103  			logLine := msg.Line
   104  			if config.Details {
   105  				logLine = append([]byte(msg.Attrs.String()+" "), logLine...)
   106  			}
   107  			if config.Timestamps {
   108  				logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...)
   109  			}
   110  			if msg.Source == "stdout" && config.ShowStdout {
   111  				outStream.Write(logLine)
   112  			}
   113  			if msg.Source == "stderr" && config.ShowStderr {
   114  				errStream.Write(logLine)
   115  			}
   116  		}
   117  	}
   118  }
   119  
   120  func (daemon *Daemon) getLogger(container *container.Container) (logger.Logger, error) {
   121  	if container.LogDriver != nil && container.IsRunning() {
   122  		return container.LogDriver, nil
   123  	}
   124  	return container.StartLogger(container.HostConfig.LogConfig)
   125  }
   126  
   127  // mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified.
   128  func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error {
   129  	if cfg.Type == "" {
   130  		cfg.Type = daemon.defaultLogConfig.Type
   131  	}
   132  
   133  	if cfg.Config == nil {
   134  		cfg.Config = make(map[string]string)
   135  	}
   136  
   137  	if cfg.Type == daemon.defaultLogConfig.Type {
   138  		for k, v := range daemon.defaultLogConfig.Config {
   139  			if _, ok := cfg.Config[k]; !ok {
   140  				cfg.Config[k] = v
   141  			}
   142  		}
   143  	}
   144  
   145  	return logger.ValidateLogOpts(cfg.Type, cfg.Config)
   146  }