summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/containers/logs.go3
-rw-r--r--cmd/podman/pods/logs.go2
-rw-r--r--docs/source/markdown/podman-logs.1.md4
-rw-r--r--docs/source/markdown/podman-pod-logs.1.md4
-rw-r--r--libpod/container_log.go14
-rw-r--r--libpod/container_log_linux.go3
-rw-r--r--libpod/container_log_unsupported.go2
-rw-r--r--libpod/logs/log.go33
-rw-r--r--libpod/oci_conmon_linux.go2
-rw-r--r--pkg/domain/entities/containers.go2
-rw-r--r--pkg/domain/entities/pods.go3
-rw-r--r--pkg/domain/infra/abi/containers.go1
-rw-r--r--test/e2e/logs_test.go23
13 files changed, 86 insertions, 10 deletions
diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go
index 8b08a6313..374bf6b1c 100644
--- a/cmd/podman/containers/logs.go
+++ b/cmd/podman/containers/logs.go
@@ -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`,
@@ -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")
}
diff --git a/cmd/podman/pods/logs.go b/cmd/podman/pods/logs.go
index e35b48bed..28e7b7a43 100644
--- a/cmd/podman/pods/logs.go
+++ b/cmd/podman/pods/logs.go
@@ -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")
}
diff --git a/docs/source/markdown/podman-logs.1.md b/docs/source/markdown/podman-logs.1.md
index e12042030..6ce6c3812 100644
--- a/docs/source/markdown/podman-logs.1.md
+++ b/docs/source/markdown/podman-logs.1.md
@@ -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.
diff --git a/docs/source/markdown/podman-pod-logs.1.md b/docs/source/markdown/podman-pod-logs.1.md
index 5ef667504..7fc1d4b90 100644
--- a/docs/source/markdown/podman-pod-logs.1.md
+++ b/docs/source/markdown/podman-pod-logs.1.md
@@ -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`.
diff --git a/libpod/container_log.go b/libpod/container_log.go
index dd5c36e26..7a9eb2dbf 100644
--- a/libpod/container_log.go
+++ b/libpod/container_log.go
@@ -23,8 +23,8 @@ 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
}
}
@@ -32,26 +32,26 @@ func (r *Runtime) Log(ctx context.Context, containers []*Container, options *log
}
// 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.
@@ -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
}
@@ -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
}
diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go
index 8ae8ff2c0..d96647e51 100644
--- a/libpod/container_log_linux.go
+++ b/libpod/container_log_linux.go
@@ -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" {
@@ -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
diff --git a/libpod/container_log_unsupported.go b/libpod/container_log_unsupported.go
index 4f50f9f4c..c84a578cc 100644
--- a/libpod/container_log_unsupported.go
+++ b/libpod/container_log_unsupported.go
@@ -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")
}
diff --git a/libpod/logs/log.go b/libpod/logs/log.go
index 9672f6ee1..0eb3bb922 100644
--- a/libpod/logs/log.go
+++ b/libpod/logs/log.go
@@ -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
@@ -37,6 +40,7 @@ type LogOptions struct {
Until time.Time
Tail int64
Timestamps bool
+ Colors bool
Multi bool
WaitGroup *sync.WaitGroup
UseName bool
@@ -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
@@ -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 {
@@ -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
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index 38bf85834..264236dc1 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -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() {
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 79795a221..072514d0f 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -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.
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index da93d3f8b..a483064ab 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -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 {
@@ -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,
}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index f45bdeba5..35689092a 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -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,
}
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index cb795438d..934a306ce 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -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`))
+ })
})