github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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 "github.com/docker/docker/container" 14 "github.com/docker/docker/daemon/logger" 15 "github.com/docker/docker/daemon/logger/jsonfilelog" 16 "github.com/docker/docker/pkg/ioutils" 17 "github.com/docker/docker/pkg/stdcopy" 18 containertypes "github.com/docker/engine-api/types/container" 19 timetypes "github.com/docker/engine-api/types/time" 20 ) 21 22 // ContainerLogs hooks up a container's stdout and stderr streams 23 // configured with the given struct. 24 func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *backend.ContainerLogsConfig, started chan struct{}) error { 25 container, err := daemon.GetContainer(containerName) 26 if err != nil { 27 return err 28 } 29 30 if !(config.ShowStdout || config.ShowStderr) { 31 return fmt.Errorf("You must choose at least one stream") 32 } 33 34 cLog, err := daemon.getLogger(container) 35 if err != nil { 36 return err 37 } 38 logReader, ok := cLog.(logger.LogReader) 39 if !ok { 40 return logger.ErrReadLogsNotSupported 41 } 42 43 follow := config.Follow && container.IsRunning() 44 tailLines, err := strconv.Atoi(config.Tail) 45 if err != nil { 46 tailLines = -1 47 } 48 49 logrus.Debug("logs: begin stream") 50 51 var since time.Time 52 if config.Since != "" { 53 s, n, err := timetypes.ParseTimestamps(config.Since, 0) 54 if err != nil { 55 return err 56 } 57 since = time.Unix(s, n) 58 } 59 readConfig := logger.ReadConfig{ 60 Since: since, 61 Tail: tailLines, 62 Follow: follow, 63 } 64 logs := logReader.ReadLogs(readConfig) 65 66 wf := ioutils.NewWriteFlusher(config.OutStream) 67 defer wf.Close() 68 close(started) 69 wf.Flush() 70 71 var outStream io.Writer = 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.Debugf("logs: end stream") 89 logs.Close() 90 return nil 91 } 92 logLine := msg.Line 93 if config.Timestamps { 94 logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...) 95 } 96 if msg.Source == "stdout" && config.ShowStdout { 97 outStream.Write(logLine) 98 } 99 if msg.Source == "stderr" && config.ShowStderr { 100 errStream.Write(logLine) 101 } 102 } 103 } 104 } 105 106 func (daemon *Daemon) getLogger(container *container.Container) (logger.Logger, error) { 107 if container.LogDriver != nil && container.IsRunning() { 108 return container.LogDriver, nil 109 } 110 cfg := daemon.getLogConfig(container.HostConfig.LogConfig) 111 if err := logger.ValidateLogOpts(cfg.Type, cfg.Config); err != nil { 112 return nil, err 113 } 114 return container.StartLogger(cfg) 115 } 116 117 // StartLogging initializes and starts the container logging stream. 118 func (daemon *Daemon) StartLogging(container *container.Container) error { 119 cfg := daemon.getLogConfig(container.HostConfig.LogConfig) 120 if cfg.Type == "none" { 121 return nil // do not start logging routines 122 } 123 124 if err := logger.ValidateLogOpts(cfg.Type, cfg.Config); err != nil { 125 return err 126 } 127 l, err := container.StartLogger(cfg) 128 if err != nil { 129 return fmt.Errorf("Failed to initialize logging driver: %v", err) 130 } 131 132 copier := logger.NewCopier(container.ID, map[string]io.Reader{"stdout": container.StdoutPipe(), "stderr": container.StderrPipe()}, l) 133 container.LogCopier = copier 134 copier.Run() 135 container.LogDriver = l 136 137 // set LogPath field only for json-file logdriver 138 if jl, ok := l.(*jsonfilelog.JSONFileLogger); ok { 139 container.LogPath = jl.LogPath() 140 } 141 142 return nil 143 } 144 145 // getLogConfig returns the log configuration for the container. 146 func (daemon *Daemon) getLogConfig(cfg containertypes.LogConfig) containertypes.LogConfig { 147 if cfg.Type != "" || len(cfg.Config) > 0 { // container has log driver configured 148 if cfg.Type == "" { 149 cfg.Type = jsonfilelog.Name 150 } 151 return cfg 152 } 153 154 // Use daemon's default log config for containers 155 return daemon.defaultLogConfig 156 }