write_log_stream.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. package httputils
  2. import (
  3. "fmt"
  4. "io"
  5. "net/url"
  6. "sort"
  7. "golang.org/x/net/context"
  8. "github.com/docker/docker/api/types"
  9. "github.com/docker/docker/api/types/backend"
  10. "github.com/docker/docker/pkg/ioutils"
  11. "github.com/docker/docker/pkg/jsonlog"
  12. "github.com/docker/docker/pkg/stdcopy"
  13. )
  14. // WriteLogStream writes an encoded byte stream of log messages from the
  15. // messages channel, multiplexing them with a stdcopy.Writer if mux is true
  16. func WriteLogStream(ctx context.Context, w io.Writer, msgs <-chan *backend.LogMessage, config *types.ContainerLogsOptions, mux bool) {
  17. wf := ioutils.NewWriteFlusher(w)
  18. defer wf.Close()
  19. wf.Flush()
  20. // this might seem like doing below is clear:
  21. // var outStream io.Writer = wf
  22. // however, this GREATLY DISPLEASES golint, and if you do that, it will
  23. // fail CI. we need outstream to be type writer because if we mux streams,
  24. // we will need to reassign all of the streams to be stdwriters, which only
  25. // conforms to the io.Writer interface.
  26. var outStream io.Writer
  27. outStream = wf
  28. errStream := outStream
  29. sysErrStream := errStream
  30. if mux {
  31. sysErrStream = stdcopy.NewStdWriter(outStream, stdcopy.Systemerr)
  32. errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
  33. outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
  34. }
  35. for {
  36. msg, ok := <-msgs
  37. if !ok {
  38. return
  39. }
  40. // check if the message contains an error. if so, write that error
  41. // and exit
  42. if msg.Err != nil {
  43. fmt.Fprintf(sysErrStream, "Error grabbing logs: %v\n", msg.Err)
  44. continue
  45. }
  46. logLine := msg.Line
  47. if config.Details {
  48. logLine = append(attrsByteSlice(msg.Attrs), ' ')
  49. logLine = append(logLine, msg.Line...)
  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 []backend.LogAttr
  67. func (b byKey) Len() int { return len(b) }
  68. func (b byKey) Less(i, j int) bool { return b[i].Key < b[j].Key }
  69. func (b byKey) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
  70. func attrsByteSlice(a []backend.LogAttr) []byte {
  71. // Note this sorts "a" in-place. That is fine here - nothing else is
  72. // going to use Attrs or care about the order.
  73. sort.Sort(byKey(a))
  74. var ret []byte
  75. for i, pair := range a {
  76. k, v := url.QueryEscape(pair.Key), url.QueryEscape(pair.Value)
  77. ret = append(ret, []byte(k)...)
  78. ret = append(ret, '=')
  79. ret = append(ret, []byte(v)...)
  80. if i != len(a)-1 {
  81. ret = append(ret, ',')
  82. }
  83. }
  84. return ret
  85. }