summaryrefslogtreecommitdiff
path: root/cmd/podman/logs.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/podman/logs.go')
-rw-r--r--cmd/podman/logs.go153
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
+}