Skip to content

Commit

Permalink
logger: TTY logs support (live update)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
  • Loading branch information
aluzzardi committed Oct 5, 2021
1 parent 91f0271 commit dd1bf18
Show file tree
Hide file tree
Showing 13 changed files with 479 additions and 77 deletions.
2 changes: 1 addition & 1 deletion cmd/dagger/cmd/root.go
Expand Up @@ -24,7 +24,7 @@ var rootCmd = &cobra.Command{
}

func init() {
rootCmd.PersistentFlags().String("log-format", "", "Log format (json, pretty). Defaults to json if the terminal is not a tty")
rootCmd.PersistentFlags().String("log-format", "auto", "Log format (auto, plain, tty, json)")
rootCmd.PersistentFlags().StringP("log-level", "l", "info", "Log level")

rootCmd.PersistentFlags().Bool("no-cache", false, "Disable caching")
Expand Down
25 changes: 23 additions & 2 deletions cmd/dagger/cmd/up.go
Expand Up @@ -31,7 +31,23 @@ var upCmd = &cobra.Command{
}
},
Run: func(cmd *cobra.Command, args []string) {
lg := logger.New()
var (
lg = logger.New()
tty *logger.TTYOutput
err error
)

if f := viper.GetString("log-format"); f == "tty" || f == "auto" && term.IsTerminal(int(os.Stdout.Fd())) {
tty, err = logger.NewTTYOutput(os.Stderr)
if err != nil {
lg.Fatal().Err(err).Msg("failed to initialize TTY logger")
}
tty.Start()
defer tty.Stop()

lg = lg.Output(tty)
}

ctx := lg.WithContext(cmd.Context())

project := common.CurrentProject(ctx)
Expand All @@ -45,7 +61,7 @@ var upCmd = &cobra.Command{

cl := common.NewClient(ctx)

err := cl.Do(ctx, st, func(ctx context.Context, env *environment.Environment, s solver.Solver) error {
err = cl.Do(ctx, st, func(ctx context.Context, env *environment.Environment, s solver.Solver) error {
// check that all inputs are set
if err := checkInputs(ctx, env); err != nil {
return err
Expand All @@ -60,6 +76,11 @@ var upCmd = &cobra.Command{
return err
}

// FIXME: `ListOutput` is printing to Stdout directly which messes
// up the TTY logger.
if tty != nil {
tty.Stop()
}
return output.ListOutputs(ctx, env, term.IsTerminal(int(os.Stdout.Fd())))
})

Expand Down
16 changes: 9 additions & 7 deletions cmd/dagger/logger/logger.go
Expand Up @@ -21,8 +21,8 @@ func New() zerolog.Logger {
Timestamp().
Logger()

if prettyLogs() {
logger = logger.Output(&Console{Out: os.Stderr})
if !jsonLogs() {
logger = logger.Output(&PlainOutput{Out: os.Stderr})
} else {
logger = logger.With().Timestamp().Caller().Logger()
}
Expand All @@ -35,14 +35,16 @@ func New() zerolog.Logger {
return logger.Level(lvl)
}

func prettyLogs() bool {
func jsonLogs() bool {
switch f := viper.GetString("log-format"); f {
case "json":
return false
case "pretty":
return true
case "":
return term.IsTerminal(int(os.Stdout.Fd()))
case "plain":
return false
case "tty":
return false
case "auto":
return !term.IsTerminal(int(os.Stdout.Fd()))
default:
fmt.Fprintf(os.Stderr, "invalid --log-format %q\n", f)
os.Exit(1)
Expand Down
56 changes: 25 additions & 31 deletions cmd/dagger/logger/console.go → cmd/dagger/logger/plain.go
Expand Up @@ -6,8 +6,8 @@ import (
"fmt"
"hash/adler32"
"io"
"sort"
"strings"
"sync"
"time"

"github.com/mitchellh/colorstring"
Expand All @@ -19,38 +19,29 @@ var colorize = colorstring.Colorize{
Reset: true,
}

type Console struct {
Out io.Writer
maxLength int
l sync.Mutex
type PlainOutput struct {
Out io.Writer
}

func (c *Console) Write(p []byte) (n int, err error) {
func (c *PlainOutput) Write(p []byte) (int, error) {
event := map[string]interface{}{}
d := json.NewDecoder(bytes.NewReader(p))
if err := d.Decode(&event); err != nil {
return n, fmt.Errorf("cannot decode event: %s", err)
return 0, fmt.Errorf("cannot decode event: %s", err)
}

source := c.parseSource(event)
source := parseSource(event)

c.l.Lock()
if len(source) > c.maxLength {
c.maxLength = len(source)
}
c.l.Unlock()

return fmt.Fprintln(c.Out,
colorize.Color(fmt.Sprintf("%s %s %s%s%s",
c.formatTimestamp(event),
c.formatLevel(event),
c.formatSource(source),
c.formatMessage(event),
c.formatFields(event),
)))
return fmt.Fprintln(c.Out, colorize.Color(fmt.Sprintf("%s %s %s%s%s",
formatTimestamp(event),
formatLevel(event),
formatSource(source),
formatMessage(event),
formatFields(event),
)))
}

func (c *Console) formatLevel(event map[string]interface{}) string {
func formatLevel(event map[string]interface{}) string {
level := zerolog.DebugLevel
if l, ok := event[zerolog.LevelFieldName].(string); ok {
level, _ = zerolog.ParseLevel(l)
Expand All @@ -76,7 +67,7 @@ func (c *Console) formatLevel(event map[string]interface{}) string {
}
}

func (c *Console) formatTimestamp(event map[string]interface{}) string {
func formatTimestamp(event map[string]interface{}) string {
ts, ok := event[zerolog.TimestampFieldName].(string)
if !ok {
return "???"
Expand All @@ -89,7 +80,7 @@ func (c *Console) formatTimestamp(event map[string]interface{}) string {
return fmt.Sprintf("[dark_gray]%s[reset]", t.Format(time.Kitchen))
}

func (c *Console) formatMessage(event map[string]interface{}) string {
func formatMessage(event map[string]interface{}) string {
message, ok := event[zerolog.MessageFieldName].(string)
if !ok {
return ""
Expand Down Expand Up @@ -125,22 +116,22 @@ func (c *Console) formatMessage(event map[string]interface{}) string {
}
}

func (c *Console) parseSource(event map[string]interface{}) string {
func parseSource(event map[string]interface{}) string {
source := "system"
if task, ok := event["component"].(string); ok && task != "" {
source = task
}
return source
}

func (c *Console) formatSource(source string) string {
func formatSource(source string) string {
return fmt.Sprintf("[%s]%s | [reset]",
hashColor(source),
source,
)
}

func (c *Console) formatFields(entry map[string]interface{}) string {
func formatFields(entry map[string]interface{}) string {
// these are the fields we don't want to expose, either because they're
// already part of the Log structure or because they're internal
fieldSkipList := map[string]struct{}{
Expand All @@ -149,7 +140,9 @@ func (c *Console) formatFields(entry map[string]interface{}) string {
zerolog.TimestampFieldName: {},
zerolog.ErrorFieldName: {},
zerolog.CallerFieldName: {},
"environment": {},
"component": {},
"state": {},
}

fields := []string{}
Expand Down Expand Up @@ -180,7 +173,10 @@ func (c *Console) formatFields(entry map[string]interface{}) string {
if len(fields) == 0 {
return ""
}
return fmt.Sprintf(" [dim]%s[reset]", strings.Join(fields, " "))
sort.SliceStable(fields, func(i, j int) bool {
return fields[i] < fields[j]
})
return fmt.Sprintf(" [bold]%s[reset]", strings.Join(fields, " "))
}

// hashColor returns a consistent color for a given string
Expand All @@ -195,8 +191,6 @@ func hashColor(text string) string {
"light_yellow",
"cyan",
"light_cyan",
"red",
"light_red",
}
h := adler32.Checksum([]byte(text))
return colors[int(h)%len(colors)]
Expand Down

0 comments on commit dd1bf18

Please sign in to comment.