logs.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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. "github.com/docker/docker/container"
  11. "github.com/docker/docker/daemon/logger"
  12. "github.com/docker/docker/daemon/logger/jsonfilelog"
  13. "github.com/docker/docker/pkg/ioutils"
  14. "github.com/docker/docker/pkg/stdcopy"
  15. containertypes "github.com/docker/engine-api/types/container"
  16. timetypes "github.com/docker/engine-api/types/time"
  17. )
  18. // ContainerLogs hooks up a container's stdout and stderr streams
  19. // configured with the given struct.
  20. func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *backend.ContainerLogsConfig, started chan struct{}) error {
  21. container, err := daemon.GetContainer(containerName)
  22. if err != nil {
  23. return err
  24. }
  25. if !(config.ShowStdout || config.ShowStderr) {
  26. return fmt.Errorf("You must choose at least one stream")
  27. }
  28. cLog, err := daemon.getLogger(container)
  29. if err != nil {
  30. return err
  31. }
  32. logReader, ok := cLog.(logger.LogReader)
  33. if !ok {
  34. return logger.ErrReadLogsNotSupported
  35. }
  36. follow := config.Follow && container.IsRunning()
  37. tailLines, err := strconv.Atoi(config.Tail)
  38. if err != nil {
  39. tailLines = -1
  40. }
  41. logrus.Debug("logs: begin stream")
  42. var since time.Time
  43. if config.Since != "" {
  44. s, n, err := timetypes.ParseTimestamps(config.Since, 0)
  45. if err != nil {
  46. return err
  47. }
  48. since = time.Unix(s, n)
  49. }
  50. readConfig := logger.ReadConfig{
  51. Since: since,
  52. Tail: tailLines,
  53. Follow: follow,
  54. }
  55. logs := logReader.ReadLogs(readConfig)
  56. wf := ioutils.NewWriteFlusher(config.OutStream)
  57. defer wf.Close()
  58. close(started)
  59. wf.Flush()
  60. var outStream io.Writer = 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(container.HostConfig.LogConfig)
  108. }
  109. // StartLogging initializes and starts the container logging stream.
  110. func (daemon *Daemon) StartLogging(container *container.Container) error {
  111. if container.HostConfig.LogConfig.Type == "none" {
  112. return nil // do not start logging routines
  113. }
  114. l, err := container.StartLogger(container.HostConfig.LogConfig)
  115. if err != nil {
  116. return fmt.Errorf("Failed to initialize logging driver: %v", err)
  117. }
  118. copier := logger.NewCopier(map[string]io.Reader{"stdout": container.StdoutPipe(), "stderr": container.StderrPipe()}, l)
  119. container.LogCopier = copier
  120. copier.Run()
  121. container.LogDriver = l
  122. // set LogPath field only for json-file logdriver
  123. if jl, ok := l.(*jsonfilelog.JSONFileLogger); ok {
  124. container.LogPath = jl.LogPath()
  125. }
  126. return nil
  127. }
  128. // mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified.
  129. func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error {
  130. if cfg.Type == "" {
  131. cfg.Type = daemon.defaultLogConfig.Type
  132. }
  133. if cfg.Config == nil {
  134. cfg.Config = make(map[string]string)
  135. }
  136. if cfg.Type == daemon.defaultLogConfig.Type {
  137. for k, v := range daemon.defaultLogConfig.Config {
  138. if _, ok := cfg.Config[k]; !ok {
  139. cfg.Config[k] = v
  140. }
  141. }
  142. }
  143. return logger.ValidateLogOpts(cfg.Type, cfg.Config)
  144. }