mirror of https://github.com/containers/podman.git
Update kpod logs to use the new container state and runtime
Signed-off-by: Urvashi Mohnani <umohnani@redhat.com> Closes: #62 Approved by: rhatdan
This commit is contained in:
parent
40dce698d3
commit
ee4051db61
105
cmd/kpod/logs.go
105
cmd/kpod/logs.go
|
@ -2,13 +2,22 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hpcloud/tail"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/projectatomic/libpod/libkpod"
|
"github.com/projectatomic/libpod/libpod"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type logOptions struct {
|
||||||
|
details bool
|
||||||
|
follow bool
|
||||||
|
sinceTime time.Time
|
||||||
|
tail uint64
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logsFlags = []cli.Flag{
|
logsFlags = []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
|
@ -42,51 +51,103 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func logsCmd(c *cli.Context) error {
|
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()
|
args := c.Args()
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.Errorf("'kpod logs' requires exactly one container name/ID")
|
return errors.Errorf("'kpod logs' requires exactly one container name/ID")
|
||||||
}
|
}
|
||||||
if err := validateFlags(c, logsFlags); err != nil {
|
|
||||||
return err
|
sinceTime := time.Time{}
|
||||||
}
|
|
||||||
container := c.Args().First()
|
|
||||||
var opts libkpod.LogOptions
|
|
||||||
opts.Details = c.Bool("details")
|
|
||||||
opts.Follow = c.Bool("follow")
|
|
||||||
opts.SinceTime = time.Time{}
|
|
||||||
if c.IsSet("since") {
|
if c.IsSet("since") {
|
||||||
// parse time, error out if something is wrong
|
// parse time, error out if something is wrong
|
||||||
since, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", c.String("since"))
|
since, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", c.String("since"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "could not parse time: %q", c.String("since"))
|
return errors.Wrapf(err, "could not parse time: %q", c.String("since"))
|
||||||
}
|
}
|
||||||
opts.SinceTime = since
|
sinceTime = since
|
||||||
}
|
}
|
||||||
opts.Tail = c.Uint64("tail")
|
|
||||||
|
|
||||||
config, err := getConfig(c)
|
opts := logOptions{
|
||||||
if err != nil {
|
details: c.Bool("details"),
|
||||||
return errors.Wrapf(err, "could not get config")
|
follow: c.Bool("follow"),
|
||||||
|
sinceTime: sinceTime,
|
||||||
|
tail: c.Uint64("tail"),
|
||||||
}
|
}
|
||||||
server, err := libkpod.New(config)
|
|
||||||
|
ctr, err := runtime.LookupContainer(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "could not create container server")
|
return err
|
||||||
}
|
|
||||||
defer server.Shutdown()
|
|
||||||
err = server.Update()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "could not update list of containers")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logs := make(chan string)
|
logs := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
err = server.GetLogs(container, logs, opts)
|
err = getLogs(ctr, logs, opts)
|
||||||
}()
|
}()
|
||||||
printLogs(logs)
|
printLogs(logs)
|
||||||
return err
|
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) {
|
func printLogs(logs chan string) {
|
||||||
for line := range logs {
|
for line := range logs {
|
||||||
fmt.Println(line)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
package libkpod
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hpcloud/tail"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LogOptions contains all of the options for displaying logs in kpod
|
|
||||||
type LogOptions struct {
|
|
||||||
Details bool
|
|
||||||
Follow bool
|
|
||||||
SinceTime time.Time
|
|
||||||
Tail uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogs gets each line of a log file and, if it matches the criteria in logOptions, sends it down logChan
|
|
||||||
func (c *ContainerServer) GetLogs(container string, logChan chan string, opts LogOptions) error {
|
|
||||||
defer close(logChan)
|
|
||||||
// Get the full ID of the container
|
|
||||||
ctr, err := c.LookupContainer(container)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
containerID := ctr.ID()
|
|
||||||
sandbox := ctr.Sandbox()
|
|
||||||
if sandbox == "" {
|
|
||||||
sandbox = containerID
|
|
||||||
}
|
|
||||||
// Read the log line by line and pass it into the pipe
|
|
||||||
logsFile := path.Join(c.config.LogDir, sandbox, containerID+".log")
|
|
||||||
|
|
||||||
seekInfo := &tail.SeekInfo{Offset: 0, Whence: 0}
|
|
||||||
if opts.Tail > 0 {
|
|
||||||
// seek to correct position in logs files
|
|
||||||
seekInfo.Offset = int64(opts.Tail)
|
|
||||||
seekInfo.Whence = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := tail.TailFile(logsFile, tail.Config{Follow: false, 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):]
|
|
||||||
if opts.Details {
|
|
||||||
// add additional information to line
|
|
||||||
}
|
|
||||||
logChan <- logMessage
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
load helpers
|
load helpers
|
||||||
|
|
||||||
IMAGE="alpine:latest"
|
|
||||||
|
|
||||||
function teardown() {
|
function teardown() {
|
||||||
cleanup_test
|
cleanup_test
|
||||||
}
|
}
|
||||||
|
@ -13,69 +11,42 @@ function setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "display logs for container" {
|
@test "display logs for container" {
|
||||||
skip "Test needs to be converted to kpod run"
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} run -d $BB ls"
|
||||||
start_crio
|
|
||||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
|
||||||
echo "$output"
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
pod_id="$output"
|
|
||||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
|
||||||
echo "$output"
|
echo "$output"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
ctr_id="$output"
|
ctr_id="$output"
|
||||||
run crioctl ctr start --id "$ctr_id"
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} logs $ctr_id"
|
||||||
echo "$output"
|
echo "$output"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
run bash -c ${KPOD_BINARY} $KPOD_OPTIONS logs "$ctr_id"
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} rm $ctr_id"
|
||||||
echo "$output"
|
echo "$output"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
cleanup_ctrs
|
|
||||||
cleanup_pods
|
|
||||||
stop_crio
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "tail three lines of logs for container" {
|
@test "tail three lines of logs for container" {
|
||||||
skip "Test needs to be converted to kpod run"
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} run -d $BB ls"
|
||||||
start_crio
|
|
||||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
|
||||||
echo "$output"
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
pod_id="$output"
|
|
||||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
|
||||||
echo "$output"
|
echo "$output"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
ctr_id="$output"
|
ctr_id="$output"
|
||||||
run crioctl ctr start --id "$ctr_id"
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} logs --tail 3 $ctr_id"
|
||||||
echo "$output"
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
run bash -c ${KPOD_BINARY} $KPOD_OPTIONS logs --tail 3 $ctr_id
|
|
||||||
echo "$output"
|
echo "$output"
|
||||||
lines=$(echo "$output" | wc -l)
|
lines=$(echo "$output" | wc -l)
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
[[ $(wc -l < "$output" ) -le 3 ]]
|
[[ $(wc -l < "$output" ) -le 3 ]]
|
||||||
cleanup_ctrs
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} rm $ctr_id"
|
||||||
cleanup_pods
|
echo "$output"
|
||||||
stop_crio
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "display logs for container since a given time" {
|
@test "display logs for container since a given time" {
|
||||||
skip "Test needs to be converted to kpod run"
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} run -d $BB ls"
|
||||||
start_crio
|
|
||||||
run crioctl pod run --config "$TESTDATA"/sandbox_config.json
|
|
||||||
echo "$output"
|
|
||||||
[ "$status" -eq 0 ]
|
|
||||||
pod_id="$output"
|
|
||||||
run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id"
|
|
||||||
echo "$output"
|
echo "$output"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
ctr_id="$output"
|
ctr_id="$output"
|
||||||
run crioctl ctr start --id "$ctr_id"
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} logs --since 2017-08-07T10:10:09.056611202-04:00 $ctr_id"
|
||||||
echo "$output"
|
echo "$output"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
run bash -c ${KPOD_BINARY} $KPOD_OPTIONS logs --since 2017-08-07T10:10:09.056611202-04:00 $ctr_id
|
run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} rm $ctr_id"
|
||||||
echo "$output"
|
echo "$output"
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
cleanup_ctrs
|
|
||||||
cleanup_pods
|
|
||||||
stop_crio
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue