logs.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package daemon
  2. import (
  3. "fmt"
  4. "io"
  5. "strconv"
  6. "time"
  7. "golang.org/x/net/context"
  8. "github.com/Sirupsen/logrus"
  9. "github.com/docker/docker/api/types/backend"
  10. containertypes "github.com/docker/docker/api/types/container"
  11. timetypes "github.com/docker/docker/api/types/time"
  12. "github.com/docker/docker/container"
  13. "github.com/docker/docker/daemon/logger"
  14. "github.com/docker/docker/pkg/ioutils"
  15. "github.com/docker/docker/pkg/stdcopy"
  16. )
  17. // ContainerLogs hooks up a container's stdout and stderr streams
  18. // configured with the given struct.
  19. func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *backend.ContainerLogsConfig, started chan struct{}) error {
  20. container, err := daemon.GetContainer(containerName)
  21. if err != nil {
  22. return err
  23. }
  24. if !(config.ShowStdout || config.ShowStderr) {
  25. return fmt.Errorf("You must choose at least one stream")
  26. }
  27. cLog, err := daemon.getLogger(container)
  28. if err != nil {
  29. return err
  30. }
  31. logReader, ok := cLog.(logger.LogReader)
  32. if !ok {
  33. return logger.ErrReadLogsNotSupported
  34. }
  35. follow := config.Follow && container.IsRunning()
  36. tailLines, err := strconv.Atoi(config.Tail)
  37. if err != nil {
  38. tailLines = -1
  39. }
  40. logrus.Debug("logs: begin stream")
  41. var since time.Time
  42. if config.Since != "" {
  43. s, n, err := timetypes.ParseTimestamps(config.Since, 0)
  44. if err != nil {
  45. return err
  46. }
  47. since = time.Unix(s, n)
  48. }
  49. readConfig := logger.ReadConfig{
  50. Since: since,
  51. Tail: tailLines,
  52. Follow: follow,
  53. }
  54. logs := logReader.ReadLogs(readConfig)
  55. wf := ioutils.NewWriteFlusher(config.OutStream)
  56. defer wf.Close()
  57. close(started)
  58. wf.Flush()
  59. var outStream io.Writer
  60. outStream = wf
  61. errStream := outStream
  62. if !container.Config.Tty {
  63. errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
  64. outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
  65. }
  66. for {
  67. select {
  68. case err := <-logs.Err:
  69. logrus.Errorf("Error streaming logs: %v", err)
  70. return nil
  71. case <-ctx.Done():
  72. logs.Close()
  73. return nil
  74. case msg, ok := <-logs.Msg:
  75. if !ok {
  76. logrus.Debug("logs: end stream")
  77. logs.Close()
  78. if cLog != container.LogDriver {
  79. // Since the logger isn't cached in the container, which occurs if it is running, it
  80. // must get explicitly closed here to avoid leaking it and any file handles it has.
  81. if err := cLog.Close(); err != nil {
  82. logrus.Errorf("Error closing logger: %v", err)
  83. }
  84. }
  85. return nil
  86. }
  87. logLine := msg.Line
  88. if config.Details {
  89. logLine = append([]byte(msg.Attrs.String()+" "), logLine...)
  90. }
  91. if config.Timestamps {
  92. logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...)
  93. }
  94. if msg.Source == "stdout" && config.ShowStdout {
  95. outStream.Write(logLine)
  96. }
  97. if msg.Source == "stderr" && config.ShowStderr {
  98. errStream.Write(logLine)
  99. }
  100. }
  101. }
  102. }
  103. func (daemon *Daemon) getLogger(container *container.Container) (logger.Logger, error) {
  104. if container.LogDriver != nil && container.IsRunning() {
  105. return container.LogDriver, nil
  106. }
  107. return container.StartLogger()
  108. }
  109. // mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified.
  110. func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error {
  111. if cfg.Type == "" {
  112. cfg.Type = daemon.defaultLogConfig.Type
  113. }
  114. if cfg.Config == nil {
  115. cfg.Config = make(map[string]string)
  116. }
  117. if cfg.Type == daemon.defaultLogConfig.Type {
  118. for k, v := range daemon.defaultLogConfig.Config {
  119. if _, ok := cfg.Config[k]; !ok {
  120. cfg.Config[k] = v
  121. }
  122. }
  123. }
  124. return logger.ValidateLogOpts(cfg.Type, cfg.Config)
  125. }