mirror of
https://github.com/containers/podman.git
synced 2025-05-17 15:18:43 +08:00
Add option for pod logs to display different colors per container.
Signed-off-by: Krzysztof Baran <krysbaran@gmail.com> Signed-off-by: gcalin <caling@protonmail.com>
This commit is contained in:
@ -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")
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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`.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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`))
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user