github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/daemon/logs.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"context"
     5  	"strconv"
     6  	"time"
     7  
     8  	"github.com/docker/docker/api/types"
     9  	"github.com/docker/docker/api/types/backend"
    10  	containertypes "github.com/docker/docker/api/types/container"
    11  	timetypes "github.com/docker/docker/api/types/time"
    12  	"github.com/docker/docker/container"
    13  	"github.com/docker/docker/daemon/logger"
    14  	logcache "github.com/docker/docker/daemon/logger/loggerutils/cache"
    15  	"github.com/docker/docker/errdefs"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  // ContainerLogs copies the container's log channel to the channel provided in
    21  // the config. If ContainerLogs returns an error, no messages have been copied.
    22  // and the channel will be closed without data.
    23  //
    24  // if it returns nil, the config channel will be active and return log
    25  // messages until it runs out or the context is canceled.
    26  func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *types.ContainerLogsOptions) (messages <-chan *backend.LogMessage, isTTY bool, retErr error) {
    27  	lg := logrus.WithFields(logrus.Fields{
    28  		"module":    "daemon",
    29  		"method":    "(*Daemon).ContainerLogs",
    30  		"container": containerName,
    31  	})
    32  
    33  	if !(config.ShowStdout || config.ShowStderr) {
    34  		return nil, false, errdefs.InvalidParameter(errors.New("You must choose at least one stream"))
    35  	}
    36  	ctr, err := daemon.GetContainer(containerName)
    37  	if err != nil {
    38  		return nil, false, err
    39  	}
    40  
    41  	if ctr.RemovalInProgress || ctr.Dead {
    42  		return nil, false, errdefs.Conflict(errors.New("can not get logs from container which is dead or marked for removal"))
    43  	}
    44  
    45  	if ctr.HostConfig.LogConfig.Type == "none" {
    46  		return nil, false, logger.ErrReadLogsNotSupported{}
    47  	}
    48  
    49  	cLog, cLogCreated, err := daemon.getLogger(ctr)
    50  	if err != nil {
    51  		return nil, false, err
    52  	}
    53  	if cLogCreated {
    54  		defer func() {
    55  			if retErr != nil {
    56  				if err = cLog.Close(); err != nil {
    57  					logrus.Errorf("Error closing logger: %v", err)
    58  				}
    59  			}
    60  		}()
    61  	}
    62  
    63  	logReader, ok := cLog.(logger.LogReader)
    64  	if !ok {
    65  		return nil, false, logger.ErrReadLogsNotSupported{}
    66  	}
    67  
    68  	follow := config.Follow && !cLogCreated
    69  	tailLines, err := strconv.Atoi(config.Tail)
    70  	if err != nil {
    71  		tailLines = -1
    72  	}
    73  
    74  	var since time.Time
    75  	if config.Since != "" {
    76  		s, n, err := timetypes.ParseTimestamps(config.Since, 0)
    77  		if err != nil {
    78  			return nil, false, err
    79  		}
    80  		since = time.Unix(s, n)
    81  	}
    82  
    83  	var until time.Time
    84  	if config.Until != "" && config.Until != "0" {
    85  		s, n, err := timetypes.ParseTimestamps(config.Until, 0)
    86  		if err != nil {
    87  			return nil, false, err
    88  		}
    89  		until = time.Unix(s, n)
    90  	}
    91  
    92  	readConfig := logger.ReadConfig{
    93  		Since:  since,
    94  		Until:  until,
    95  		Tail:   tailLines,
    96  		Follow: follow,
    97  	}
    98  
    99  	logs := logReader.ReadLogs(readConfig)
   100  
   101  	// past this point, we can't possibly return any errors, so we can just
   102  	// start a goroutine and return to tell the caller not to expect errors
   103  	// (if the caller wants to give up on logs, they have to cancel the context)
   104  	// this goroutine functions as a shim between the logger and the caller.
   105  	messageChan := make(chan *backend.LogMessage, 1)
   106  	go func() {
   107  		if cLogCreated {
   108  			defer func() {
   109  				if err = cLog.Close(); err != nil {
   110  					logrus.Errorf("Error closing logger: %v", err)
   111  				}
   112  			}()
   113  		}
   114  		// signal that the log reader is gone
   115  		defer logs.ConsumerGone()
   116  
   117  		// close the messages channel. closing is the only way to signal above
   118  		// that we're doing with logs (other than context cancel i guess).
   119  		defer close(messageChan)
   120  
   121  		lg.Debug("begin logs")
   122  		defer lg.Debugf("end logs (%v)", ctx.Err())
   123  
   124  		for {
   125  			select {
   126  			// i do not believe as the system is currently designed any error
   127  			// is possible, but we should be prepared to handle it anyway. if
   128  			// we do get an error, copy only the error field to a new object so
   129  			// we don't end up with partial data in the other fields
   130  			case err := <-logs.Err:
   131  				lg.Errorf("Error streaming logs: %v", err)
   132  				select {
   133  				case <-ctx.Done():
   134  				case messageChan <- &backend.LogMessage{Err: err}:
   135  				}
   136  				return
   137  			case <-ctx.Done():
   138  				return
   139  			case msg, ok := <-logs.Msg:
   140  				// there is some kind of pool or ring buffer in the logger that
   141  				// produces these messages, and a possible future optimization
   142  				// might be to use that pool and reuse message objects
   143  				if !ok {
   144  					return
   145  				}
   146  				m := msg.AsLogMessage() // just a pointer conversion, does not copy data
   147  
   148  				// there could be a case where the reader stops accepting
   149  				// messages and the context is canceled. we need to check that
   150  				// here, or otherwise we risk blocking forever on the message
   151  				// send.
   152  				select {
   153  				case <-ctx.Done():
   154  					return
   155  				case messageChan <- m:
   156  				}
   157  			}
   158  		}
   159  	}()
   160  	return messageChan, ctr.Config.Tty, nil
   161  }
   162  
   163  func (daemon *Daemon) getLogger(container *container.Container) (l logger.Logger, created bool, err error) {
   164  	container.Lock()
   165  	if container.State.Running {
   166  		l = container.LogDriver
   167  	}
   168  	container.Unlock()
   169  	if l == nil {
   170  		created = true
   171  		l, err = container.StartLogger()
   172  	}
   173  	return
   174  }
   175  
   176  // mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified.
   177  func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error {
   178  	if cfg.Type == "" {
   179  		cfg.Type = daemon.defaultLogConfig.Type
   180  	}
   181  
   182  	if cfg.Config == nil {
   183  		cfg.Config = make(map[string]string)
   184  	}
   185  
   186  	if cfg.Type == daemon.defaultLogConfig.Type {
   187  		for k, v := range daemon.defaultLogConfig.Config {
   188  			if _, ok := cfg.Config[k]; !ok {
   189  				cfg.Config[k] = v
   190  			}
   191  		}
   192  	}
   193  
   194  	logcache.MergeDefaultLogConfig(cfg.Config, daemon.defaultLogConfig.Config)
   195  
   196  	return logger.ValidateLogOpts(cfg.Type, cfg.Config)
   197  }
   198  
   199  func (daemon *Daemon) setupDefaultLogConfig() error {
   200  	config := daemon.configStore
   201  	if len(config.LogConfig.Config) > 0 {
   202  		if err := logger.ValidateLogOpts(config.LogConfig.Type, config.LogConfig.Config); err != nil {
   203  			return errors.Wrap(err, "failed to set log opts")
   204  		}
   205  	}
   206  	daemon.defaultLogConfig = containertypes.LogConfig{
   207  		Type:   config.LogConfig.Type,
   208  		Config: config.LogConfig.Config,
   209  	}
   210  
   211  	logrus.Debugf("Using default logging driver %s", daemon.defaultLogConfig.Type)
   212  	return nil
   213  }