github.com/morganxf/moby@v1.13.1/daemon/logs.go (about) 1 package daemon 2 3 import ( 4 "fmt" 5 "io" 6 "strconv" 7 "time" 8 9 "golang.org/x/net/context" 10 11 "github.com/Sirupsen/logrus" 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 "github.com/docker/docker/pkg/ioutils" 18 "github.com/docker/docker/pkg/stdcopy" 19 ) 20 21 // ContainerLogs hooks up a container's stdout and stderr streams 22 // configured with the given struct. 23 func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *backend.ContainerLogsConfig, started chan struct{}) error { 24 container, err := daemon.GetContainer(containerName) 25 if err != nil { 26 return err 27 } 28 29 if !(config.ShowStdout || config.ShowStderr) { 30 return fmt.Errorf("You must choose at least one stream") 31 } 32 33 cLog, err := daemon.getLogger(container) 34 if err != nil { 35 return err 36 } 37 logReader, ok := cLog.(logger.LogReader) 38 if !ok { 39 return logger.ErrReadLogsNotSupported 40 } 41 42 follow := config.Follow && container.IsRunning() 43 tailLines, err := strconv.Atoi(config.Tail) 44 if err != nil { 45 tailLines = -1 46 } 47 48 logrus.Debug("logs: begin stream") 49 50 var since time.Time 51 if config.Since != "" { 52 s, n, err := timetypes.ParseTimestamps(config.Since, 0) 53 if err != nil { 54 return err 55 } 56 since = time.Unix(s, n) 57 } 58 readConfig := logger.ReadConfig{ 59 Since: since, 60 Tail: tailLines, 61 Follow: follow, 62 } 63 logs := logReader.ReadLogs(readConfig) 64 65 wf := ioutils.NewWriteFlusher(config.OutStream) 66 defer wf.Close() 67 close(started) 68 wf.Flush() 69 70 var outStream io.Writer 71 outStream = wf 72 errStream := outStream 73 if !container.Config.Tty { 74 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 75 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 76 } 77 78 for { 79 select { 80 case err := <-logs.Err: 81 logrus.Errorf("Error streaming logs: %v", err) 82 return nil 83 case <-ctx.Done(): 84 logs.Close() 85 return nil 86 case msg, ok := <-logs.Msg: 87 if !ok { 88 logrus.Debug("logs: end stream") 89 logs.Close() 90 if cLog != container.LogDriver { 91 // Since the logger isn't cached in the container, which occurs if it is running, it 92 // must get explicitly closed here to avoid leaking it and any file handles it has. 93 if err := cLog.Close(); err != nil { 94 logrus.Errorf("Error closing logger: %v", err) 95 } 96 } 97 return nil 98 } 99 logLine := msg.Line 100 if config.Details { 101 logLine = append([]byte(msg.Attrs.String()+" "), logLine...) 102 } 103 if config.Timestamps { 104 logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...) 105 } 106 if msg.Source == "stdout" && config.ShowStdout { 107 outStream.Write(logLine) 108 } 109 if msg.Source == "stderr" && config.ShowStderr { 110 errStream.Write(logLine) 111 } 112 } 113 } 114 } 115 116 func (daemon *Daemon) getLogger(container *container.Container) (logger.Logger, error) { 117 if container.LogDriver != nil && container.IsRunning() { 118 return container.LogDriver, nil 119 } 120 return container.StartLogger(container.HostConfig.LogConfig) 121 } 122 123 // mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified. 124 func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error { 125 if cfg.Type == "" { 126 cfg.Type = daemon.defaultLogConfig.Type 127 } 128 129 if cfg.Config == nil { 130 cfg.Config = make(map[string]string) 131 } 132 133 if cfg.Type == daemon.defaultLogConfig.Type { 134 for k, v := range daemon.defaultLogConfig.Config { 135 if _, ok := cfg.Config[k]; !ok { 136 cfg.Config[k] = v 137 } 138 } 139 } 140 141 return logger.ValidateLogOpts(cfg.Type, cfg.Config) 142 }