## 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
297 lines
7.2 KiB
Go
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...)
|
|
}
|