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