github.com/ld86/docker@v1.7.1-rc3/daemon/logs.go (about) 1 package daemon 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net" 9 "os" 10 "strconv" 11 "syscall" 12 "time" 13 14 "github.com/Sirupsen/logrus" 15 "github.com/docker/docker/daemon/logger/jsonfilelog" 16 "github.com/docker/docker/pkg/jsonlog" 17 "github.com/docker/docker/pkg/stdcopy" 18 "github.com/docker/docker/pkg/tailfile" 19 "github.com/docker/docker/pkg/timeutils" 20 ) 21 22 type ContainerLogsConfig struct { 23 Follow, Timestamps bool 24 Tail string 25 Since time.Time 26 UseStdout, UseStderr bool 27 OutStream io.Writer 28 } 29 30 func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error { 31 var ( 32 lines = -1 33 format string 34 ) 35 if !(config.UseStdout || config.UseStderr) { 36 return fmt.Errorf("You must choose at least one stream") 37 } 38 if config.Timestamps { 39 format = timeutils.RFC3339NanoFixed 40 } 41 if config.Tail == "" { 42 config.Tail = "all" 43 } 44 45 container, err := daemon.Get(name) 46 if err != nil { 47 return err 48 } 49 50 var ( 51 outStream = config.OutStream 52 errStream io.Writer 53 ) 54 if !container.Config.Tty { 55 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 56 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 57 } else { 58 errStream = outStream 59 } 60 61 if container.LogDriverType() != jsonfilelog.Name { 62 return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver") 63 } 64 logDriver, err := container.getLogger() 65 cLog, err := logDriver.GetReader() 66 if err != nil { 67 logrus.Errorf("Error reading logs: %s", err) 68 } else { 69 // json-file driver 70 if config.Tail != "all" { 71 var err error 72 lines, err = strconv.Atoi(config.Tail) 73 if err != nil { 74 logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", config.Tail, err) 75 lines = -1 76 } 77 } 78 79 if lines != 0 { 80 if lines > 0 { 81 f := cLog.(*os.File) 82 ls, err := tailfile.TailFile(f, lines) 83 if err != nil { 84 return err 85 } 86 tmp := bytes.NewBuffer([]byte{}) 87 for _, l := range ls { 88 fmt.Fprintf(tmp, "%s\n", l) 89 } 90 cLog = tmp 91 } 92 93 dec := json.NewDecoder(cLog) 94 l := &jsonlog.JSONLog{} 95 for { 96 l.Reset() 97 if err := dec.Decode(l); err == io.EOF { 98 break 99 } else if err != nil { 100 logrus.Errorf("Error streaming logs: %s", err) 101 break 102 } 103 logLine := l.Log 104 if !config.Since.IsZero() && l.Created.Before(config.Since) { 105 continue 106 } 107 if config.Timestamps { 108 // format can be "" or time format, so here can't be error 109 logLine, _ = l.Format(format) 110 } 111 if l.Stream == "stdout" && config.UseStdout { 112 io.WriteString(outStream, logLine) 113 } 114 if l.Stream == "stderr" && config.UseStderr { 115 io.WriteString(errStream, logLine) 116 } 117 } 118 } 119 } 120 121 if config.Follow && container.IsRunning() { 122 chErr := make(chan error) 123 var stdoutPipe, stderrPipe io.ReadCloser 124 125 // write an empty chunk of data (this is to ensure that the 126 // HTTP Response is sent immediatly, even if the container has 127 // not yet produced any data) 128 outStream.Write(nil) 129 130 if config.UseStdout { 131 stdoutPipe = container.StdoutLogPipe() 132 go func() { 133 logrus.Debug("logs: stdout stream begin") 134 chErr <- jsonlog.WriteLog(stdoutPipe, outStream, format, config.Since) 135 logrus.Debug("logs: stdout stream end") 136 }() 137 } 138 if config.UseStderr { 139 stderrPipe = container.StderrLogPipe() 140 go func() { 141 logrus.Debug("logs: stderr stream begin") 142 chErr <- jsonlog.WriteLog(stderrPipe, errStream, format, config.Since) 143 logrus.Debug("logs: stderr stream end") 144 }() 145 } 146 147 err = <-chErr 148 if stdoutPipe != nil { 149 stdoutPipe.Close() 150 } 151 if stderrPipe != nil { 152 stderrPipe.Close() 153 } 154 <-chErr // wait for 2nd goroutine to exit, otherwise bad things will happen 155 156 if err != nil && err != io.EOF && err != io.ErrClosedPipe { 157 if e, ok := err.(*net.OpError); ok && e.Err != syscall.EPIPE { 158 logrus.Errorf("error streaming logs: %v", err) 159 } 160 } 161 } 162 return nil 163 }