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) 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 package cmd
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
"github.com/anchore/imgbom/imgbom" "github.com/anchore/imgbom/imgbom"
"github.com/anchore/imgbom/internal/config" "github.com/anchore/imgbom/internal/config"
"github.com/anchore/imgbom/internal/format"
"github.com/anchore/imgbom/internal/log" "github.com/anchore/imgbom/internal/log"
"github.com/anchore/imgbom/internal/logger" "github.com/anchore/imgbom/internal/logger"
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/yaml.v2"
) )
var appConfig *config.Application var appConfig *config.Application
@ -25,10 +26,10 @@ func initAppConfig() {
func initLogging() { func initLogging() {
config := logger.LogConfig{ config := logger.LogConfig{
EnableConsole: appConfig.Log.FileLocation == "" && !appConfig.Quiet, EnableConsole: (appConfig.Log.FileLocation == "" || appConfig.CliOptions.Verbosity > 0) && !appConfig.Quiet,
EnableFile: appConfig.Log.FileLocation != "", EnableFile: appConfig.Log.FileLocation != "",
Level: appConfig.Log.LevelOpt, Level: appConfig.Log.LevelOpt,
FormatAsJSON: appConfig.Log.FormatAsJSON, Structured: appConfig.Log.Structured,
FileLocation: appConfig.Log.FileLocation, FileLocation: appConfig.Log.FileLocation,
} }
@ -36,10 +37,11 @@ func initLogging() {
} }
func logAppConfig() { func logAppConfig() {
appCfgStr, err := json.MarshalIndent(&appConfig, " ", " ") appCfgStr, err := yaml.Marshal(&appConfig)
if err != nil { if err != nil {
log.Debugf("Could not display application config: %+v", err) log.Debugf("Could not display application config: %+v", err)
} else { } 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/cobra v1.0.0
github.com/spf13/viper v1.7.0 github.com/spf13/viper v1.7.0
go.uber.org/zap v1.15.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/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/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/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/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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/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/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-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.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-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/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= 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(), "id": src.ID(),
"presenter": "json", "presenter": "json",
}).Errorf("could not get metadata from catalog") }).Errorf("could not get metadata from catalog")
} }
srcObj := source{ srcObj := source{

View File

@ -20,9 +20,9 @@ type CliOnlyOptions struct {
} }
type Application struct { type Application struct {
configPath string ConfigPath string
PresenterOpt presenter.Option PresenterOpt presenter.Option
Presenter string `mapstructure:"output"` Output string `mapstructure:"output"`
ScopeOpt scope.Option ScopeOpt scope.Option
Scope string `mapstructure:"scope"` Scope string `mapstructure:"scope"`
Quiet bool `mapstructure:"quiet"` Quiet bool `mapstructure:"quiet"`
@ -31,7 +31,7 @@ type Application struct {
} }
type Logging struct { type Logging struct {
FormatAsJSON bool `mapstructure:"structured"` Structured bool `mapstructure:"structured"`
LevelOpt zapcore.Level LevelOpt zapcore.Level
Level string `mapstructure:"level"` Level string `mapstructure:"level"`
FileLocation string `mapstructure:"file"` FileLocation string `mapstructure:"file"`
@ -59,7 +59,7 @@ func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application,
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse config: %w", err) return nil, fmt.Errorf("unable to parse config: %w", err)
} }
config.configPath = v.ConfigFileUsed() config.ConfigPath = v.ConfigFileUsed()
err = config.Build() err = config.Build()
if err != nil { if err != nil {
@ -71,9 +71,9 @@ func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application,
func (cfg *Application) Build() error { func (cfg *Application) Build() error {
// set the presenter // set the presenter
presenterOption := presenter.ParseOption(cfg.Presenter) presenterOption := presenter.ParseOption(cfg.Output)
if presenterOption == presenter.UnknownPresenter { 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 cfg.PresenterOpt = presenterOption
@ -100,10 +100,10 @@ func (cfg *Application) Build() error {
} }
} else { } else {
// set the log level implicitly // set the log level implicitly
switch cfg.CliOptions.Verbosity { switch v := cfg.CliOptions.Verbosity; {
case 1: case v == 1:
cfg.Log.LevelOpt = zapcore.InfoLevel cfg.Log.LevelOpt = zapcore.InfoLevel
case 2: case v >= 2:
cfg.Log.LevelOpt = zapcore.DebugLevel cfg.Log.LevelOpt = zapcore.DebugLevel
default: default:
cfg.Log.LevelOpt = zapcore.ErrorLevel cfg.Log.LevelOpt = zapcore.ErrorLevel
@ -133,14 +133,21 @@ func readConfig(v *viper.Viper, configPath string) error {
// start searching for valid configs in order... // 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.AddConfigPath("." + internal.ApplicationName)
v.SetConfigName("config") v.SetConfigName("config")
if err := v.ReadInConfig(); err == nil { if err := v.ReadInConfig(); err == nil {
return nil return nil
} }
// 2. look for ~/.<appname>.yaml // 3. look for ~/.<appname>.yaml
home, err := homedir.Dir() home, err := homedir.Dir()
if err == nil { if err == nil {
v.AddConfigPath(home) 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)) v.AddConfigPath(path.Join(xdg.ConfigHome, internal.ApplicationName))
for _, dir := range xdg.ConfigDirs { for _, dir := range xdg.ConfigDirs {
v.AddConfigPath(path.Join(dir, internal.ApplicationName)) 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" "os"
"github.com/anchore/imgbom/imgbom/logger" "github.com/anchore/imgbom/imgbom/logger"
"github.com/anchore/imgbom/internal/format"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "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 { type LogConfig struct {
EnableConsole bool EnableConsole bool
EnableFile bool EnableFile bool
FormatAsJSON bool Structured bool
Level zapcore.Level Level zapcore.Level
FileLocation string FileLocation string
} }
@ -20,6 +31,12 @@ type ZapLogger struct {
sugaredLogger *zap.SugaredLogger 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 { func NewZapLogger(config LogConfig) *ZapLogger {
cores := []zapcore.Core{} cores := []zapcore.Core{}
@ -31,8 +48,8 @@ func NewZapLogger(config LogConfig) *ZapLogger {
} }
if config.EnableFile { if config.EnableFile {
writer := zapcore.AddSync(getLogWriter(config.FileLocation)) writer := zapcore.AddSync(logFileWriter(config.FileLocation))
core := zapcore.NewCore(getFileEncoder(config), writer, config.Level) core := zapcore.NewCore(fileEncoder(config), writer, config.Level)
cores = append(cores, core) 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 { func getConsoleEncoder(config LogConfig) zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig() encoderConfig := zap.NewProductionEncoderConfig()
if config.FormatAsJSON { if config.Structured {
encoderConfig.EncodeName = zapcore.FullNameEncoder
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder
return zapcore.NewJSONEncoder(encoderConfig) return zapcore.NewJSONEncoder(encoderConfig)
} }
encoderConfig.EncodeTime = nil encoderConfig.EncodeTime = nil
encoderConfig.EncodeCaller = nil encoderConfig.EncodeCaller = nil
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder encoderConfig.EncodeLevel = consoleLevelEncoder
encoderConfig.EncodeName = nameEncoder
return zapcore.NewConsoleEncoder(encoderConfig) 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 := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
if config.FormatAsJSON { encoderConfig.EncodeName = zapcore.FullNameEncoder
encoderConfig.EncodeCaller = zapcore.FullCallerEncoder
if config.Structured {
return zapcore.NewJSONEncoder(encoderConfig) return zapcore.NewJSONEncoder(encoderConfig)
} }
return zapcore.NewConsoleEncoder(encoderConfig) return zapcore.NewConsoleEncoder(encoderConfig)
} }
func getLogWriter(location string) zapcore.WriteSyncer { func logFileWriter(location string) zapcore.WriteSyncer {
file, _ := os.Create(location) file, _ := os.Create(location)
return zapcore.AddSync(file) return zapcore.AddSync(file)
} }