Skip to content
Draft
Show file tree
Hide file tree
Changes from 12 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
1 change: 1 addition & 0 deletions changelogs/unreleased/9317-itrooz
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add color to velero logs
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/bombsimon/logrusr/v3 v3.0.0
github.com/evanphx/json-patch/v5 v5.9.11
github.com/fatih/color v1.18.0
github.com/go-logfmt/logfmt v0.4.0
github.com/gobwas/glob v0.2.3
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
Expand Down Expand Up @@ -135,6 +136,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.4 // indirect
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
Expand Down Expand Up @@ -499,6 +500,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kopia/htmluibuild v0.0.1-0.20250607181534-77e0f3f9f557 h1:je1C/xnmKxnaJsIgj45me5qA51TgtK9uMwTxgDw+9H0=
github.com/kopia/htmluibuild v0.0.1-0.20250607181534-77e0f3f9f557/go.mod h1:h53A5JM3t2qiwxqxusBe+PFgGcgZdS+DWCQvG5PTlto=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
Expand Down
6 changes: 5 additions & 1 deletion pkg/cmd/cli/backup/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
"github.com/vmware-tanzu/velero/pkg/cmd/util/output"
)

type LogsOptions struct {
Expand Down Expand Up @@ -86,7 +87,10 @@ func (l *LogsOptions) Run(c *cobra.Command, f client.Factory) error {
bslCACert = ""
}

err = downloadrequest.StreamWithBSLCACert(context.Background(), l.Client, f.Namespace(), l.BackupName, velerov1api.DownloadTargetKindBackupLog, os.Stdout, l.Timeout, l.InsecureSkipTLSVerify, l.CaCertFile, bslCACert)
w, wg := output.PrintLogsWithColor()
err = downloadrequest.StreamWithBSLCACert(context.Background(), l.Client, f.Namespace(), l.BackupName, velerov1api.DownloadTargetKindBackupLog, w, l.Timeout, l.InsecureSkipTLSVerify, l.CaCertFile, bslCACert)
w.Close() // signal we're done writing
wg.Wait() // wait for all logs to be processed and printed
return err
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/cmd/cli/restore/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
"github.com/vmware-tanzu/velero/pkg/cmd/util/output"
)

func NewLogsCommand(f client.Factory) *cobra.Command {
Expand Down Expand Up @@ -77,7 +78,10 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
bslCACert = ""
}

err = downloadrequest.StreamWithBSLCACert(context.Background(), kbClient, f.Namespace(), restoreName, velerov1api.DownloadTargetKindRestoreLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile, bslCACert)
w, wg := output.PrintLogsWithColor()
err = downloadrequest.StreamWithBSLCACert(context.Background(), kbClient, f.Namespace(), restoreName, velerov1api.DownloadTargetKindRestoreLog, w, timeout, insecureSkipTLSVerify, caCertFile, bslCACert)
w.Close() // signal we're done writing
wg.Wait() // wait for all logs to be processed and printed
cmd.CheckError(err)
},
}
Expand Down
106 changes: 106 additions & 0 deletions pkg/cmd/util/output/logs_color.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package output

import (
"fmt"
"io"
"os"
"strings"
"sync"
"unicode/utf8"

"github.com/fatih/color"
"github.com/go-logfmt/logfmt"
)

func getLevelColor(level string) *color.Color {
switch level {
case "info":
return color.New(color.FgGreen)
case "warning":
return color.New(color.FgYellow)
case "error":
return color.New(color.FgRed)
case "debug":
return color.New(color.FgBlue)
default:
return color.New()
}
}

// https://github.com/go-logfmt/logfmt/blob/e5396c6ee35145aead27da56e7921a7656f69624/encode.go#L235
func needsQuotedValueRune(r rune) bool {
return r <= ' ' || r == '=' || r == '"' || r == 0x7f || r == utf8.RuneError
}

// Process logs (by adding color) before printing them
func processAndPrintLogs(r io.Reader, w io.Writer) error {
d := logfmt.NewDecoder(r)
for d.ScanRecord() { // get record (line)
// Scan fields and get color
var fields [][2][]byte
var lineColor *color.Color
for d.ScanKeyval() {
fields = append(fields, [2][]byte{d.Key(), d.Value()})
if string(d.Key()) == "level" {
lineColor = getLevelColor(string(d.Value()))
}
}

// Re-encode with color. We do not use logfmt Encoder because it does not support color
for _, field := range fields {
key := string(field[0])
value := string(field[1])

// Quote if needed
if strings.IndexFunc(value, needsQuotedValueRune) != -1 {
value = fmt.Sprintf("%q", value)
}

// Add color
if lineColor != nil { // handle case where no color (log level) was found
if key == "level" {
colorCopy := *lineColor
value = colorCopy.Add(color.Bold).Sprintf("%s", value)
}
key = lineColor.Sprintf("%s", field[0])
}
fmt.Fprintf(w, "%s=%s ", key, value)
}
fmt.Fprintln(w)
}
if err := d.Err(); err != nil {
return fmt.Errorf("error processing logs: %v", err)
}
return nil
}

type nopCloser struct {
io.Writer
}

func (nopCloser) Close() error { return nil }

// Print logfmt-formatted logs to stdout with color based on log level
// if color.NoColor is set, logs will be directly piped to w without processing
// Returns the writer to write logs to, and a waitgroup to wait for processing to finish
// Writer must be closed once all logs have been written
func PrintLogsWithColor() (io.WriteCloser, *sync.WaitGroup) {
// If NoColor, do not parse logs and directly fall back to stdout
var wg sync.WaitGroup
if color.NoColor {
return nopCloser{os.Stdout}, &wg
} else {
wg.Add(1)
pr, pw := io.Pipe()

// Create coroutine to process logs
go func(pr *io.PipeReader) {
defer wg.Done()
err := processAndPrintLogs(pr, os.Stdout)
if err != nil {
fmt.Fprintf(os.Stderr, "error processing logs: %v\n", err)
}
}(pr)
return pw, &wg
}
}
8 changes: 4 additions & 4 deletions pkg/cmd/velero/velero.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func NewCommand(name string) *cobra.Command {
// Declare cmdFeatures and cmdColorzied here so we can access them in the PreRun hooks
// without doing a chain of calls into the command's FlagSet
var cmdFeatures veleroflag.StringArray
var cmdColorzied veleroflag.OptionalBool
var cmdColorized veleroflag.OptionalBool

c := &cobra.Command{
Use: name,
Expand All @@ -86,8 +86,8 @@ operations can also be performed as 'velero backup get' and 'velero schedule cre
features.Enable(cmdFeatures...)

switch {
case cmdColorzied.Value != nil:
color.NoColor = !*cmdColorzied.Value
case cmdColorized.Value != nil:
color.NoColor = !*cmdColorized.Value
default:
color.NoColor = !config.Colorized()
}
Expand All @@ -101,7 +101,7 @@ operations can also be performed as 'velero backup get' and 'velero schedule cre
c.PersistentFlags().Var(&cmdFeatures, "features", "Comma-separated list of features to enable for this Velero process. Combines with values from $HOME/.config/velero/config.json if present")

// Color will be enabled or disabled for all subcommands
c.PersistentFlags().Var(&cmdColorzied, "colorized", "Show colored output in TTY. Overrides 'colorized' value from $HOME/.config/velero/config.json if present. Enabled by default")
c.PersistentFlags().Var(&cmdColorized, "colorized", "Show colored output in TTY. Overrides 'colorized' value from $HOME/.config/velero/config.json if present. Enabled by default")

c.AddCommand(
backup.NewCommand(f),
Expand Down