github.com/olljanat/moby@v1.13.1/cli/command/service/logs.go (about)

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