Merge pull request #32015 from dperny/service-logs-support-task-logs

Add Support for Service Task Logs
This commit is contained in:
Victor Vieux 2017-04-04 00:15:13 -07:00 committed by GitHub
commit 4284efd232
1 changed files with 48 additions and 21 deletions

View File

@ -30,9 +30,14 @@ type logsOptions struct {
timestamps bool timestamps bool
tail string tail string
service string target string
} }
// TODO(dperny) the whole CLI for this is kind of a mess IMHOIRL and it needs
// to be refactored agressively. There may be changes to the implementation of
// details, which will be need to be reflected in this code. The refactoring
// should be put off until we make those changes, tho, because I think the
// decisions made WRT details will impact the design of the CLI.
func newLogsCommand(dockerCli *command.DockerCli) *cobra.Command { func newLogsCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts logsOptions var opts logsOptions
@ -41,16 +46,16 @@ func newLogsCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Fetch the logs of a service", Short: "Fetch the logs of a service",
Args: cli.ExactArgs(1), Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.service = args[0] opts.target = args[0]
return runLogs(dockerCli, &opts) return runLogs(dockerCli, &opts)
}, },
Tags: map[string]string{"experimental": ""}, Tags: map[string]string{"experimental": ""},
} }
flags := cmd.Flags() flags := cmd.Flags()
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names") flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names in output")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output") flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&opts.noTaskIDs, "no-task-ids", false, "Do not include task IDs") flags.BoolVar(&opts.noTaskIDs, "no-task-ids", false, "Do not include task IDs in output")
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output") flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output")
flags.StringVar(&opts.since, "since", "", "Show logs since timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes)") flags.StringVar(&opts.since, "since", "", "Show logs since timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes)")
flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps") flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps")
@ -70,28 +75,44 @@ func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
Tail: opts.tail, Tail: opts.tail,
} }
client := dockerCli.Client() cli := dockerCli.Client()
service, _, err := client.ServiceInspectWithRaw(ctx, opts.service) var (
if err != nil { maxLength = 1
return err responseBody io.ReadCloser
} )
responseBody, err := client.ServiceLogs(ctx, opts.service, options) service, _, err := cli.ServiceInspectWithRaw(ctx, opts.target)
if err != nil { if err != nil {
return err // if it's any error other than service not found, it's Real
if !client.IsErrServiceNotFound(err) {
return err
}
task, _, err := cli.TaskInspectWithRaw(ctx, opts.target)
if err != nil {
if client.IsErrTaskNotFound(err) {
// if the task ALSO isn't found, rewrite the error to be clear
// that we looked for services AND tasks
err = fmt.Errorf("No such task or service")
}
return err
}
maxLength = getMaxLength(task.Slot)
responseBody, err = cli.TaskLogs(ctx, opts.target, options)
} else {
responseBody, err = cli.ServiceLogs(ctx, opts.target, options)
if err != nil {
return err
}
if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
// if replicas are initialized, figure out if we need to pad them
replicas := *service.Spec.Mode.Replicated.Replicas
maxLength = getMaxLength(int(replicas))
}
} }
defer responseBody.Close() defer responseBody.Close()
var replicas uint64 taskFormatter := newTaskFormatter(cli, opts, maxLength)
padding := 1
if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
// if replicas are initialized, figure out if we need to pad them
replicas = *service.Spec.Mode.Replicated.Replicas
padding = len(strconv.FormatUint(replicas, 10))
}
taskFormatter := newTaskFormatter(client, opts, padding)
stdout := &logWriter{ctx: ctx, opts: opts, f: taskFormatter, w: dockerCli.Out()} stdout := &logWriter{ctx: ctx, opts: opts, f: taskFormatter, w: dockerCli.Out()}
stderr := &logWriter{ctx: ctx, opts: opts, f: taskFormatter, w: dockerCli.Err()} stderr := &logWriter{ctx: ctx, opts: opts, f: taskFormatter, w: dockerCli.Err()}
@ -101,6 +122,11 @@ func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
return err return err
} }
// getMaxLength gets the maximum length of the number in base 10
func getMaxLength(i int) int {
return len(strconv.FormatInt(int64(i), 10))
}
type taskFormatter struct { type taskFormatter struct {
client client.APIClient client client.APIClient
opts *logsOptions opts *logsOptions
@ -148,7 +174,8 @@ func (f *taskFormatter) format(ctx context.Context, logCtx logContext) (string,
taskName += fmt.Sprintf(".%s", stringid.TruncateID(task.ID)) taskName += fmt.Sprintf(".%s", stringid.TruncateID(task.ID))
} }
} }
padding := strings.Repeat(" ", f.padding-len(strconv.FormatInt(int64(task.Slot), 10)))
padding := strings.Repeat(" ", f.padding-getMaxLength(task.Slot))
formatted := fmt.Sprintf("%s@%s%s", taskName, nodeName, padding) formatted := fmt.Sprintf("%s@%s%s", taskName, nodeName, padding)
f.cache[logCtx] = formatted f.cache[logCtx] = formatted
return formatted, nil return formatted, nil