github.com/sams1990/dockerrepo@v17.12.1-ce-rc2+incompatible/daemon/logs.go (about)

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