summaryrefslogtreecommitdiff
path: root/cmd/kpod/logs.go
blob: 726ba4a65d68600251b7be2a98e94a4ca0fb3b48 (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
145
146
147
148
149
150
151
152
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 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
}