github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/api/server/httputils/write_log_stream.go (about) 1 package httputils 2 3 import ( 4 "fmt" 5 "io" 6 "net/url" 7 "sort" 8 "strings" 9 10 "golang.org/x/net/context" 11 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/backend" 14 "github.com/docker/docker/pkg/ioutils" 15 "github.com/docker/docker/pkg/jsonlog" 16 "github.com/docker/docker/pkg/stdcopy" 17 ) 18 19 // WriteLogStream writes an encoded byte stream of log messages from the 20 // messages channel, multiplexing them with a stdcopy.Writer if mux is true 21 func WriteLogStream(ctx context.Context, w io.Writer, msgs <-chan *backend.LogMessage, config *types.ContainerLogsOptions, mux bool) { 22 wf := ioutils.NewWriteFlusher(w) 23 defer wf.Close() 24 25 wf.Flush() 26 27 // this might seem like doing below is clear: 28 // var outStream io.Writer = wf 29 // however, this GREATLY DISPLEASES golint, and if you do that, it will 30 // fail CI. we need outstream to be type writer because if we mux streams, 31 // we will need to reassign all of the streams to be stdwriters, which only 32 // conforms to the io.Writer interface. 33 var outStream io.Writer 34 outStream = wf 35 errStream := outStream 36 sysErrStream := errStream 37 if mux { 38 sysErrStream = stdcopy.NewStdWriter(outStream, stdcopy.Systemerr) 39 errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) 40 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 41 } 42 43 for { 44 msg, ok := <-msgs 45 if !ok { 46 return 47 } 48 // check if the message contains an error. if so, write that error 49 // and exit 50 if msg.Err != nil { 51 fmt.Fprintf(sysErrStream, "Error grabbing logs: %v\n", msg.Err) 52 continue 53 } 54 logLine := msg.Line 55 if config.Details { 56 logLine = append([]byte(stringAttrs(msg.Attrs)+" "), logLine...) 57 } 58 if config.Timestamps { 59 // TODO(dperny) the format is defined in 60 // daemon/logger/logger.go as logger.TimeFormat. importing 61 // logger is verboten (not part of backend) so idk if just 62 // importing the same thing from jsonlog is good enough 63 logLine = append([]byte(msg.Timestamp.Format(jsonlog.RFC3339NanoFixed)+" "), logLine...) 64 } 65 if msg.Source == "stdout" && config.ShowStdout { 66 outStream.Write(logLine) 67 } 68 if msg.Source == "stderr" && config.ShowStderr { 69 errStream.Write(logLine) 70 } 71 } 72 } 73 74 type byKey []string 75 76 func (s byKey) Len() int { return len(s) } 77 func (s byKey) Less(i, j int) bool { 78 keyI := strings.Split(s[i], "=") 79 keyJ := strings.Split(s[j], "=") 80 return keyI[0] < keyJ[0] 81 } 82 func (s byKey) Swap(i, j int) { 83 s[i], s[j] = s[j], s[i] 84 } 85 86 func stringAttrs(a backend.LogAttributes) string { 87 var ss byKey 88 for k, v := range a { 89 k, v := url.QueryEscape(k), url.QueryEscape(v) 90 ss = append(ss, k+"="+v) 91 } 92 sort.Sort(ss) 93 return strings.Join(ss, ",") 94 }