Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/podman/containers/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ var (
ValidArgsFunction: logsCommand.ValidArgsFunction,
Example: `podman container logs ctrID
podman container logs --names ctrID1 ctrID2
podman container logs --color --names ctrID1 ctrID2
podman container logs --tail 2 mywebserver
podman container logs --follow=true --since 10m ctrID
podman container logs mywebserver mydbserver`,
Expand Down Expand Up @@ -112,7 +113,9 @@ func logsFlags(cmd *cobra.Command) {
_ = cmd.RegisterFlagCompletionFunc(tailFlagName, completion.AutocompleteNone)

flags.BoolVarP(&logsOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log")
flags.BoolVarP(&logsOptions.Colors, "color", "", false, "Output the containers with different colors in the log.")
flags.BoolVarP(&logsOptions.Names, "names", "n", false, "Output the container name in the log")

flags.SetInterspersed(false)
_ = flags.MarkHidden("details")
}
Expand Down
2 changes: 2 additions & 0 deletions cmd/podman/pods/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ func logsFlags(cmd *cobra.Command) {

flags.BoolVarP(&logsPodOptions.Names, "names", "n", false, "Output container names instead of container IDs in the log")
flags.BoolVarP(&logsPodOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log")
flags.BoolVarP(&logsPodOptions.Colors, "color", "", false, "Output the containers within a pod with different colors in the log")

flags.SetInterspersed(false)
_ = flags.MarkHidden("details")
}
Expand Down
4 changes: 4 additions & 0 deletions docs/source/markdown/podman-logs.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ any logs at the time you execute podman logs).

## OPTIONS

#### **--color**

Output the containers with different colors in the log.

#### **--follow**, **-f**

Follow log output. Default is false.
Expand Down
4 changes: 4 additions & 0 deletions docs/source/markdown/podman-pod-logs.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Note: Long running command of `podman pod log` with a `-f` or `--follow` needs t

## OPTIONS

#### **--color**

Output the containers with different colors in the log.

#### **--container**, **-c**

By default `podman pod logs` retrieves logs for all the containers available within the pod differentiate by field `container`. However there are use-cases where user would want to limit the log stream only to a particular container of a pod for such cases `-c` can be used like `podman pod logs -c ctrNameorID podname`.
Expand Down
14 changes: 8 additions & 6 deletions libpod/container_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,35 @@ func init() {

// Log is a runtime function that can read one or more container logs.
func (r *Runtime) Log(ctx context.Context, containers []*Container, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
for _, ctr := range containers {
if err := ctr.ReadLog(ctx, options, logChannel); err != nil {
for c, ctr := range containers {
if err := ctr.ReadLog(ctx, options, logChannel, int64(c)); err != nil {
return err
}
}
return nil
}

// ReadLog reads a containers log based on the input options and returns log lines over a channel.
func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error {
switch c.LogDriver() {
case define.PassthroughLogging:
return errors.Wrapf(define.ErrNoLogs, "this container is using the 'passthrough' log driver, cannot read logs")
case define.NoLogging:
return errors.Wrapf(define.ErrNoLogs, "this container is using the 'none' log driver, cannot read logs")
case define.JournaldLogging:
return c.readFromJournal(ctx, options, logChannel)
return c.readFromJournal(ctx, options, logChannel, colorID)
case define.JSONLogging:
// TODO provide a separate implementation of this when Conmon
// has support.
fallthrough
case define.KubernetesLogging, "":
return c.readFromLogFile(ctx, options, logChannel)
return c.readFromLogFile(ctx, options, logChannel, colorID)
default:
return errors.Wrapf(define.ErrInternal, "unrecognized log driver %q, cannot read logs", c.LogDriver())
}
}

func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error {
t, tailLog, err := logs.GetLogFile(c.LogPath(), options)
if err != nil {
// If the log file does not exist, this is not fatal.
Expand All @@ -65,6 +65,7 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption
for _, nll := range tailLog {
nll.CID = c.ID()
nll.CName = c.Name()
nll.ColorID = colorID
if nll.Since(options.Since) && nll.Until(options.Until) {
logChannel <- nll
}
Expand Down Expand Up @@ -97,6 +98,7 @@ func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOption
}
nll.CID = c.ID()
nll.CName = c.Name()
nll.ColorID = colorID
if nll.Since(options.Since) && nll.Until(options.Until) {
logChannel <- nll
}
Expand Down
3 changes: 2 additions & 1 deletion libpod/container_log_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (c *Container) initializeJournal(ctx context.Context) error {
return journal.Send("", journal.PriInfo, m)
}

func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error {
// We need the container's events in the same journal to guarantee
// consistency, see #10323.
if options.Follow && c.runtime.config.Engine.EventsLogger != "journald" {
Expand Down Expand Up @@ -231,6 +231,7 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption
}

logLine, err := logs.NewJournaldLogLine(message, options.Multi)
logLine.ColorID = colorID
if err != nil {
logrus.Errorf("Failed parse log line: %v", err)
return
Expand Down
2 changes: 1 addition & 1 deletion libpod/container_log_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/pkg/errors"
)

func (c *Container) readFromJournal(_ context.Context, _ *logs.LogOptions, _ chan *logs.LogLine) error {
func (c *Container) readFromJournal(_ context.Context, _ *logs.LogOptions, _ chan *logs.LogLine, colorID int64) error {
return errors.Wrapf(define.ErrOSNotSupported, "Journald logging only enabled with systemd on linux")
}

Expand Down
33 changes: 32 additions & 1 deletion libpod/logs/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const (

// FullLogType signifies a log line is full
FullLogType = "F"

//ANSIEscapeResetCode is a code that resets all colors and text effects
ANSIEscapeResetCode = "\033[0m"
)

// LogOptions is the options you can use for logs
Expand All @@ -37,6 +40,7 @@ type LogOptions struct {
Until time.Time
Tail int64
Timestamps bool
Colors bool
Multi bool
WaitGroup *sync.WaitGroup
UseName bool
Expand All @@ -50,6 +54,7 @@ type LogLine struct {
Msg string
CID string
CName string
ColorID int64
}

// GetLogFile returns an hp tail for a container given options
Expand Down Expand Up @@ -162,6 +167,24 @@ func getTailLog(path string, tail int) ([]*LogLine, error) {
return tailLog, nil
}

//getColor returns a ANSI escape code for color based on the colorID
func getColor(colorID int64) string {
colors := map[int64]string{
0: "\033[37m", // Light Gray
1: "\033[31m", // Red
2: "\033[33m", // Yellow
3: "\033[34m", // Blue
4: "\033[35m", // Magenta
5: "\033[36m", // Cyan
6: "\033[32m", // Green
}
return colors[colorID%int64(len(colors))]
}

func (l *LogLine) colorize(prefix string) string {
return getColor(l.ColorID) + prefix + l.Msg + ANSIEscapeResetCode
}

// String converts a log line to a string for output given whether a detail
// bool is specified.
func (l *LogLine) String(options *LogOptions) string {
Expand All @@ -177,10 +200,18 @@ func (l *LogLine) String(options *LogOptions) string {
out = fmt.Sprintf("%s ", cid)
}
}

if options.Timestamps {
out += fmt.Sprintf("%s ", l.Time.Format(LogTimeFormat))
}
return out + l.Msg

if options.Colors {
out = l.colorize(out)
} else {
out += l.Msg
}

return out
}

// Since returns a bool as to whether a log line occurred after a given time
Expand Down
2 changes: 1 addition & 1 deletion libpod/oci_conmon_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ func (r *ConmonOCIRuntime) HTTPAttach(ctr *Container, req *http.Request, w http.
}
errChan <- err
}()
if err := ctr.ReadLog(context.Background(), logOpts, logChan); err != nil {
if err := ctr.ReadLog(context.Background(), logOpts, logChan, 0); err != nil {
return err
}
go func() {
Expand Down
2 changes: 2 additions & 0 deletions pkg/domain/entities/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ type ContainerLogsOptions struct {
Tail int64
// Show timestamps in the logs.
Timestamps bool
// Show different colors in the logs.
Colors bool
// Write the stdout to this Writer.
StdoutWriter io.Writer
// Write the stderr to this Writer.
Expand Down
3 changes: 3 additions & 0 deletions pkg/domain/entities/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ type PodLogsOptions struct {
ContainerLogsOptions
// If specified will only fetch the logs of specified container
ContainerName string
// Show different colors in the logs.
Color bool
}

type ContainerCreateOptions struct {
Expand Down Expand Up @@ -482,6 +484,7 @@ func PodLogsOptionsToContainerLogsOptions(options PodLogsOptions) ContainerLogsO
Until: options.Until,
Tail: options.Tail,
Timestamps: options.Timestamps,
Colors: options.Colors,
StdoutWriter: options.StdoutWriter,
StderrWriter: options.StderrWriter,
}
Expand Down
1 change: 1 addition & 0 deletions pkg/domain/infra/abi/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin
Until: options.Until,
Tail: options.Tail,
Timestamps: options.Timestamps,
Colors: options.Colors,
UseName: options.Names,
WaitGroup: &wg,
}
Expand Down
23 changes: 23 additions & 0 deletions test/e2e/logs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,27 @@ var _ = Describe("Podman logs", func() {
Expect(output).To(ContainElement(ContainSubstring(containerName1)))
Expect(output).To(ContainElement(ContainSubstring(containerName2)))
})
It("podman pod logs with different colors", func() {
SkipIfRemote("Remote can only process one container at a time")
SkipIfInContainer("journalctl inside a container doesn't work correctly")
podName := "testPod"
containerName1 := "container1"
containerName2 := "container2"
testPod := podmanTest.Podman([]string{"pod", "create", fmt.Sprintf("--name=%s", podName)})
testPod.WaitWithDefaultTimeout()
Expect(testPod).To(Exit(0))
log1 := podmanTest.Podman([]string{"run", "--name", containerName1, "-d", "--pod", podName, BB, "/bin/sh", "-c", "echo log1"})
log1.WaitWithDefaultTimeout()
Expect(log1).To(Exit(0))
log2 := podmanTest.Podman([]string{"run", "--name", containerName2, "-d", "--pod", podName, BB, "/bin/sh", "-c", "echo log2"})
log2.WaitWithDefaultTimeout()
Expect(log2).To(Exit(0))
results := podmanTest.Podman([]string{"pod", "logs", "--color", podName})
results.WaitWithDefaultTimeout()
Expect(results).To(Exit(0))
output := results.OutputToStringArray()
Expect(output).To(HaveLen(2))
Expect(output[0]).To(MatchRegexp(`\x1b\[3[0-9a-z ]+\x1b\[0m`))
Expect(output[1]).To(MatchRegexp(`\x1b\[3[0-9a-z ]+\x1b\[0m`))
})
})