package httputils import ( "fmt" "io" "sort" "strings" "golang.org/x/net/context" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/stdcopy" ) // WriteLogStream writes an encoded byte stream of log messages from the // messages channel, multiplexing them with a stdcopy.Writer if mux is true func WriteLogStream(ctx context.Context, w io.Writer, msgs <-chan *backend.LogMessage, config *types.ContainerLogsOptions, mux bool) { wf := ioutils.NewWriteFlusher(w) defer wf.Close() wf.Flush() // this might seem like doing below is clear: // var outStream io.Writer = wf // however, this GREATLY DISPLEASES golint, and if you do that, it will // fail CI. we need outstream to be type writer because if we mux streams, // we will need to reassign all of the streams to be stdwriters, which only // conforms to the io.Writer interface. var outStream io.Writer outStream = wf errStream := outStream sysErrStream := errStream if mux { sysErrStream = stdcopy.NewStdWriter(outStream, stdcopy.Systemerr) errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) } for { msg, ok := <-msgs if !ok { return } // check if the message contains an error. if so, write that error // and exit if msg.Err != nil { fmt.Fprintf(sysErrStream, "Error grabbing logs: %v\n", msg.Err) continue } logLine := msg.Line if config.Details { logLine = append([]byte(stringAttrs(msg.Attrs)+" "), logLine...) } if config.Timestamps { // TODO(dperny) the format is defined in // daemon/logger/logger.go as logger.TimeFormat. importing // logger is verboten (not part of backend) so idk if just // importing the same thing from jsonlog is good enough logLine = append([]byte(msg.Timestamp.Format(jsonlog.RFC3339NanoFixed)+" "), logLine...) } if msg.Source == "stdout" && config.ShowStdout { outStream.Write(logLine) } if msg.Source == "stderr" && config.ShowStderr { errStream.Write(logLine) } } } type byKey []string func (s byKey) Len() int { return len(s) } func (s byKey) Less(i, j int) bool { keyI := strings.Split(s[i], "=") keyJ := strings.Split(s[j], "=") return keyI[0] < keyJ[0] } func (s byKey) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func stringAttrs(a backend.LogAttributes) string { var ss byKey for k, v := range a { ss = append(ss, k+"="+v) } sort.Sort(ss) return strings.Join(ss, ",") }