summaryrefslogtreecommitdiff
path: root/cmd/podman/containers/logs.go
blob: 9a97492102d6e1af93647d78845bdd77c85f4c00 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package containers

import (
	"errors"
	"fmt"
	"os"

	"github.com/containers/common/pkg/completion"
	"github.com/containers/podman/v4/cmd/podman/common"
	"github.com/containers/podman/v4/cmd/podman/registry"
	"github.com/containers/podman/v4/cmd/podman/validate"
	"github.com/containers/podman/v4/pkg/domain/entities"
	"github.com/containers/podman/v4/pkg/util"
	"github.com/spf13/cobra"
)

// logsOptionsWrapper wraps entities.LogsOptions and prevents leaking
// CLI-only fields into the API types.
type logsOptionsWrapper struct {
	entities.ContainerLogsOptions

	SinceRaw string

	UntilRaw string
}

var (
	logsOptions     logsOptionsWrapper
	logsDescription = `Retrieves logs for one or more containers.

  This does not guarantee execution order when combined with podman run (i.e., your run may not have generated any logs at the time you execute podman logs).
`
	logsCommand = &cobra.Command{
		Use:   "logs [options] CONTAINER [CONTAINER...]",
		Short: "Fetch the logs of one or more containers",
		Long:  logsDescription,
		Args: func(cmd *cobra.Command, args []string) error {
			switch {
			case registry.IsRemote() && logsOptions.Latest:
				return errors.New(cmd.Name() + " does not support 'latest' when run remotely")
			case registry.IsRemote() && len(args) > 1:
				return errors.New(cmd.Name() + " does not support multiple containers when run remotely")
			case logsOptions.Latest && len(args) > 0:
				return errors.New("--latest and containers cannot be used together")
			case !logsOptions.Latest && len(args) < 1:
				return errors.New("specify at least one container name or ID to log")
			}
			return nil
		},
		RunE:              logs,
		ValidArgsFunction: common.AutocompleteContainers,
		Example: `podman logs ctrID
  podman logs --names ctrID1 ctrID2
  podman logs --tail 2 mywebserver
  podman logs --follow=true --since 10m ctrID
  podman logs mywebserver mydbserver`,
	}

	containerLogsCommand = &cobra.Command{
		Use:               logsCommand.Use,
		Short:             logsCommand.Short,
		Long:              logsCommand.Long,
		Args:              logsCommand.Args,
		RunE:              logsCommand.RunE,
		ValidArgsFunction: logsCommand.ValidArgsFunction,
		Example: `podman container logs ctrID
		podman container logs --names ctrID1 ctrID2
		podman container logs --color --names ctrID1 ctrID2
		podman container logs --tail 2 mywebserver
		podman container logs --follow=true --since 10m ctrID
		podman container logs mywebserver mydbserver`,
	}
)

func init() {
	// if run remotely we only allow one container arg
	if registry.IsRemote() {
		logsCommand.Use = "logs [options] CONTAINER"
		containerLogsCommand.Use = logsCommand.Use
	}

	// logs
	registry.Commands = append(registry.Commands, registry.CliCommand{
		Command: logsCommand,
	})
	logsFlags(logsCommand)
	validate.AddLatestFlag(logsCommand, &logsOptions.Latest)

	// container logs
	registry.Commands = append(registry.Commands, registry.CliCommand{
		Command: containerLogsCommand,
		Parent:  containerCmd,
	})
	logsFlags(containerLogsCommand)
	validate.AddLatestFlag(containerLogsCommand, &logsOptions.Latest)
}

func logsFlags(cmd *cobra.Command) {
	flags := cmd.Flags()

	flags.BoolVar(&logsOptions.Details, "details", false, "Show extra details provided to the logs")
	flags.BoolVarP(&logsOptions.Follow, "follow", "f", false, "Follow log output.  The default is false")

	sinceFlagName := "since"
	flags.StringVar(&logsOptions.SinceRaw, sinceFlagName, "", "Show logs since TIMESTAMP")
	_ = cmd.RegisterFlagCompletionFunc(sinceFlagName, completion.AutocompleteNone)

	untilFlagName := "until"
	flags.StringVar(&logsOptions.UntilRaw, untilFlagName, "", "Show logs until TIMESTAMP")
	_ = cmd.RegisterFlagCompletionFunc(untilFlagName, completion.AutocompleteNone)

	tailFlagName := "tail"
	flags.Int64Var(&logsOptions.Tail, tailFlagName, -1, "Output the specified number of LINES at the end of the logs.  Defaults to -1, which prints all lines")
	_ = cmd.RegisterFlagCompletionFunc(tailFlagName, completion.AutocompleteNone)

	flags.BoolVarP(&logsOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log")
	flags.BoolVarP(&logsOptions.Colors, "color", "", false, "Output the containers with different colors in the log.")
	flags.BoolVarP(&logsOptions.Names, "names", "n", false, "Output the container name in the log")

	flags.SetInterspersed(false)
	_ = flags.MarkHidden("details")
}

func logs(_ *cobra.Command, args []string) error {
	if logsOptions.SinceRaw != "" {
		// parse time, error out if something is wrong
		since, err := util.ParseInputTime(logsOptions.SinceRaw, true)
		if err != nil {
			return fmt.Errorf("error parsing --since %q: %w", logsOptions.SinceRaw, err)
		}
		logsOptions.Since = since
	}
	if logsOptions.UntilRaw != "" {
		// parse time, error out if something is wrong
		until, err := util.ParseInputTime(logsOptions.UntilRaw, false)
		if err != nil {
			return fmt.Errorf("error parsing --until %q: %w", logsOptions.UntilRaw, err)
		}
		logsOptions.Until = until
	}
	logsOptions.StdoutWriter = os.Stdout
	logsOptions.StderrWriter = os.Stderr
	return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions)
}