write_log_stream.go 2.6 KB

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