logs.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package daemon
  2. import (
  3. "errors"
  4. "strconv"
  5. "time"
  6. "golang.org/x/net/context"
  7. "github.com/docker/docker/api/types"
  8. "github.com/docker/docker/api/types/backend"
  9. containertypes "github.com/docker/docker/api/types/container"
  10. timetypes "github.com/docker/docker/api/types/time"
  11. "github.com/docker/docker/container"
  12. "github.com/docker/docker/daemon/logger"
  13. "github.com/docker/docker/errdefs"
  14. "github.com/sirupsen/logrus"
  15. )
  16. // ContainerLogs copies the container's log channel to the channel provided in
  17. // the config. If ContainerLogs returns an error, no messages have been copied.
  18. // and the channel will be closed without data.
  19. //
  20. // if it returns nil, the config channel will be active and return log
  21. // messages until it runs out or the context is canceled.
  22. func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *types.ContainerLogsOptions) (<-chan *backend.LogMessage, bool, error) {
  23. lg := logrus.WithFields(logrus.Fields{
  24. "module": "daemon",
  25. "method": "(*Daemon).ContainerLogs",
  26. "container": containerName,
  27. })
  28. if !(config.ShowStdout || config.ShowStderr) {
  29. return nil, false, errdefs.InvalidParameter(errors.New("You must choose at least one stream"))
  30. }
  31. container, err := daemon.GetContainer(containerName)
  32. if err != nil {
  33. return nil, false, err
  34. }
  35. if container.RemovalInProgress || container.Dead {
  36. return nil, false, errdefs.Conflict(errors.New("can not get logs from container which is dead or marked for removal"))
  37. }
  38. if container.HostConfig.LogConfig.Type == "none" {
  39. return nil, false, logger.ErrReadLogsNotSupported{}
  40. }
  41. cLog, cLogCreated, err := daemon.getLogger(container)
  42. if err != nil {
  43. return nil, false, err
  44. }
  45. if cLogCreated {
  46. defer func() {
  47. if err = cLog.Close(); err != nil {
  48. logrus.Errorf("Error closing logger: %v", err)
  49. }
  50. }()
  51. }
  52. logReader, ok := cLog.(logger.LogReader)
  53. if !ok {
  54. return nil, false, logger.ErrReadLogsNotSupported{}
  55. }
  56. follow := config.Follow && !cLogCreated
  57. tailLines, err := strconv.Atoi(config.Tail)
  58. if err != nil {
  59. tailLines = -1
  60. }
  61. var since time.Time
  62. if config.Since != "" {
  63. s, n, err := timetypes.ParseTimestamps(config.Since, 0)
  64. if err != nil {
  65. return nil, false, err
  66. }
  67. since = time.Unix(s, n)
  68. }
  69. var until time.Time
  70. if config.Until != "" && config.Until != "0" {
  71. s, n, err := timetypes.ParseTimestamps(config.Until, 0)
  72. if err != nil {
  73. return nil, false, err
  74. }
  75. until = time.Unix(s, n)
  76. }
  77. readConfig := logger.ReadConfig{
  78. Since: since,
  79. Until: until,
  80. Tail: tailLines,
  81. Follow: follow,
  82. }
  83. logs := logReader.ReadLogs(readConfig)
  84. // past this point, we can't possibly return any errors, so we can just
  85. // start a goroutine and return to tell the caller not to expect errors
  86. // (if the caller wants to give up on logs, they have to cancel the context)
  87. // this goroutine functions as a shim between the logger and the caller.
  88. messageChan := make(chan *backend.LogMessage, 1)
  89. go func() {
  90. // set up some defers
  91. defer logs.Close()
  92. // close the messages channel. closing is the only way to signal above
  93. // that we're doing with logs (other than context cancel i guess).
  94. defer close(messageChan)
  95. lg.Debug("begin logs")
  96. for {
  97. select {
  98. // i do not believe as the system is currently designed any error
  99. // is possible, but we should be prepared to handle it anyway. if
  100. // we do get an error, copy only the error field to a new object so
  101. // we don't end up with partial data in the other fields
  102. case err := <-logs.Err:
  103. lg.Errorf("Error streaming logs: %v", err)
  104. select {
  105. case <-ctx.Done():
  106. case messageChan <- &backend.LogMessage{Err: err}:
  107. }
  108. return
  109. case <-ctx.Done():
  110. lg.Debugf("logs: end stream, ctx is done: %v", ctx.Err())
  111. return
  112. case msg, ok := <-logs.Msg:
  113. // there is some kind of pool or ring buffer in the logger that
  114. // produces these messages, and a possible future optimization
  115. // might be to use that pool and reuse message objects
  116. if !ok {
  117. lg.Debug("end logs")
  118. return
  119. }
  120. m := msg.AsLogMessage() // just a pointer conversion, does not copy data
  121. // there could be a case where the reader stops accepting
  122. // messages and the context is canceled. we need to check that
  123. // here, or otherwise we risk blocking forever on the message
  124. // send.
  125. select {
  126. case <-ctx.Done():
  127. return
  128. case messageChan <- m:
  129. }
  130. }
  131. }
  132. }()
  133. return messageChan, container.Config.Tty, nil
  134. }
  135. func (daemon *Daemon) getLogger(container *container.Container) (l logger.Logger, created bool, err error) {
  136. container.Lock()
  137. if container.State.Running {
  138. l = container.LogDriver
  139. }
  140. container.Unlock()
  141. if l == nil {
  142. created = true
  143. l, err = container.StartLogger()
  144. }
  145. return
  146. }
  147. // mergeLogConfig merges the daemon log config to the container's log config if the container's log driver is not specified.
  148. func (daemon *Daemon) mergeAndVerifyLogConfig(cfg *containertypes.LogConfig) error {
  149. if cfg.Type == "" {
  150. cfg.Type = daemon.defaultLogConfig.Type
  151. }
  152. if cfg.Config == nil {
  153. cfg.Config = make(map[string]string)
  154. }
  155. if cfg.Type == daemon.defaultLogConfig.Type {
  156. for k, v := range daemon.defaultLogConfig.Config {
  157. if _, ok := cfg.Config[k]; !ok {
  158. cfg.Config[k] = v
  159. }
  160. }
  161. }
  162. return logger.ValidateLogOpts(cfg.Type, cfg.Config)
  163. }