corso/src/pkg/logger/logger.go
ashmrtn 09cc2769d9
Fix most remaining wsl lint errors (#656)
* Fix wsl lint errors in pkg package

* Fix wsl lint errors in most of internal package

Leave some sub-packages out that have higher churn at the moment.
2022-08-26 17:58:58 +00:00

160 lines
3.5 KiB
Go

package logger
import (
"context"
"os"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/alcionai/corso/cli/print"
)
var (
logCore *zapcore.Core
loggerton *zap.SugaredLogger
// logging level flag
// TODO: infer default based on environment.
llFlag = "info"
)
type logLevel int
const (
Development logLevel = iota
Info
Warn
Production
)
// adds the persistent flag --log-level to the provided command.
// defaults to "info".
// This is a hack for help displays. Due to seeding the context, we
// need to parse the log level before we execute the command.
func AddLogLevelFlag(parent *cobra.Command) {
fs := parent.PersistentFlags()
fs.StringVar(&llFlag, "log-level", "info", "set the log level to debug|info|warn|error")
}
func singleton(level logLevel) *zap.SugaredLogger {
if loggerton != nil {
return loggerton
}
// set up a logger core to use as a fallback
levelFilter := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
switch level {
case Info:
return lvl >= zapcore.InfoLevel
case Warn:
return lvl >= zapcore.WarnLevel
case Production:
return lvl >= zapcore.ErrorLevel
default:
return true
}
})
out := zapcore.Lock(os.Stderr)
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
core := zapcore.NewTee(
zapcore.NewCore(consoleEncoder, out, levelFilter),
)
logCore = &core
// then try to set up a logger directly
var (
lgr *zap.Logger
err error
)
if level != Production {
cfg := zap.NewDevelopmentConfig()
switch level {
case Info:
cfg.Level = zap.NewAtomicLevelAt(zapcore.InfoLevel)
case Warn:
cfg.Level = zap.NewAtomicLevelAt(zapcore.WarnLevel)
}
lgr, err = cfg.Build()
} else {
lgr, err = zap.NewProduction()
}
// fall back to the core config if the default creation fails
if err != nil {
lgr = zap.New(*logCore)
}
loggerton = lgr.Sugar()
return loggerton
}
type loggingKey string
const ctxKey loggingKey = "corsoLogger"
// Seed embeds a logger into the context for later retrieval.
// It also parses the command line for flag values prior to executing
// cobra. This early parsing is necessary since logging depends on
// a seeded context prior to cobra evaluating flags.
func Seed(ctx context.Context) (ctxOut context.Context, zsl *zap.SugaredLogger) {
level := Info
// this func handles composing the return values whether or not an error occurs
defer func() {
zsl = singleton(level)
ctxOut = context.WithValue(ctx, ctxKey, zsl)
}()
fs := pflag.NewFlagSet("seed-logger", pflag.ContinueOnError)
fs.ParseErrorsWhitelist.UnknownFlags = true
fs.String("log-level", "info", "set the log level to debug|info|warn|error")
// parse the os args list to find the log level flag
if err := fs.Parse(os.Args[1:]); err != nil {
print.Err(ctx, err.Error())
return
}
// retrieve the user's preferred log level
// automatically defaults to "info"
levelString, err := fs.GetString("log-level")
if err != nil {
print.Err(ctx, err.Error())
return
}
level = levelOf(levelString)
return // return values handled in defer
}
// Ctx retrieves the logger embedded in the context.
func Ctx(ctx context.Context) *zap.SugaredLogger {
l := ctx.Value(ctxKey)
if l == nil {
return singleton(levelOf(llFlag))
}
return l.(*zap.SugaredLogger)
}
// transforms the llevel flag value to a logLevel enum
func levelOf(lvl string) logLevel {
switch lvl {
case "debug":
return Development
case "warn":
return Warn
case "error":
return Production
}
return Info
}