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