add named logger + color formatting

This commit is contained in:
Alex Goodman 2020-05-22 11:04:03 -04:00
parent 09c7ca8f8f
commit ae6feed8fc
No known key found for this signature in database
GPG key ID: 86E2870463D5E890
8 changed files with 103 additions and 27 deletions

View file

@ -42,5 +42,5 @@ func setCliOptions() {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
}
rootCmd.Flags().CountVarP(&cliOpts.Verbosity, "verbose", "v", "increase verbosity (-v, -vv, -vvv ...)")
rootCmd.Flags().CountVarP(&cliOpts.Verbosity, "verbose", "v", "increase verbosity (-v = info, -vv = debug)")
}

View file

@ -1,15 +1,16 @@
package cmd
import (
"encoding/json"
"fmt"
"os"
"github.com/anchore/imgbom/imgbom"
"github.com/anchore/imgbom/internal/config"
"github.com/anchore/imgbom/internal/format"
"github.com/anchore/imgbom/internal/log"
"github.com/anchore/imgbom/internal/logger"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
)
var appConfig *config.Application
@ -25,10 +26,10 @@ func initAppConfig() {
func initLogging() {
config := logger.LogConfig{
EnableConsole: appConfig.Log.FileLocation == "" && !appConfig.Quiet,
EnableConsole: (appConfig.Log.FileLocation == "" || appConfig.CliOptions.Verbosity > 0) && !appConfig.Quiet,
EnableFile: appConfig.Log.FileLocation != "",
Level: appConfig.Log.LevelOpt,
FormatAsJSON: appConfig.Log.FormatAsJSON,
Structured: appConfig.Log.Structured,
FileLocation: appConfig.Log.FileLocation,
}
@ -36,10 +37,11 @@ func initLogging() {
}
func logAppConfig() {
appCfgStr, err := json.MarshalIndent(&appConfig, " ", " ")
appCfgStr, err := yaml.Marshal(&appConfig)
if err != nil {
log.Debugf("Could not display application config: %+v", err)
} else {
log.Debugf("Application config:\n%+v", string(appCfgStr))
log.Debugf("Application config:\n%+v", format.Magenta.Format(string(appCfgStr)))
}
}

1
go.mod
View file

@ -14,4 +14,5 @@ require (
github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.7.0
go.uber.org/zap v1.15.0
gopkg.in/yaml.v2 v2.2.8
)

3
go.sum
View file

@ -131,6 +131,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -280,8 +281,10 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=

View file

@ -95,7 +95,6 @@ func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, cata
"id": src.ID(),
"presenter": "json",
}).Errorf("could not get metadata from catalog")
}
srcObj := source{

View file

@ -20,9 +20,9 @@ type CliOnlyOptions struct {
}
type Application struct {
configPath string
ConfigPath string
PresenterOpt presenter.Option
Presenter string `mapstructure:"output"`
Output string `mapstructure:"output"`
ScopeOpt scope.Option
Scope string `mapstructure:"scope"`
Quiet bool `mapstructure:"quiet"`
@ -31,7 +31,7 @@ type Application struct {
}
type Logging struct {
FormatAsJSON bool `mapstructure:"structured"`
Structured bool `mapstructure:"structured"`
LevelOpt zapcore.Level
Level string `mapstructure:"level"`
FileLocation string `mapstructure:"file"`
@ -59,7 +59,7 @@ func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application,
if err != nil {
return nil, fmt.Errorf("unable to parse config: %w", err)
}
config.configPath = v.ConfigFileUsed()
config.ConfigPath = v.ConfigFileUsed()
err = config.Build()
if err != nil {
@ -71,9 +71,9 @@ func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application,
func (cfg *Application) Build() error {
// set the presenter
presenterOption := presenter.ParseOption(cfg.Presenter)
presenterOption := presenter.ParseOption(cfg.Output)
if presenterOption == presenter.UnknownPresenter {
return fmt.Errorf("bad --output value '%s'", cfg.Presenter)
return fmt.Errorf("bad --output value '%s'", cfg.Output)
}
cfg.PresenterOpt = presenterOption
@ -100,10 +100,10 @@ func (cfg *Application) Build() error {
}
} else {
// set the log level implicitly
switch cfg.CliOptions.Verbosity {
case 1:
switch v := cfg.CliOptions.Verbosity; {
case v == 1:
cfg.Log.LevelOpt = zapcore.InfoLevel
case 2:
case v >= 2:
cfg.Log.LevelOpt = zapcore.DebugLevel
default:
cfg.Log.LevelOpt = zapcore.ErrorLevel
@ -133,14 +133,21 @@ func readConfig(v *viper.Viper, configPath string) error {
// start searching for valid configs in order...
// 1. look for .<appname>/config.yaml (in the current directory)
// 1. look for .<appname>.yaml (in the current directory)
v.AddConfigPath(".")
v.SetConfigName(internal.ApplicationName)
if err := v.ReadInConfig(); err == nil {
return nil
}
// 2. look for .<appname>/config.yaml (in the current directory)
v.AddConfigPath("." + internal.ApplicationName)
v.SetConfigName("config")
if err := v.ReadInConfig(); err == nil {
return nil
}
// 2. look for ~/.<appname>.yaml
// 3. look for ~/.<appname>.yaml
home, err := homedir.Dir()
if err == nil {
v.AddConfigPath(home)
@ -150,7 +157,7 @@ func readConfig(v *viper.Viper, configPath string) error {
}
}
// 3. look for <appname>/config.yaml in xdg locations (starting with xdg home config dir, then moving upwards)
// 4. look for <appname>/config.yaml in xdg locations (starting with xdg home config dir, then moving upwards)
v.AddConfigPath(path.Join(xdg.ConfigHome, internal.ApplicationName))
for _, dir := range xdg.ConfigDirs {
v.AddConfigPath(path.Join(dir, internal.ApplicationName))

21
internal/format/color.go Normal file
View file

@ -0,0 +1,21 @@
package format
import "fmt"
const (
DefaultColor Color = iota + 30
Red
Green
Yellow
Blue
Magenta
Cyan
White
)
type Color uint8
// TODO: not cross platform (windows...)
func (c Color) Format(s string) string {
return fmt.Sprintf("\x1b[%dm%s\x1b[0m", c, s)
}

View file

@ -4,14 +4,25 @@ import (
"os"
"github.com/anchore/imgbom/imgbom/logger"
"github.com/anchore/imgbom/internal/format"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var levelToColor = map[zapcore.Level]format.Color{
zapcore.DebugLevel: format.Magenta,
zapcore.InfoLevel: format.Blue,
zapcore.WarnLevel: format.Yellow,
zapcore.ErrorLevel: format.Red,
zapcore.DPanicLevel: format.Red,
zapcore.PanicLevel: format.Red,
zapcore.FatalLevel: format.Red,
}
type LogConfig struct {
EnableConsole bool
EnableFile bool
FormatAsJSON bool
Structured bool
Level zapcore.Level
FileLocation string
}
@ -20,6 +31,12 @@ type ZapLogger struct {
sugaredLogger *zap.SugaredLogger
}
// TODO: Consider a human readable text encoder for better field handeling:
// - https://github.com/uber-go/zap/issues/570
// - https://github.com/uber-go/zap/pull/123
// - TextEncoder w/ old interface: https://github.com/uber-go/zap/blob/6c2107996402d47d559199b78e1c44747fe732f9/text_encoder.go
// - New interface example: https://github.com/uber-go/zap/blob/c2633d6de2d6e1170ad8f150660e3cf5310067c8/zapcore/json_encoder.go
// - Register the encoder: https://github.com/uber-go/zap/blob/v1.15.0/encoder.go
func NewZapLogger(config LogConfig) *ZapLogger {
cores := []zapcore.Core{}
@ -31,8 +48,8 @@ func NewZapLogger(config LogConfig) *ZapLogger {
}
if config.EnableFile {
writer := zapcore.AddSync(getLogWriter(config.FileLocation))
core := zapcore.NewCore(getFileEncoder(config), writer, config.Level)
writer := zapcore.AddSync(logFileWriter(config.FileLocation))
core := zapcore.NewCore(fileEncoder(config), writer, config.Level)
cores = append(cores, core)
}
@ -50,27 +67,53 @@ func NewZapLogger(config LogConfig) *ZapLogger {
}
}
func (l *ZapLogger) GetNamedLogger(name string) *ZapLogger {
return &ZapLogger{
sugaredLogger: l.sugaredLogger.Named(name),
}
}
func getConsoleEncoder(config LogConfig) zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
if config.FormatAsJSON {
if config.Structured {
encoderConfig.EncodeName = zapcore.FullNameEncoder
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder
return zapcore.NewJSONEncoder(encoderConfig)
}
encoderConfig.EncodeTime = nil
encoderConfig.EncodeCaller = nil
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
encoderConfig.EncodeLevel = consoleLevelEncoder
encoderConfig.EncodeName = nameEncoder
return zapcore.NewConsoleEncoder(encoderConfig)
}
func getFileEncoder(config LogConfig) zapcore.Encoder {
func nameEncoder(loggerName string, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("[" + loggerName + "]")
}
func consoleLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
if level != zapcore.InfoLevel {
color, ok := levelToColor[level]
if !ok {
enc.AppendString("[" + level.CapitalString() + "]")
} else {
enc.AppendString("[" + color.Format(level.CapitalString()) + "]")
}
}
}
func fileEncoder(config LogConfig) zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
if config.FormatAsJSON {
encoderConfig.EncodeName = zapcore.FullNameEncoder
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder
if config.Structured {
return zapcore.NewJSONEncoder(encoderConfig)
}
return zapcore.NewConsoleEncoder(encoderConfig)
}
func getLogWriter(location string) zapcore.WriteSyncer {
func logFileWriter(location string) zapcore.WriteSyncer {
file, _ := os.Create(location)
return zapcore.AddSync(file)
}