introduce log-level control (#589)
Introduces the log-level flag, defaulting to info. Also does a minor refactor of how Print is called for backup results, which moves the backup/details imports out of the cli/print, and instead has thoses packages call a Print func.
This commit is contained in:
parent
c13ef798eb
commit
3ac05acf64
@ -9,6 +9,7 @@ import (
|
||||
"github.com/alcionai/corso/cli/options"
|
||||
. "github.com/alcionai/corso/cli/print"
|
||||
"github.com/alcionai/corso/cli/utils"
|
||||
"github.com/alcionai/corso/pkg/backup"
|
||||
"github.com/alcionai/corso/pkg/logger"
|
||||
"github.com/alcionai/corso/pkg/repository"
|
||||
"github.com/alcionai/corso/pkg/selectors"
|
||||
@ -208,7 +209,7 @@ func createExchangeCmd(cmd *cobra.Command, args []string) error {
|
||||
return Only(ctx, errors.Wrap(err, "Unable to retrieve backup results from storage"))
|
||||
}
|
||||
|
||||
OutputBackup(ctx, *bu)
|
||||
bu.Print(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -290,12 +291,12 @@ func listExchangeCmd(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
defer utils.CloseRepo(ctx, r)
|
||||
|
||||
rps, err := r.Backups(ctx)
|
||||
bs, err := r.Backups(ctx)
|
||||
if err != nil {
|
||||
return Only(ctx, errors.Wrap(err, "Failed to list backups in the repository"))
|
||||
}
|
||||
|
||||
OutputBackups(ctx, rps)
|
||||
backup.PrintAll(ctx, bs)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -384,7 +385,7 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
|
||||
return Only(ctx, errors.New("nothing to display: no items in the backup match the provided selectors"))
|
||||
}
|
||||
|
||||
OutputEntries(ctx, ds.Entries)
|
||||
ds.PrintEntries(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -58,6 +58,7 @@ func BuildCommandTree(cmd *cobra.Command) {
|
||||
cmd.PersistentPostRunE = config.InitFunc()
|
||||
config.AddConfigFileFlag(cmd)
|
||||
print.AddOutputFlag(cmd)
|
||||
logger.AddLogLevelFlag(cmd)
|
||||
|
||||
cmd.CompletionOptions.DisableDefaultCmd = true
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
|
||||
. "github.com/alcionai/corso/cli/print"
|
||||
"github.com/alcionai/corso/pkg/account"
|
||||
"github.com/alcionai/corso/pkg/logger"
|
||||
"github.com/alcionai/corso/pkg/storage"
|
||||
)
|
||||
|
||||
@ -133,7 +134,7 @@ func GetViper(ctx context.Context) *viper.Viper {
|
||||
// set up properly.
|
||||
func Read(ctx context.Context) error {
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
Info(ctx, "Using config file:", viper.ConfigFileUsed())
|
||||
logger.Ctx(ctx).Debugw("found config file", "configFile", viper.ConfigFileUsed())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -9,9 +9,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tidwall/pretty"
|
||||
"github.com/tomlazar/table"
|
||||
|
||||
"github.com/alcionai/corso/pkg/backup"
|
||||
"github.com/alcionai/corso/pkg/backup/details"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -119,6 +116,13 @@ type Printable interface {
|
||||
Values() []string
|
||||
}
|
||||
|
||||
// Item prints the printable, according to the caller's requested format.
|
||||
func Item(ctx context.Context, p Printable) {
|
||||
print(getRootCmd(ctx).OutOrStdout(), p)
|
||||
}
|
||||
|
||||
// print prints the printable items,
|
||||
// according to the caller's requested format.
|
||||
//revive:disable:redefines-builtin-id
|
||||
func print(w io.Writer, p Printable) {
|
||||
if outputAsJSON || outputAsJSONDebug {
|
||||
@ -128,6 +132,12 @@ func print(w io.Writer, p Printable) {
|
||||
outputTable(w, []Printable{p})
|
||||
}
|
||||
|
||||
// All prints the slice of printable items,
|
||||
// according to the caller's requested format.
|
||||
func All(ctx context.Context, ps ...Printable) {
|
||||
printAll(getRootCmd(ctx).OutOrStdout(), ps)
|
||||
}
|
||||
|
||||
// printAll prints the slice of printable items,
|
||||
// according to the caller's requested format.
|
||||
func printAll(w io.Writer, ps []Printable) {
|
||||
@ -141,33 +151,6 @@ func printAll(w io.Writer, ps []Printable) {
|
||||
outputTable(w, ps)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Type Formatters (TODO: migrate to owning packages)
|
||||
// ------------------------------------------------------------------------------------------
|
||||
|
||||
// Prints the backup to the terminal with stdout.
|
||||
func OutputBackup(ctx context.Context, b backup.Backup) {
|
||||
print(getRootCmd(ctx).OutOrStdout(), b)
|
||||
}
|
||||
|
||||
// Prints the backups to the terminal with stdout.
|
||||
func OutputBackups(ctx context.Context, bs []backup.Backup) {
|
||||
ps := []Printable{}
|
||||
for _, b := range bs {
|
||||
ps = append(ps, b)
|
||||
}
|
||||
printAll(getRootCmd(ctx).OutOrStdout(), ps)
|
||||
}
|
||||
|
||||
// Prints the entries to the terminal with stdout.
|
||||
func OutputEntries(ctx context.Context, des []details.DetailsEntry) {
|
||||
ps := []Printable{}
|
||||
for _, de := range des {
|
||||
ps = append(ps, de)
|
||||
}
|
||||
printAll(getRootCmd(ctx).OutOrStdout(), ps)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Tabular
|
||||
// ------------------------------------------------------------------------------------------
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/alcionai/corso/cli/print"
|
||||
"github.com/alcionai/corso/internal/common"
|
||||
"github.com/alcionai/corso/internal/connector/support"
|
||||
"github.com/alcionai/corso/internal/model"
|
||||
@ -34,6 +36,9 @@ type Backup struct {
|
||||
stats.StartAndEndTime
|
||||
}
|
||||
|
||||
// interface compliance checks
|
||||
var _ print.Printable = &Backup{}
|
||||
|
||||
func New(
|
||||
snapshotID, detailsID, status string,
|
||||
selector selectors.Selector,
|
||||
@ -55,6 +60,20 @@ func New(
|
||||
// CLI Output
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// Print writes the Backup to StdOut, in the format requested by the caller.
|
||||
func (b Backup) Print(ctx context.Context) {
|
||||
print.Item(ctx, b)
|
||||
}
|
||||
|
||||
// PrintAll writes the slice of Backups to StdOut, in the format requested by the caller.
|
||||
func PrintAll(ctx context.Context, bs []Backup) {
|
||||
ps := []print.Printable{}
|
||||
for _, b := range bs {
|
||||
ps = append(ps, print.Printable(b))
|
||||
}
|
||||
print.All(ctx, ps...)
|
||||
}
|
||||
|
||||
type Printable struct {
|
||||
ID model.StableID `json:"id"`
|
||||
ErrorCount int `json:"errorCount"`
|
||||
@ -66,7 +85,6 @@ type Printable struct {
|
||||
|
||||
// MinimumPrintable reduces the Backup to its minimally printable details.
|
||||
func (b Backup) MinimumPrintable() any {
|
||||
// todo: implement printable backup struct
|
||||
return Printable{
|
||||
ID: b.ID,
|
||||
ErrorCount: support.GetNumberOfErrors(b.ReadErrors) + support.GetNumberOfErrors(b.WriteErrors),
|
||||
|
||||
@ -1,18 +1,48 @@
|
||||
package details
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alcionai/corso/cli/print"
|
||||
"github.com/alcionai/corso/internal/model"
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Model
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// DetailsModel describes what was stored in a Backup
|
||||
type DetailsModel struct {
|
||||
model.BaseModel
|
||||
Entries []DetailsEntry `json:"entries"`
|
||||
}
|
||||
|
||||
// Print writes the DetailModel Entries to StdOut, in the format
|
||||
// requested by the caller.
|
||||
func (dm DetailsModel) PrintEntries(ctx context.Context) {
|
||||
ps := []print.Printable{}
|
||||
for _, de := range dm.Entries {
|
||||
ps = append(ps, de)
|
||||
}
|
||||
print.All(ctx, ps...)
|
||||
}
|
||||
|
||||
// Paths returns the list of Paths extracted from the Entries slice.
|
||||
func (dm DetailsModel) Paths() []string {
|
||||
ents := dm.Entries
|
||||
r := make([]string, len(ents))
|
||||
for i := range ents {
|
||||
r[i] = ents[i].RepoRef
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Details
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// Details augments the core with a mutex for processing.
|
||||
// Should be sliced back to d.DetailsModel for storage and
|
||||
// printing.
|
||||
@ -29,6 +59,10 @@ func (d *Details) Add(repoRef string, info ItemInfo) {
|
||||
d.Entries = append(d.Entries, DetailsEntry{RepoRef: repoRef, ItemInfo: info})
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// Entry
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// DetailsEntry describes a single item stored in a Backup
|
||||
type DetailsEntry struct {
|
||||
// TODO: `RepoRef` is currently the full path to the item in Kopia
|
||||
@ -37,15 +71,12 @@ type DetailsEntry struct {
|
||||
ItemInfo
|
||||
}
|
||||
|
||||
// Paths returns the list of Paths extracted from the Entries slice.
|
||||
func (dm DetailsModel) Paths() []string {
|
||||
ents := dm.Entries
|
||||
r := make([]string, len(ents))
|
||||
for i := range ents {
|
||||
r[i] = ents[i].RepoRef
|
||||
}
|
||||
return r
|
||||
}
|
||||
// --------------------------------------------------------------------------------
|
||||
// CLI Output
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// interface compliance checks
|
||||
var _ print.Printable = &DetailsEntry{}
|
||||
|
||||
// MinimumPrintable DetailsEntries is a passthrough func, because no
|
||||
// reduction is needed for the json output.
|
||||
|
||||
@ -4,13 +4,20 @@ 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
|
||||
@ -22,6 +29,15 @@ const (
|
||||
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
|
||||
@ -79,16 +95,58 @@ type loggingKey string
|
||||
const ctxKey loggingKey = "corsoLogger"
|
||||
|
||||
// Seed embeds a logger into the context for later retrieval.
|
||||
func Seed(ctx context.Context) (context.Context, *zap.SugaredLogger) {
|
||||
l := singleton(0)
|
||||
return context.WithValue(ctx, ctxKey, l), l
|
||||
// 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(0)
|
||||
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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user