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 }