Skip to content

Commit

Permalink
feat(logging): support JSON and logfmt logging with --log-fmt flag
Browse files Browse the repository at this point in the history
Signed-off-by: Justen Stall <39888103+justenstall@users.noreply.github.com>
  • Loading branch information
justenstall committed May 29, 2024
1 parent 3ba56c3 commit 3c3718d
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 75 deletions.
130 changes: 113 additions & 17 deletions cmd/hops/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package main

import (
"errors"
"io"
"log/slog"
"os"
"time"

"github.com/charmbracelet/log"
"github.com/muesli/termenv"
Expand Down Expand Up @@ -33,8 +34,6 @@ func main() {
root := cli.NewCLI(info.Version) // Create the root command
root.SilenceUsage = true // Silence usage when root is called

logopts := logutil.WithPersistentVerbosityFlags(root) // Add "quiet", "verbose", and "debug" flags

// Layout of embedded documentation to surface in the help command
// and generate in the gendocs command
embeddedDocs := docs.Embedded(root)
Expand All @@ -49,33 +48,55 @@ func main() {
docsCmd,
)

// Logger flags
logopts := logutil.WithPersistentVerbosityFlags(root) // Add "quiet", "verbose", and "debug" flags
var logfmt string
root.PersistentFlags().StringVar(&logfmt, "log-fmt", "text", "Set format for log messages. Options: text, json")
// var logfile string
// root.PersistentFlags().StringVar(&logfile, "log-file", "", "Send JSON logs to a file")

// Restores the original ANSI processing state on Windows
var restoreWindowsANSI func() error

// The pre run function logs build info and sets the default output writer
root.PersistentPreRun = func(cmd *cobra.Command, _ []string) {
level := logopts.LogLevel(slog.LevelInfo) // parse log level flags
cmd.Println("log level: ", level)
logger := log.NewWithOptions(cmd.OutOrStdout(), log.Options{
TimeFormat: time.Kitchen, // set human-readable time
Level: log.Level(level), // parse verbose/debug flags
})
logger.SetStyles(o.LogStyles()) // set homebrew-inspired styles
log.SetDefault(logger) // set as default charmbracelet/log.Logger
slog.SetDefault(slog.New(log.Default())) // set as the default slog.Logger
root.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()

// Create [log/slog.Handler] using flag configuration
level := logopts.LogLevel(slog.LevelInfo) // evaluate log level flags
handler, err := newTermHandler(
cmd.ErrOrStderr(),
level, // log level flags
logfmt, // log format flag
// logfile, // log file flag
)
if err != nil {
return err
}

// Set default [log/slog.Logger]
slog.SetDefault(slog.New(handler))

// Set termenv default output
termenv.SetDefaultOutput(termenv.NewOutput(cmd.OutOrStdout()))
// Enable ANSI processing on Windows color output
var err error
restoreWindowsANSI, err = termenv.EnableVirtualTerminalProcessing(termenv.DefaultOutput())
if err != nil {
slog.Error("error enabling ANSI processing", slog.String("error", err.Error()))
}

slog.Debug("Starting logger", slog.String("format", "text"), slog.String("level", level.String()))
slog.Debug("Software", slog.String("version", info.Version)) // Log version info
slog.Debug("Software details", slog.Any("info", info)) // Log build info
// slog.Log(ctx, logutil.LevelTrace,
slog.Log(ctx, slog.LevelDebug,
"Starting logger", slog.String("format", "text"), slog.String("verbosity", level.String()))
// slog.Log(ctx, logutil.LevelVerbose,
slog.Log(ctx, slog.LevelDebug,
"Software", slog.String("version", info.Version)) // Log version info
slog.Debug("Software details", slog.Attr{ // Log build info
Key: "info",
Value: slog.GroupValue(logutil.VersionAttrs(info)...),
})

return nil
}

// The post run function restores the terminal
Expand All @@ -90,3 +111,78 @@ func main() {
os.Exit(1)
}
}

func newTermHandler(w io.Writer, level slog.Level, format string) (slog.Handler, error) {
opts := log.Options{
Level: log.Level(level), // parse verbose/debug flags
}

switch format {
case "text", "":
opts.Formatter = log.TextFormatter
case "logfmt":
opts.Formatter = log.LogfmtFormatter
opts.ReportTimestamp = true
case "json":
opts.Formatter = log.JSONFormatter
opts.ReportTimestamp = true
default:
return nil, errors.New("starting logger: unsupported log format \"" + format + "\"")
}

logger := log.NewWithOptions(w, opts)
logger.SetStyles(o.LogStyles()) // set homebrew-inspired styles
log.SetDefault(logger) // set as default charmbracelet/log.Logger

return logger, nil
}

/*
func logHandler(level slog.Level, format, file string) (slog.Handler, error) {
var termHandler slog.Handler
switch format {
// Text logger using charmbracelet/log
case "text", "":
logger := log.NewWithOptions(os.Stdout, log.Options{
// TimeFormat: time.Kitchen, // set human-readable time
Level: log.Level(level), // parse verbose/debug flags
})
logger.SetStyles(o.LogStyles()) // set homebrew-inspired styles
log.SetDefault(logger) // set as default charmbracelet/log.Logger
termHandler = logger // use as termHandler
// JSON logger using zerolog
case "json":
zlogger := zerolog.New(os.Stderr)
termHandler = slogzerolog.Option{
Level: level,
Logger: &zlogger,
}.NewZerologHandler()
default:
return nil, errors.New("starting logger: unsupported log format \"" + format + "\"")
}
// Return terminal handler if no file is specified
if file == "" {
return termHandler, nil
}
// Create log file
f, err := os.Create(file)
if err != nil {
return nil, fmt.Errorf("creating log file: %w", err)
}
// Create file logger
fileLogger := zerolog.New(f)
// Create log/slog.Handler
fileHandler := slogzerolog.Option{
Level: logutil.LevelTrace,
Logger: &fileLogger,
}.NewZerologHandler()
// Create fanout logger to duplicate messages to file and terminal handler
return slogmulti.Fanout(termHandler, fileHandler), nil
}
*/
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,12 @@ require (
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
Expand Down
11 changes: 0 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMt
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
Expand All @@ -29,7 +28,6 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k=
github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
Expand All @@ -48,10 +46,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
Expand All @@ -78,9 +72,6 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
Expand All @@ -105,9 +96,7 @@ golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
Expand Down
4 changes: 2 additions & 2 deletions internal/actions/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,9 @@ func pushMetadata(ctx context.Context, dst oras.Target, manifestDesc ocispec.Des
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("pushing platform metadata: %w", err)
}
l.Debug("Pushed metadata for platform", logutil.OCIPlatformValue(manifestDesc.Platform), logutil.DescriptorGroup(configDesc))
configDesc.Platform = manifestDesc.Platform // set platform field on config descriptor

configDesc.Platform = manifestDesc.Platform // preserve platform
l.Debug("Pushed metadata for platform", logutil.DescriptorGroup(configDesc))

manifestOptions = platformMetadatManifestOptions(f.FullName, f.Version(), plat, manifestDesc, configDesc)
} else {
Expand Down
13 changes: 9 additions & 4 deletions internal/o/styles.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var (
green = termenv.ANSIGreen
blue = termenv.ANSIBlue
magenta = termenv.ANSIMagenta
cyan = termenv.ANSICyan

styleBold = output.String().Bold()
styleUnderline = output.String().Underline()
Expand Down Expand Up @@ -94,20 +95,24 @@ func StyleYellow(s string) string {
// Returns log styles for charmbracelet/log.
func LogStyles() *log.Styles {
styles := log.DefaultStyles()
arrow := lipgloss.NewStyle().SetString(Arrow)

styles.Levels[log.ErrorLevel] = lipgloss.NewStyle().
SetString("Error:").
Foreground(lipgloss.ANSIColor(red))
styles.Levels[log.WarnLevel] = lipgloss.NewStyle().
SetString("Warning:").
Foreground(lipgloss.ANSIColor(yellow))
styles.Levels[log.InfoLevel] = lipgloss.NewStyle().SetString(Arrow).Foreground(lipgloss.ANSIColor(blue))
styles.Levels[log.DebugLevel] = lipgloss.NewStyle().
SetString(Arrow).
styles.Levels[log.InfoLevel] = arrow.
Foreground(lipgloss.ANSIColor(blue))
styles.Levels[log.DebugLevel] = arrow.
Foreground(lipgloss.ANSIColor(magenta))
styles.Levels[log.Level(logutil.LevelTrace)] = arrow.
Foreground(lipgloss.ANSIColor(cyan))

// Add a custom style for key err/error
styles.Keys[logutil.ErrKey] = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(red))
styles.Keys[logutil.ErrKey] = lipgloss.NewStyle().
Foreground(lipgloss.ANSIColor(red))

return styles
}
Loading

0 comments on commit 3c3718d

Please sign in to comment.