daemon/logs.go
4f0d95fa
 package daemon // import "github.com/docker/docker/daemon"
066330fc
 
 import (
7d62e40f
 	"context"
066330fc
 	"strconv"
06d8f504
 	"time"
066330fc
 
1044093b
 	"github.com/docker/docker/api/types"
06d8f504
 	"github.com/docker/docker/api/types/backend"
91e197d6
 	containertypes "github.com/docker/docker/api/types/container"
 	timetypes "github.com/docker/docker/api/types/time"
6bb0d181
 	"github.com/docker/docker/container"
9b782d3a
 	"github.com/docker/docker/daemon/logger"
d453fe35
 	"github.com/docker/docker/errdefs"
b0b9a25e
 	"github.com/pkg/errors"
1009e6a4
 	"github.com/sirupsen/logrus"
066330fc
 )
 
1044093b
 // ContainerLogs copies the container's log channel to the channel provided in
 // the config. If ContainerLogs returns an error, no messages have been copied.
 // and the channel will be closed without data.
 //
 // if it returns nil, the config channel will be active and return log
 // messages until it runs out or the context is canceled.
2c252a48
 func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *types.ContainerLogsOptions) (messages <-chan *backend.LogMessage, isTTY bool, retErr error) {
1044093b
 	lg := logrus.WithFields(logrus.Fields{
 		"module":    "daemon",
 		"method":    "(*Daemon).ContainerLogs",
 		"container": containerName,
 	})
 
05dc9846
 	if !(config.ShowStdout || config.ShowStderr) {
87a12421
 		return nil, false, errdefs.InvalidParameter(errors.New("You must choose at least one stream"))
05dc9846
 	}
d7d512bb
 	container, err := daemon.GetContainer(containerName)
1eecc1e7
 	if err != nil {
ebcb7d6b
 		return nil, false, err
1eecc1e7
 	}
 
238ad8c3
 	if container.RemovalInProgress || container.Dead {
87a12421
 		return nil, false, errdefs.Conflict(errors.New("can not get logs from container which is dead or marked for removal"))
238ad8c3
 	}
 
05dc9846
 	if container.HostConfig.LogConfig.Type == "none" {
ebcb7d6b
 		return nil, false, logger.ErrReadLogsNotSupported{}
066330fc
 	}
91bfed60
 
68e71aa3
 	cLog, cLogCreated, err := daemon.getLogger(container)
3a8728b4
 	if err != nil {
ebcb7d6b
 		return nil, false, err
9b782d3a
 	}
68e71aa3
 	if cLogCreated {
 		defer func() {
2c252a48
 			if retErr != nil {
 				if err = cLog.Close(); err != nil {
 					logrus.Errorf("Error closing logger: %v", err)
 				}
68e71aa3
 			}
 		}()
 	}
1044093b
 
c0391bf5
 	logReader, ok := cLog.(logger.LogReader)
9b782d3a
 	if !ok {
ebcb7d6b
 		return nil, false, logger.ErrReadLogsNotSupported{}
066330fc
 	}
e3ba3dd5
 
4fdb17c7
 	follow := config.Follow && !cLogCreated
c0391bf5
 	tailLines, err := strconv.Atoi(config.Tail)
 	if err != nil {
 		tailLines = -1
 	}
589de356
 
06d8f504
 	var since time.Time
 	if config.Since != "" {
 		s, n, err := timetypes.ParseTimestamps(config.Since, 0)
 		if err != nil {
ebcb7d6b
 			return nil, false, err
06d8f504
 		}
 		since = time.Unix(s, n)
 	}
1044093b
 
e8d9a61f
 	var until time.Time
 	if config.Until != "" && config.Until != "0" {
 		s, n, err := timetypes.ParseTimestamps(config.Until, 0)
 		if err != nil {
 			return nil, false, err
 		}
 		until = time.Unix(s, n)
 	}
 
c0391bf5
 	readConfig := logger.ReadConfig{
06d8f504
 		Since:  since,
e8d9a61f
 		Until:  until,
c0391bf5
 		Tail:   tailLines,
 		Follow: follow,
 	}
c2cf97a0
 
1044093b
 	logs := logReader.ReadLogs(readConfig)
ae4ee974
 
1044093b
 	// past this point, we can't possibly return any errors, so we can just
 	// start a goroutine and return to tell the caller not to expect errors
 	// (if the caller wants to give up on logs, they have to cancel the context)
 	// this goroutine functions as a shim between the logger and the caller.
 	messageChan := make(chan *backend.LogMessage, 1)
 	go func() {
2c252a48
 		if cLogCreated {
 			defer func() {
 				if err = cLog.Close(); err != nil {
 					logrus.Errorf("Error closing logger: %v", err)
 				}
 			}()
 		}
916eabd4
 		// signal that the log reader is gone
 		defer logs.ConsumerGone()
68e71aa3
 
1044093b
 		// close the messages channel. closing is the only way to signal above
 		// that we're doing with logs (other than context cancel i guess).
 		defer close(messageChan)
 
 		lg.Debug("begin logs")
2e4c2a6b
 		defer lg.Debugf("end logs (%v)", ctx.Err())
 
1044093b
 		for {
 			select {
 			// i do not believe as the system is currently designed any error
 			// is possible, but we should be prepared to handle it anyway. if
 			// we do get an error, copy only the error field to a new object so
 			// we don't end up with partial data in the other fields
 			case err := <-logs.Err:
 				lg.Errorf("Error streaming logs: %v", err)
 				select {
 				case <-ctx.Done():
 				case messageChan <- &backend.LogMessage{Err: err}:
 				}
 				return
 			case <-ctx.Done():
 				return
 			case msg, ok := <-logs.Msg:
 				// there is some kind of pool or ring buffer in the logger that
 				// produces these messages, and a possible future optimization
 				// might be to use that pool and reuse message objects
 				if !ok {
 					return
 				}
 				m := msg.AsLogMessage() // just a pointer conversion, does not copy data
 
 				// there could be a case where the reader stops accepting
 				// messages and the context is canceled. we need to check that
 				// here, or otherwise we risk blocking forever on the message
 				// send.
 				select {
 				case <-ctx.Done():
 					return
 				case messageChan <- m:
 				}
c2cf97a0
 			}
066330fc
 		}
1044093b
 	}()
ebcb7d6b
 	return messageChan, container.Config.Tty, nil
9b782d3a
 }
