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