logs.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. package service
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "strings"
  7. "golang.org/x/net/context"
  8. "github.com/docker/docker/api/types"
  9. "github.com/docker/docker/api/types/swarm"
  10. "github.com/docker/docker/cli"
  11. "github.com/docker/docker/cli/command"
  12. "github.com/docker/docker/cli/command/idresolver"
  13. "github.com/docker/docker/pkg/stdcopy"
  14. "github.com/spf13/cobra"
  15. )
  16. type logsOptions struct {
  17. noResolve bool
  18. follow bool
  19. since string
  20. timestamps bool
  21. details bool
  22. tail string
  23. service string
  24. }
  25. func newLogsCommand(dockerCli *command.DockerCli) *cobra.Command {
  26. var opts logsOptions
  27. cmd := &cobra.Command{
  28. Use: "logs [OPTIONS] SERVICE",
  29. Short: "Fetch the logs of a service",
  30. Args: cli.ExactArgs(1),
  31. RunE: func(cmd *cobra.Command, args []string) error {
  32. opts.service = args[0]
  33. return runLogs(dockerCli, &opts)
  34. },
  35. Tags: map[string]string{"experimental": ""},
  36. }
  37. flags := cmd.Flags()
  38. flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
  39. flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output")
  40. flags.StringVar(&opts.since, "since", "", "Show logs since timestamp")
  41. flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps")
  42. flags.BoolVar(&opts.details, "details", false, "Show extra details provided to logs")
  43. flags.StringVar(&opts.tail, "tail", "all", "Number of lines to show from the end of the logs")
  44. return cmd
  45. }
  46. func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
  47. ctx := context.Background()
  48. options := types.ContainerLogsOptions{
  49. ShowStdout: true,
  50. ShowStderr: true,
  51. Since: opts.since,
  52. Timestamps: opts.timestamps,
  53. Follow: opts.follow,
  54. Tail: opts.tail,
  55. Details: opts.details,
  56. }
  57. client := dockerCli.Client()
  58. responseBody, err := client.ServiceLogs(ctx, opts.service, options)
  59. if err != nil {
  60. return err
  61. }
  62. defer responseBody.Close()
  63. resolver := idresolver.New(client, opts.noResolve)
  64. stdout := &logWriter{ctx: ctx, opts: opts, r: resolver, w: dockerCli.Out()}
  65. stderr := &logWriter{ctx: ctx, opts: opts, r: resolver, w: dockerCli.Err()}
  66. // TODO(aluzzardi): Do an io.Copy for services with TTY enabled.
  67. _, err = stdcopy.StdCopy(stdout, stderr, responseBody)
  68. return err
  69. }
  70. type logWriter struct {
  71. ctx context.Context
  72. opts *logsOptions
  73. r *idresolver.IDResolver
  74. w io.Writer
  75. }
  76. func (lw *logWriter) Write(buf []byte) (int, error) {
  77. contextIndex := 0
  78. numParts := 2
  79. if lw.opts.timestamps {
  80. contextIndex++
  81. numParts++
  82. }
  83. parts := bytes.SplitN(buf, []byte(" "), numParts)
  84. if len(parts) != numParts {
  85. return 0, fmt.Errorf("invalid context in log message: %v", string(buf))
  86. }
  87. taskName, nodeName, err := lw.parseContext(string(parts[contextIndex]))
  88. if err != nil {
  89. return 0, err
  90. }
  91. output := []byte{}
  92. for i, part := range parts {
  93. // First part doesn't get space separation.
  94. if i > 0 {
  95. output = append(output, []byte(" ")...)
  96. }
  97. if i == contextIndex {
  98. // TODO(aluzzardi): Consider constant padding.
  99. output = append(output, []byte(fmt.Sprintf("%s@%s |", taskName, nodeName))...)
  100. } else {
  101. output = append(output, part...)
  102. }
  103. }
  104. _, err = lw.w.Write(output)
  105. if err != nil {
  106. return 0, err
  107. }
  108. return len(buf), nil
  109. }
  110. func (lw *logWriter) parseContext(input string) (string, string, error) {
  111. context := make(map[string]string)
  112. components := strings.Split(input, ",")
  113. for _, component := range components {
  114. parts := strings.SplitN(component, "=", 2)
  115. if len(parts) != 2 {
  116. return "", "", fmt.Errorf("invalid context: %s", input)
  117. }
  118. context[parts[0]] = parts[1]
  119. }
  120. taskID, ok := context["com.docker.swarm.task.id"]
  121. if !ok {
  122. return "", "", fmt.Errorf("missing task id in context: %s", input)
  123. }
  124. taskName, err := lw.r.Resolve(lw.ctx, swarm.Task{}, taskID)
  125. if err != nil {
  126. return "", "", err
  127. }
  128. nodeID, ok := context["com.docker.swarm.node.id"]
  129. if !ok {
  130. return "", "", fmt.Errorf("missing node id in context: %s", input)
  131. }
  132. nodeName, err := lw.r.Resolve(lw.ctx, swarm.Node{}, nodeID)
  133. if err != nil {
  134. return "", "", err
  135. }
  136. return taskName, nodeName, nil
  137. }