corso/src/pkg/logger/logger.go
Keepers 7e2fbbea4f
adds sharepoint restore cli commands (#1637)
## Description

Adds restore commands to the cli for sharepoint.
The restore process is only partially functional at this time.
Library files that pass auth are able to be restored as expected.
However, auth issues (not directly related to these changes) prevent
restoration of all library items at this time.

## Type of change

- [x] 🌻 Feature

## Issue(s)

* #1615

## Test Plan

- [x] 💪 Manual
- [x] 💚 E2E
2022-12-06 17:00:37 +00:00

297 lines
7.2 KiB
Go

package logger
import (
"context"
"os"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
logCore *zapcore.Core
loggerton *zap.SugaredLogger
// logging level flag
// TODO: infer default based on environment.
llFlag = "info"
readableOutput bool
)
type logLevel int
const (
Development logLevel = iota
Info
Warn
Production
Disabled
)
const (
logLevelFN = "log-level"
readableLogsFN = "readable-logs"
)
// 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 also
// need to parse the log level before we execute the command.
func AddLogLevelFlag(cmd *cobra.Command) {
fs := cmd.PersistentFlags()
fs.StringVar(&llFlag, logLevelFN, "info", "set the log level to debug|info|warn|error")
fs.Bool(
readableLogsFN, false,
"minimizes log output for console readability: removes the file and date, colors the level")
//nolint:errcheck
fs.MarkHidden(readableLogsFN)
}
// Due to races between the lazy evaluation of flags in cobra and the need to init logging
// behavior in a ctx, log-level gets pre-processed manually here using pflags. The canonical
// AddLogLevelFlag() ensures the flag is displayed as part of the help/usage output.
func PreloadLogLevel() string {
fs := pflag.NewFlagSet("seed-logger", pflag.ContinueOnError)
fs.ParseErrorsWhitelist.UnknownFlags = true
fs.String(logLevelFN, "info", "set the log level to debug|info|warn|error")
fs.BoolVar(&readableOutput, readableLogsFN, false, "minimizes log output: removes the file and date, colors the level")
// prevents overriding the corso/cobra help processor
fs.BoolP("help", "h", false, "")
// parse the os args list to find the log level flag
if err := fs.Parse(os.Args[1:]); err != nil {
return "info"
}
// retrieve the user's preferred log level
// automatically defaults to "info"
levelString, err := fs.GetString(logLevelFN)
if err != nil {
return "info"
}
return levelString
}
func genLogger(level logLevel) (*zapcore.Core, *zap.SugaredLogger) {
// when testing, ensure debug logging matches the test.v setting
for _, arg := range os.Args {
if arg == `--test.v=true` {
level = Development
}
}
// 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
case Disabled:
return false
default:
return true
}
})
out := zapcore.Lock(os.Stderr)
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
core := zapcore.NewTee(
zapcore.NewCore(consoleEncoder, out, levelFilter),
)
// 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)
case Disabled:
cfg.Level = zap.NewAtomicLevelAt(zapcore.FatalLevel)
}
opts := []zap.Option{}
if readableOutput {
opts = append(opts, zap.WithCaller(false), zap.AddStacktrace(zapcore.DPanicLevel))
cfg.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("15:04:05.00")
cfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
}
lgr, err = cfg.Build(opts...)
} else {
lgr, err = zap.NewProduction()
}
// fall back to the core config if the default creation fails
if err != nil {
lgr = zap.New(*logCore)
}
return &core, lgr.Sugar()
}
func singleton(level logLevel) *zap.SugaredLogger {
if loggerton != nil {
return loggerton
}
if logCore != nil {
lgr := zap.New(*logCore)
loggerton = lgr.Sugar()
return loggerton
}
logCore, loggerton = genLogger(level)
return loggerton
}
// ------------------------------------------------------------------------------------------------
// context management
// ------------------------------------------------------------------------------------------------
type loggingKey string
const ctxKey loggingKey = "corsoLogger"
// Seed generates a logger within 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, lvl string) (context.Context, *zap.SugaredLogger) {
if len(lvl) == 0 {
lvl = "info"
}
zsl := singleton(levelOf(lvl))
return Set(ctx, zsl), zsl
}
// SeedLevel generates a logger within the context with the given log-level.
func SeedLevel(ctx context.Context, level logLevel) (context.Context, *zap.SugaredLogger) {
l := ctx.Value(ctxKey)
if l == nil {
zsl := singleton(level)
return Set(ctx, zsl), zsl
}
return ctx, l.(*zap.SugaredLogger)
}
// Set allows users to embed their own zap.SugaredLogger within the context.
func Set(ctx context.Context, logger *zap.SugaredLogger) context.Context {
if logger == nil {
return ctx
}
return context.WithValue(ctx, ctxKey, logger)
}
// 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
case "disabled":
return Disabled
}
return Info
}
// Flush writes out all buffered logs.
func Flush(ctx context.Context) {
_ = Ctx(ctx).Sync()
}
// ------------------------------------------------------------------------------------------------
// log wrapper for downstream api compliance
// ------------------------------------------------------------------------------------------------
type wrapper struct {
zap.SugaredLogger
forceDebugLogLevel bool
}
func (w *wrapper) process(opts ...option) {
for _, opt := range opts {
opt(w)
}
}
type option func(*wrapper)
// ForceDebugLogLevel reduces all logs emitted in the wrapper to
// debug level, independent of their original log level. Useful
// for silencing noisy dependency packages without losing the info
// altogether.
func ForceDebugLogLevel() option {
return func(w *wrapper) {
w.forceDebugLogLevel = true
}
}
// Wrap returns the logger in the package with an extended api used for
// dependency package interface compliance.
func WrapCtx(ctx context.Context, opts ...option) *wrapper {
return Wrap(Ctx(ctx), opts...)
}
// Wrap returns the sugaredLogger with an extended api used for
// dependency package interface compliance.
func Wrap(zsl *zap.SugaredLogger, opts ...option) *wrapper {
w := &wrapper{SugaredLogger: *zsl}
w.process(opts...)
return w
}
func (w *wrapper) Logf(tmpl string, args ...any) {
if w.forceDebugLogLevel {
w.SugaredLogger.Debugf(tmpl, args...)
return
}
w.SugaredLogger.Infof(tmpl, args...)
}
func (w *wrapper) Errorf(tmpl string, args ...any) {
if w.forceDebugLogLevel {
w.SugaredLogger.Debugf(tmpl, args...)
return
}
w.SugaredLogger.Errorf(tmpl, args...)
}