mirror of https://github.com/docker/cli.git
Add support for task and arbitrary combo logs
Refactored the API to more easily accept new endpoints. Added REST, client, and CLI endpoints for getting logs from a specific task. All that is needed after this commit to enable arbitrary service log selectors is a REST endpoint and handler. Task logs can be retrieved by putting in a task ID at the CLI instead of a service ID. Signed-off-by: Drew Erny <drew.erny@docker.com>
This commit is contained in:
parent
f066943a05
commit
b4ca6ebb09
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue