aboutsummaryrefslogtreecommitdiff
path: root/cmd/podman/logs.go
blob: 75947c34e243b89ca3452366c76043690a312f2d (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
package main

import (
	"os"
	"time"

	"github.com/containers/libpod/cmd/podman/libpodruntime"
	"github.com/containers/libpod/libpod"
	"github.com/containers/libpod/pkg/logs"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

var (
	logsFlags = []cli.Flag{
		cli.BoolFlag{
			Name:   "details",
			Usage:  "Show extra details provided to the logs",
			Hidden: true,
		},
		cli.BoolFlag{
			Name:  "follow, f",
			Usage: "Follow log output.  The default is false",
		},
		cli.StringFlag{
			Name:  "since",
			Usage: "Show logs since TIMESTAMP",
		},
		cli.Uint64Flag{
			Name:  "tail",
			Usage: "Output the specified number of LINES at the end of the logs.  Defaults to 0, which prints all lines",
		},
		cli.BoolFlag{
			Name:  "timestamps, t",
			Usage: "Output the timestamps in the log",
		},
		LatestFlag,
	}
	logsDescription = "The podman logs command batch-retrieves whatever logs are present for a container at the time of execution.  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 = cli.Command{
		Name:                   "logs",
		Usage:                  "Fetch the logs of a container",
		Description:            logsDescription,
		Flags:                  sortFlags(logsFlags),
		Action:                 logsCmd,
		ArgsUsage:              "CONTAINER",
		SkipArgReorder:         true,
		OnUsageError:           usageErrorHandler,
		UseShortOptionHandling: true,
	}
)

func logsCmd(c *cli.Context) error {
	var ctr *libpod.Container
	var err error
	if err := validateFlags(c, logsFlags); err != nil {
		return err
	}

	runtime, err := libpodruntime.GetRuntime(c)
	if err != nil {
		return errors.Wrapf(err, "could not get runtime")
	}
	defer runtime.Shutdown(false)

	args := c.Args()
	if len(args) != 1 && !c.Bool("latest") {
		return errors.Errorf("'podman logs' requires exactly one container name/ID")
	}

	sinceTime := time.Time{}
	if c.IsSet("since") {
		// parse time, error out if something is wrong
		since, err := parseInputTime(c.String("since"))
		if err != nil {
			return errors.Wrapf(err, "could not parse time: %q", c.String("since"))
		}
		sinceTime = since
	}

	opts := &logs.LogOptions{
		Details:    c.Bool("details"),
		Follow:     c.Bool("follow"),
		Since:      sinceTime,
		Tail:       c.Uint64("tail"),
		Timestamps: c.Bool("timestamps"),
	}

	if c.Bool("latest") {
		ctr, err = runtime.GetLatestContainer()
	} else {
		ctr, err = runtime.LookupContainer(args[0])
	}
	if err != nil {
		return err
	}

	logPath := ctr.LogPath()

	state, err := ctr.State()
	if err != nil {
		return err
	}

	// If the log file does not exist yet and the container is in the
	// Configured state, it has never been started before and no logs exist
	// Exit cleanly in this case
	if _, err := os.Stat(logPath); err != nil {
		if state == libpod.ContainerStateConfigured {
			logrus.Debugf("Container has not been started, no logs exist yet")
			return nil
		}
	}
	return logs.ReadLogs(logPath, ctr, opts)
}

// parseInputTime takes the users input and to determine if it is valid and
// returns a time format and error.  The input is compared to known time formats
// or a duration which implies no-duration
func parseInputTime(inputTime string) (time.Time, error) {
	timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999",
		"2006-01-02Z07:00", "2006-01-02"}
	// iterate the supported time formats
	for _, tf := range timeFormats {
		t, err := time.Parse(tf, inputTime)
		if err == nil {
			return t, nil
		}
	}

	// input might be a duration
	duration, err := time.ParseDuration(inputTime)
	if err != nil {
		return time.Time{}, errors.Errorf("unable to interpret time value")
	}
	return time.Now().Add(-duration), nil
}