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 }