diff options
-rw-r--r-- | cmd/podman/containers/logs.go | 3 | ||||
-rw-r--r-- | cmd/podman/pods/logs.go | 2 | ||||
-rw-r--r-- | docs/source/markdown/podman-logs.1.md | 4 | ||||
-rw-r--r-- | docs/source/markdown/podman-pod-logs.1.md | 4 | ||||
-rw-r--r-- | libpod/container_log.go | 14 | ||||
-rw-r--r-- | libpod/container_log_linux.go | 3 | ||||
-rw-r--r-- | libpod/container_log_unsupported.go | 2 | ||||
-rw-r--r-- | libpod/logs/log.go | 33 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 2 | ||||
-rw-r--r-- | pkg/domain/entities/containers.go | 2 | ||||
-rw-r--r-- | pkg/domain/entities/pods.go | 3 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 1 | ||||
-rw-r--r-- | test/e2e/logs_test.go | 23 |
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`)) + }) }) |