diff options
Diffstat (limited to 'cmd/podman/logs.go')
-rw-r--r-- | cmd/podman/logs.go | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go new file mode 100644 index 000000000..8745d5d7f --- /dev/null +++ b/cmd/podman/logs.go @@ -0,0 +1,153 @@ +package main + +import ( + "fmt" + "strings" + "time" + + "github.com/hpcloud/tail" + "github.com/pkg/errors" + "github.com/projectatomic/libpod/libpod" + "github.com/urfave/cli" +) + +type logOptions struct { + details bool + follow bool + sinceTime time.Time + tail uint64 +} + +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", + }, + } + 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: logsFlags, + Action: logsCmd, + ArgsUsage: "CONTAINER", + } +) + +func logsCmd(c *cli.Context) error { + if err := validateFlags(c, logsFlags); err != nil { + return err + } + + runtime, err := getRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + args := c.Args() + if len(args) != 1 { + 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 := time.Parse("2006-01-02T15:04:05.999999999-07:00", c.String("since")) + if err != nil { + return errors.Wrapf(err, "could not parse time: %q", c.String("since")) + } + sinceTime = since + } + + opts := logOptions{ + details: c.Bool("details"), + follow: c.Bool("follow"), + sinceTime: sinceTime, + tail: c.Uint64("tail"), + } + + ctr, err := runtime.LookupContainer(args[0]) + if err != nil { + return err + } + + logs := make(chan string) + go func() { + err = getLogs(ctr, logs, opts) + }() + printLogs(logs) + return err +} + +// getLogs returns the logs of a container from the log file +// log file is created when the container is started/ran +func getLogs(container *libpod.Container, logChan chan string, opts logOptions) error { + defer close(logChan) + + seekInfo := &tail.SeekInfo{Offset: 0, Whence: 0} + if opts.tail > 0 { + // seek to correct position in log files + seekInfo.Offset = int64(opts.tail) + seekInfo.Whence = 2 + } + + t, err := tail.TailFile(container.LogPath(), tail.Config{Follow: opts.follow, ReOpen: false, Location: seekInfo}) + for line := range t.Lines { + if since, err := logSinceTime(opts.sinceTime, line.Text); err != nil || !since { + continue + } + logMessage := line.Text[secondSpaceIndex(line.Text):] + logChan <- logMessage + } + return err +} + +func printLogs(logs chan string) { + for line := range logs { + fmt.Println(line) + } +} + +// returns true if the time stamps of the logs are equal to or after the +// timestamp comparing to +func logSinceTime(sinceTime time.Time, logStr string) (bool, error) { + timestamp := strings.Split(logStr, " ")[0] + logTime, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", timestamp) + if err != nil { + return false, err + } + return logTime.After(sinceTime) || logTime.Equal(sinceTime), nil +} + +// secondSpaceIndex returns the index of the second space in a string +// In a line of the logs, the first two tokens are a timestamp and stdout/stderr, +// followed by the message itself. This allows us to get the index of the message +// and avoid sending the other information back to the caller of GetLogs() +func secondSpaceIndex(line string) int { + index := strings.Index(line, " ") + if index == -1 { + return 0 + } + index = strings.Index(line[index:], " ") + if index == -1 { + return 0 + } + return index +} |