c412300d
 
68e71aa3
 func (daemon *Daemon) getLogger(container *container.Container) (l logger.Logger, created bool, err error) {
 	container.Lock()
 	if container.State.Running {
 		l = container.LogDriver
 	}
 	container.Unlock()
 	if l == nil {
 		created = true
 		l, err = container.StartLogger()
c412300d
 	}
68e71aa3
 	return
c412300d
 }
 
1790980e
 // mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified.
 func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error {
 	if cfg.Type == "" {
 		cfg.Type = daemon.defaultLogConfig.Type
 	}
 
7dff3106
 	if cfg.Config == nil {
 		cfg.Config = make(map[string]string)
 	}
 
1790980e
 	if cfg.Type == daemon.defaultLogConfig.Type {
 		for k, v := range daemon.defaultLogConfig.Config {
 			if _, ok := cfg.Config[k]; !ok {
 				cfg.Config[k] = v
 			}
06808500
 		}
 	}
 
1790980e
 	return logger.ValidateLogOpts(cfg.Type, cfg.Config)
06808500
 }
b0b9a25e
 
 func (daemon *Daemon) setupDefaultLogConfig() error {
 	config := daemon.configStore
 	if len(config.LogConfig.Config) > 0 {
 		if err := logger.ValidateLogOpts(config.LogConfig.Type, config.LogConfig.Config); err != nil {
 			return errors.Wrap(err, "failed to set log opts")
 		}
 	}
 	daemon.defaultLogConfig = containertypes.LogConfig{
 		Type:   config.LogConfig.Type,
 		Config: config.LogConfig.Config,
 	}
 	logrus.Debugf("Using default logging driver %s", daemon.defaultLogConfig.Type)
 	return nil
 }