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
80 changes: 72 additions & 8 deletions cmd/root/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package root
import (
"context"
"fmt"
"io"
"os"

"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/flags"
"github.com/databricks/cli/libs/log"
"github.com/fatih/color"
"golang.org/x/exp/slog"
)

Expand All @@ -16,6 +19,72 @@ const (
envLogFormat = "DATABRICKS_LOG_FORMAT"
)

type friendlyHandler struct {
slog.Handler
w io.Writer
}

var (
levelTrace = color.New(color.FgYellow).Sprint("TRACE")
levelDebug = color.New(color.FgYellow).Sprint("DEBUG")
levelInfo = color.New(color.FgGreen).Sprintf("%5s", "INFO")
levelWarn = color.New(color.FgMagenta).Sprintf("%5s", "WARN")
levelError = color.New(color.FgRed).Sprint("ERROR")
)

func (l *friendlyHandler) coloredLevel(rec slog.Record) string {
switch rec.Level {
case log.LevelTrace:
return levelTrace
case slog.LevelDebug:
return levelDebug
case slog.LevelInfo:
return levelInfo
case slog.LevelWarn:
return levelWarn
case log.LevelError:
return levelError
}
return ""
}

func (l *friendlyHandler) Handle(ctx context.Context, rec slog.Record) error {
t := fmt.Sprintf("%02d:%02d", rec.Time.Hour(), rec.Time.Minute())
attrs := ""
rec.Attrs(func(a slog.Attr) {
attrs += fmt.Sprintf(" %s%s%s",
color.CyanString(a.Key),
color.CyanString("="),
color.YellowString(a.Value.String()))
})
msg := fmt.Sprintf("%s %s %s%s\n",
color.MagentaString(t),
l.coloredLevel(rec),
color.HiWhiteString(rec.Message),
attrs)
_, err := l.w.Write([]byte(msg))
return err
}

func makeLogHandler(opts slog.HandlerOptions) (slog.Handler, error) {
switch logOutput {
case flags.OutputJSON:
return opts.NewJSONHandler(logFile.Writer()), nil
case flags.OutputText:
w := logFile.Writer()
if cmdio.IsTTY(w) {
return &friendlyHandler{
Handler: opts.NewTextHandler(w),
w: w,
}, nil
}
return opts.NewTextHandler(w), nil

default:
return nil, fmt.Errorf("invalid log output mode: %s", logOutput)
}
}

func initializeLogger(ctx context.Context) (context.Context, error) {
opts := slog.HandlerOptions{}
opts.Level = logLevel.Level()
Expand All @@ -31,14 +100,9 @@ func initializeLogger(ctx context.Context) (context.Context, error) {
return nil, err
}

var handler slog.Handler
switch logOutput {
case flags.OutputJSON:
handler = opts.NewJSONHandler(logFile.Writer())
case flags.OutputText:
handler = opts.NewTextHandler(logFile.Writer())
default:
return nil, fmt.Errorf("invalid log output: %s", logOutput)
handler, err := makeLogHandler(opts)
if err != nil {
return nil, err
}

slog.SetDefault(slog.New(handler))
Expand Down
10 changes: 10 additions & 0 deletions libs/cmdio/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ func IsInteractive(ctx context.Context) bool {
return c.interactive
}

// IsTTY detects if io.Writer is a terminal.
func IsTTY(w io.Writer) bool {
f, ok := w.(*os.File)
if !ok {
return false
}
fd := f.Fd()
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
}

// IsTTY detects if stdout is a terminal. It assumes that stderr is terminal as well
func (c *cmdIO) IsTTY() bool {
f, ok := c.out.(*os.File)
Expand Down