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 }