mirror of
https://github.com/containers/podman
synced 2024-10-20 01:03:51 +00:00
display logs for multiple containers at the same time
add the ability for users to specify more than one container at a time while using podman logs. If more than one container is being displayed, podman will also prepend a shortened container id of the container on the log line. also, enabled the podman-remote logs command during the refactoring of the above ability. fixes issue #2219 Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
parent
6e4c32967e
commit
5e86acd591
|
@ -21,7 +21,6 @@ func getMainCommands() []*cobra.Command {
|
|||
&_psCommand,
|
||||
_loginCommand,
|
||||
_logoutCommand,
|
||||
_logsCommand,
|
||||
_mountCommand,
|
||||
_pauseCommand,
|
||||
_portCommand,
|
||||
|
@ -63,7 +62,6 @@ func getContainerSubCommands() []*cobra.Command {
|
|||
_execCommand,
|
||||
_exportCommand,
|
||||
_killCommand,
|
||||
_logsCommand,
|
||||
_mountCommand,
|
||||
_pauseCommand,
|
||||
_portCommand,
|
||||
|
|
|
@ -53,6 +53,7 @@ var (
|
|||
_containerExistsCommand,
|
||||
_contInspectSubCommand,
|
||||
_listSubCommand,
|
||||
_logsCommand,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -1,27 +1,24 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containers/libpod/cmd/podman/cliconfig"
|
||||
"github.com/containers/libpod/cmd/podman/libpodruntime"
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/pkg/logs"
|
||||
"github.com/containers/libpod/pkg/adapter"
|
||||
"github.com/containers/libpod/pkg/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
logsCommand cliconfig.LogsValues
|
||||
logsDescription = `Retrieves logs for a container.
|
||||
logsDescription = `Retrieves logs for one or more containers.
|
||||
|
||||
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 = &cobra.Command{
|
||||
Use: "logs [flags] CONTAINER",
|
||||
Use: "logs [flags] CONTAINER [CONTAINER...]",
|
||||
Short: "Fetch the logs of a container",
|
||||
Long: logsDescription,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
@ -29,9 +26,19 @@ var (
|
|||
logsCommand.GlobalFlags = MainGlobalOpts
|
||||
return logsCmd(&logsCommand)
|
||||
},
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 && logsCommand.Latest {
|
||||
return errors.New("no containers can be specified when using 'latest'")
|
||||
}
|
||||
if !logsCommand.Latest && len(args) < 1 {
|
||||
return errors.New("specify at least one container name or ID to log")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Example: `podman logs ctrID
|
||||
podman logs --tail 2 mywebserver
|
||||
podman logs --follow=true --since 10m ctrID`,
|
||||
podman logs --follow=true --since 10m ctrID
|
||||
podman logs mywebserver mydbserver`,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -54,20 +61,14 @@ func init() {
|
|||
}
|
||||
|
||||
func logsCmd(c *cliconfig.LogsValues) error {
|
||||
var ctr *libpod.Container
|
||||
var err error
|
||||
|
||||
runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
|
||||
runtime, err := adapter.GetRuntime(&c.PodmanCommand)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get runtime")
|
||||
}
|
||||
defer runtime.Shutdown(false)
|
||||
|
||||
args := c.InputArgs
|
||||
if len(args) != 1 && !c.Latest {
|
||||
return errors.Errorf("'podman logs' requires exactly one container name/ID")
|
||||
}
|
||||
|
||||
sinceTime := time.Time{}
|
||||
if c.Flag("since").Changed {
|
||||
// parse time, error out if something is wrong
|
||||
|
@ -78,7 +79,7 @@ func logsCmd(c *cliconfig.LogsValues) error {
|
|||
sinceTime = since
|
||||
}
|
||||
|
||||
opts := &logs.LogOptions{
|
||||
opts := &libpod.LogOptions{
|
||||
Details: c.Details,
|
||||
Follow: c.Follow,
|
||||
Since: sinceTime,
|
||||
|
@ -86,30 +87,5 @@ func logsCmd(c *cliconfig.LogsValues) error {
|
|||
Timestamps: c.Timestamps,
|
||||
}
|
||||
|
||||
if c.Latest {
|
||||
ctr, err = runtime.GetLatestContainer()
|
||||
} else {
|
||||
ctr, err = runtime.LookupContainer(args[0])
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logPath := ctr.LogPath()
|
||||
|
||||
state, err := ctr.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the log file does not exist yet and the container is in the
|
||||
// Configured state, it has never been started before and no logs exist
|
||||
// Exit cleanly in this case
|
||||
if _, err := os.Stat(logPath); err != nil {
|
||||
if state == libpod.ContainerStateConfigured {
|
||||
logrus.Debugf("Container has not been started, no logs exist yet")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return logs.ReadLogs(logPath, ctr, opts)
|
||||
return runtime.Log(c, opts)
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ var mainCommands = []*cobra.Command{
|
|||
&_inspectCommand,
|
||||
_killCommand,
|
||||
_loadCommand,
|
||||
_logsCommand,
|
||||
podCommand.Command,
|
||||
_pullCommand,
|
||||
_pushCommand,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/buildah/pkg/formats"
|
||||
|
@ -79,7 +80,10 @@ func searchCmd(c *cliconfig.SearchValues) error {
|
|||
return err
|
||||
}
|
||||
format := genSearchFormat(c.Format)
|
||||
out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: results[0].HeaderMap()}
|
||||
if len(results) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: genSearchOutputMap()}
|
||||
formats.Writer(out).Out()
|
||||
return nil
|
||||
}
|
||||
|
@ -99,3 +103,16 @@ func searchToGeneric(params []image.SearchResult) (genericParams []interface{})
|
|||
}
|
||||
return genericParams
|
||||
}
|
||||
|
||||
func genSearchOutputMap() map[string]string {
|
||||
io := image.SearchResult{}
|
||||
v := reflect.Indirect(reflect.ValueOf(io))
|
||||
values := make(map[string]string)
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
key := v.Type().Field(i).Name
|
||||
value := key
|
||||
values[key] = strings.ToUpper(splitCamelCase(value))
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
|
|
@ -19,6 +19,14 @@ type StringResponse (
|
|||
message: string
|
||||
)
|
||||
|
||||
type LogLine (
|
||||
device: string,
|
||||
parseLogType : string,
|
||||
time: string,
|
||||
msg: string,
|
||||
cid: string
|
||||
)
|
||||
|
||||
# ContainerChanges describes the return struct for ListContainerChanges
|
||||
type ContainerChanges (
|
||||
changed: []string,
|
||||
|
@ -522,6 +530,8 @@ method ListContainerProcesses(name: string, opts: []string) -> (container: []str
|
|||
# capability of varlink if the client invokes it.
|
||||
method GetContainerLogs(name: string) -> (container: []string)
|
||||
|
||||
method GetContainersLogs(names: []string, follow: bool, latest: bool, since: string, tail: int, timestamps: bool) -> (log: LogLine)
|
||||
|
||||
# ListContainerChanges takes a name or ID of a container and returns changes between the container and
|
||||
# its base image. It returns a struct of changed, deleted, and added path names.
|
||||
method ListContainerChanges(name: string) -> (container: ContainerChanges)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
% podman-logs(1)
|
||||
|
||||
## NAME
|
||||
podman\-logs - Fetch the logs of a container
|
||||
podman\-logs - Fetch the logs of one or more containers
|
||||
|
||||
## SYNOPSIS
|
||||
**podman** **logs** [*options*] *container*
|
||||
**podman** **logs** [*options*] *container* [*container...*]
|
||||
|
||||
## DESCRIPTION
|
||||
The podman logs command batch-retrieves whatever logs are present for a container at the time of execution.
|
||||
The podman logs command batch-retrieves whatever logs are present for one or more containers 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
|
||||
|
||||
|
|
208
libpod/container_log.go
Normal file
208
libpod/container_log.go
Normal file
|
@ -0,0 +1,208 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hpcloud/tail"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// logTimeFormat is the time format used in the log.
|
||||
// It is a modified version of RFC3339Nano that guarantees trailing
|
||||
// zeroes are not trimmed, taken from
|
||||
// https://github.com/golang/go/issues/19635
|
||||
logTimeFormat = "2006-01-02T15:04:05.000000000Z07:00"
|
||||
)
|
||||
|
||||
// LogOptions is the options you can use for logs
|
||||
type LogOptions struct {
|
||||
Details bool
|
||||
Follow bool
|
||||
Since time.Time
|
||||
Tail uint64
|
||||
Timestamps bool
|
||||
Multi bool
|
||||
WaitGroup *sync.WaitGroup
|
||||
}
|
||||
|
||||
// LogLine describes the information for each line of a log
|
||||
type LogLine struct {
|
||||
Device string
|
||||
ParseLogType string
|
||||
Time time.Time
|
||||
Msg string
|
||||
CID string
|
||||
}
|
||||
|
||||
// Log is a runtime function that can read one or more container logs.
|
||||
func (r *Runtime) Log(containers []*Container, options *LogOptions, logChannel chan *LogLine) error {
|
||||
for _, ctr := range containers {
|
||||
if err := ctr.ReadLog(options, logChannel); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadLog reads a containers log based on the input options and returns loglines over a channel
|
||||
func (c *Container) ReadLog(options *LogOptions, logChannel chan *LogLine) error {
|
||||
t, tailLog, err := getLogFile(c.LogPath(), options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to read log file %s for %s ", c.ID(), c.LogPath())
|
||||
}
|
||||
options.WaitGroup.Add(1)
|
||||
if len(tailLog) > 0 {
|
||||
for _, nll := range tailLog {
|
||||
nll.CID = c.ID()
|
||||
if nll.Since(options.Since) {
|
||||
logChannel <- nll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
var partial string
|
||||
for line := range t.Lines {
|
||||
nll, err := newLogLine(line.Text)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
if nll.Partial() {
|
||||
partial = partial + nll.Msg
|
||||
continue
|
||||
} else if !nll.Partial() && len(partial) > 1 {
|
||||
nll.Msg = partial
|
||||
partial = ""
|
||||
}
|
||||
nll.CID = c.ID()
|
||||
if nll.Since(options.Since) {
|
||||
logChannel <- nll
|
||||
}
|
||||
}
|
||||
options.WaitGroup.Done()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// getLogFile returns an hp tail for a container given options
|
||||
func getLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error) {
|
||||
var (
|
||||
whence int
|
||||
err error
|
||||
logTail []*LogLine
|
||||
)
|
||||
// whence 0=origin, 2=end
|
||||
if options.Tail > 0 {
|
||||
whence = 2
|
||||
logTail, err = getTailLog(path, int(options.Tail))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
seek := tail.SeekInfo{
|
||||
Offset: 0,
|
||||
Whence: whence,
|
||||
}
|
||||
|
||||
t, err := tail.TailFile(path, tail.Config{Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger})
|
||||
return t, logTail, err
|
||||
}
|
||||
|
||||
func getTailLog(path string, tail int) ([]*LogLine, error) {
|
||||
var (
|
||||
tailLog []*LogLine
|
||||
nlls []*LogLine
|
||||
tailCounter int
|
||||
partial string
|
||||
)
|
||||
content, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
splitContent := strings.Split(string(content), "\n")
|
||||
// We read the content in reverse and add each nll until we have the same
|
||||
// number of F type messages as the desired tail
|
||||
for i := len(splitContent) - 1; i >= 0; i-- {
|
||||
if len(splitContent[i]) == 0 {
|
||||
continue
|
||||
}
|
||||
nll, err := newLogLine(splitContent[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nlls = append(nlls, nll)
|
||||
if !nll.Partial() {
|
||||
tailCounter = tailCounter + 1
|
||||
}
|
||||
if tailCounter == tail {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Now we iterate the results and assemble partial messages to become full messages
|
||||
for _, nll := range nlls {
|
||||
if nll.Partial() {
|
||||
partial = partial + nll.Msg
|
||||
} else {
|
||||
nll.Msg = nll.Msg + partial
|
||||
tailLog = append(tailLog, nll)
|
||||
partial = ""
|
||||
}
|
||||
}
|
||||
return tailLog, nil
|
||||
}
|
||||
|
||||
// String converts a logline to a string for output given whether a detail
|
||||
// bool is specified.
|
||||
func (l *LogLine) String(options *LogOptions) string {
|
||||
var out string
|
||||
if options.Multi {
|
||||
cid := l.CID
|
||||
if len(cid) > 12 {
|
||||
cid = cid[:12]
|
||||
}
|
||||
out = fmt.Sprintf("%s ", cid)
|
||||
}
|
||||
if options.Timestamps {
|
||||
out = out + fmt.Sprintf("%s ", l.Time.Format(logTimeFormat))
|
||||
}
|
||||
return out + l.Msg
|
||||
}
|
||||
|
||||
// Since returns a bool as to whether a log line occurred after a given time
|
||||
func (l *LogLine) Since(since time.Time) bool {
|
||||
return l.Time.After(since)
|
||||
}
|
||||
|
||||
// newLogLine creates a logLine struct from a container log string
|
||||
func newLogLine(line string) (*LogLine, error) {
|
||||
splitLine := strings.Split(line, " ")
|
||||
if len(splitLine) < 4 {
|
||||
return nil, errors.Errorf("'%s' is not a valid container log line", line)
|
||||
}
|
||||
logTime, err := time.Parse(time.RFC3339Nano, splitLine[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0])
|
||||
}
|
||||
l := LogLine{
|
||||
Time: logTime,
|
||||
Device: splitLine[1],
|
||||
ParseLogType: splitLine[2],
|
||||
Msg: strings.Join(splitLine[3:], " "),
|
||||
}
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
// Partial returns a bool if the log line is a partial log type
|
||||
func (l *LogLine) Partial() bool {
|
||||
if l.ParseLogType == "P" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -92,5 +92,5 @@ func (r *Runtime) getTail(fromStart, stream bool) (*tail.Tail, error) {
|
|||
seek.Whence = 0
|
||||
reopen = false
|
||||
}
|
||||
return tail.TailFile(r.config.EventsLogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek})
|
||||
return tail.TailFile(r.config.EventsLogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger})
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@ package adapter
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
@ -127,3 +129,28 @@ func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.Wait
|
|||
}
|
||||
return ok, failures, err
|
||||
}
|
||||
|
||||
// Log logs one or more containers
|
||||
func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error {
|
||||
var wg sync.WaitGroup
|
||||
options.WaitGroup = &wg
|
||||
if len(c.InputArgs) > 1 {
|
||||
options.Multi = true
|
||||
}
|
||||
logChannel := make(chan *libpod.LogLine, int(c.Tail)*len(c.InputArgs)+1)
|
||||
containers, err := shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.Runtime.Log(containers, options, logChannel); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(logChannel)
|
||||
}()
|
||||
for line := range logChannel {
|
||||
fmt.Println(line.String(options))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,18 +5,19 @@ package adapter
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containers/libpod/cmd/podman/cliconfig"
|
||||
"github.com/containers/libpod/cmd/podman/shared"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
iopodman "github.com/containers/libpod/cmd/podman/varlink"
|
||||
"github.com/containers/libpod/cmd/podman/varlink"
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/pkg/inspect"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/varlink/go/varlink"
|
||||
)
|
||||
|
||||
// Inspect returns an inspect struct from varlink
|
||||
|
@ -223,3 +224,41 @@ func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContai
|
|||
}
|
||||
return bcs, nil
|
||||
}
|
||||
|
||||
// Logs one or more containers over a varlink connection
|
||||
func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error {
|
||||
//GetContainersLogs
|
||||
reply, err := iopodman.GetContainersLogs().Send(r.Conn, uint64(varlink.More), c.InputArgs, c.Follow, c.Latest, options.Since.Format(time.RFC3339Nano), int64(c.Tail), c.Timestamps)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get container logs")
|
||||
}
|
||||
if len(c.InputArgs) > 1 {
|
||||
options.Multi = true
|
||||
}
|
||||
for {
|
||||
log, flags, err := reply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if log.Time == "" && log.Msg == "" {
|
||||
// We got a blank log line which can signal end of stream
|
||||
break
|
||||
}
|
||||
lTime, err := time.Parse(time.RFC3339Nano, log.Time)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to parse time of log %s", log.Time)
|
||||
}
|
||||
logLine := libpod.LogLine{
|
||||
Device: log.Device,
|
||||
ParseLogType: log.ParseLogType,
|
||||
Time: lTime,
|
||||
Msg: log.Msg,
|
||||
CID: log.Cid,
|
||||
}
|
||||
fmt.Println(logLine.String(options))
|
||||
if flags&varlink.Continues == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
@ -602,3 +603,56 @@ func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.
|
|||
}
|
||||
return cstats
|
||||
}
|
||||
|
||||
// GetContainersLogs is the varlink endpoint to obtain one or more container logs
|
||||
func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error {
|
||||
var wg sync.WaitGroup
|
||||
if call.WantsMore() {
|
||||
call.Continues = true
|
||||
}
|
||||
sinceTime, err := time.Parse(time.RFC3339Nano, since)
|
||||
if err != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
options := libpod.LogOptions{
|
||||
Follow: follow,
|
||||
Since: sinceTime,
|
||||
Tail: uint64(tail),
|
||||
Timestamps: timestamps,
|
||||
}
|
||||
|
||||
options.WaitGroup = &wg
|
||||
if len(names) > 1 {
|
||||
options.Multi = true
|
||||
}
|
||||
logChannel := make(chan *libpod.LogLine, int(tail)*len(names)+1)
|
||||
containers, err := shortcuts.GetContainersByContext(false, latest, names, i.Runtime)
|
||||
if err != nil {
|
||||
return call.ReplyErrorOccurred(err.Error())
|
||||
}
|
||||
if err := i.Runtime.Log(containers, &options, logChannel); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(logChannel)
|
||||
}()
|
||||
for line := range logChannel {
|
||||
call.ReplyGetContainersLogs(newPodmanLogLine(line))
|
||||
if !call.Continues {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
return call.ReplyGetContainersLogs(iopodman.LogLine{})
|
||||
}
|
||||
|
||||
func newPodmanLogLine(line *libpod.LogLine) iopodman.LogLine {
|
||||
return iopodman.LogLine{
|
||||
Device: line.Device,
|
||||
ParseLogType: line.ParseLogType,
|
||||
Time: line.Time.Format(time.RFC3339Nano),
|
||||
Msg: line.Msg,
|
||||
Cid: line.CID,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ package integration
|
|||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
. "github.com/containers/libpod/test/utils"
|
||||
. "github.com/onsi/ginkgo"
|
||||
|
@ -34,7 +35,6 @@ var _ = Describe("Podman logs", func() {
|
|||
|
||||
})
|
||||
|
||||
//sudo bin/podman run -it --rm fedora-minimal bash -c 'for a in `seq 5`; do echo hello; done'
|
||||
It("podman logs for container", func() {
|
||||
logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"})
|
||||
logc.WaitWithDefaultTimeout()
|
||||
|
@ -106,4 +106,30 @@ var _ = Describe("Podman logs", func() {
|
|||
Expect(results.ExitCode()).To(Equal(0))
|
||||
Expect(len(results.OutputToStringArray())).To(Equal(3))
|
||||
})
|
||||
|
||||
It("podman logs latest and container name should fail", func() {
|
||||
results := podmanTest.Podman([]string{"logs", "-l", "foobar"})
|
||||
results.WaitWithDefaultTimeout()
|
||||
Expect(results.ExitCode()).ToNot(Equal(0))
|
||||
})
|
||||
|
||||
It("podman logs two containers and should display short container IDs", func() {
|
||||
log1 := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"})
|
||||
log1.WaitWithDefaultTimeout()
|
||||
Expect(log1.ExitCode()).To(Equal(0))
|
||||
cid1 := log1.OutputToString()
|
||||
|
||||
log2 := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"})
|
||||
log2.WaitWithDefaultTimeout()
|
||||
Expect(log2.ExitCode()).To(Equal(0))
|
||||
cid2 := log2.OutputToString()
|
||||
|
||||
results := podmanTest.Podman([]string{"logs", cid1, cid2})
|
||||
results.WaitWithDefaultTimeout()
|
||||
Expect(results.ExitCode()).To(Equal(0))
|
||||
|
||||
output := results.OutputToStringArray()
|
||||
Expect(len(output)).To(Equal(6))
|
||||
Expect(strings.Contains(output[0], cid1[:12]) || strings.Contains(output[0], cid2[:12])).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue