podman/cmd/kpod/logs.go
Urvashi Mohnani ee4051db61 Update kpod logs to use the new container state and runtime
Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>

Closes: #62
Approved by: rhatdan
2017-11-22 19:33:13 +00:00

154 lines
3.9 KiB
Go

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 kpod 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 kpod run (i.e. your run may not have generated any logs at the time you execute kpod 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("'kpod 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
}