flags/log.go (151 lines of code) (raw):
/*
Copyright (c) Facebook, Inc. and its affiliates.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
*/
package flags
import (
"errors"
"fmt"
"os"
"sort"
"strconv"
"strings"
"github.com/facebookincubator/fbender/utils"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// LogLevel represents a log level flag value.
type LogLevel struct {
Logger *logrus.Logger
}
// LogLevelChoices returns a string representation of available levels.
func LogLevelChoices() []string {
choices := []string{}
for _, level := range logrus.AllLevels {
choices = append(choices, level.String())
}
return choices
}
// ErrInvalidLogLevel is raised when an unknown level is set.
var ErrInvalidLogLevel = errors.New("invalid log level")
func (l *LogLevel) String() string {
return l.Logger.Level.String()
}
// Set validates a given value and sets log level.
func (l *LogLevel) Set(value string) error {
level, err := logrus.ParseLevel(value)
if err != nil {
v, err := strconv.ParseUint(value, 10, 32)
if err != nil || v >= uint64(len(logrus.AllLevels)) {
choices := ChoicesString(LogLevelChoices())
return fmt.Errorf("%w, want: integer [0..%d] or %s, got: %s",
ErrInvalidLogLevel, len(logrus.AllLevels)-1, choices, value)
}
level = logrus.Level(v)
}
l.Logger.SetLevel(level)
return nil
}
// Type returns a log level value type.
func (l *LogLevel) Type() string {
return "level"
}
// Bash completion function constants.
const (
fnameLogLevel = "__fbender_handle_loglevel_flag"
fbodyLogLevel = `COMPREPLY=($(compgen -W "%s" -- "${cur}"))`
)
// BashCompletionLogLevel adds bash completion to a distribution flag.
func BashCompletionLogLevel(cmd *cobra.Command, f *pflag.FlagSet, name string) error {
flag := f.Lookup(name)
if flag == nil {
return fmt.Errorf("%w: %q", ErrUndefined, name)
}
if _, ok := flag.Value.(*LogLevel); !ok {
return fmt.Errorf("%w, want: level, got: %s", ErrInvalidType, flag.Value.Type())
}
fbody := fmt.Sprintf(fbodyLogLevel, strings.Join(LogLevelChoices(), " "))
return utils.BashCompletion(cmd, f, name, fnameLogLevel, fbody)
}
// LogFormat represents a log format flag value.
type LogFormat struct {
Logger *logrus.Logger
Format string
}
const (
testFormatter = "text"
jsonFormatter = "json"
)
// We need to store a lambda function which generates formatters so we don't
// assign the same static formatter to different Loggers.
//nolint:gochecknoglobals
var formatters = map[string](func() logrus.Formatter){
testFormatter: func() logrus.Formatter { return new(logrus.TextFormatter) },
jsonFormatter: func() logrus.Formatter { return new(logrus.JSONFormatter) },
}
// LogFormatChoices returns a string representation of available formats.
func LogFormatChoices() []string {
choices := []string{}
for key := range formatters {
choices = append(choices, key)
}
sort.Strings(choices)
return choices
}
// ErrInvalidLogFormat is raised when an unknown format is set.
var ErrInvalidLogFormat = errors.New("invalid log format")
func (l *LogFormat) String() string {
return l.Format
}
// Set validates a given value and sets log format.
func (l *LogFormat) Set(value string) error {
if formatter, ok := formatters[value]; ok {
l.Logger.Formatter = formatter()
l.Format = value
return nil
}
return fmt.Errorf("%w, want: %s, got: %q",
ErrInvalidLogFormat, ChoicesString(LogFormatChoices()), value)
}
// Type returns a log format value type.
func (l *LogFormat) Type() string {
return "format"
}
// Bash completion function constants.
const (
fnameLogFormat = "__fbender_handle_logformat_flag"
fbodyLogFormat = `COMPREPLY=($(compgen -W "%s" -- "${cur}"))`
)
// BashCompletionLogFormat adds bash completion to a distribution flag.
func BashCompletionLogFormat(cmd *cobra.Command, f *pflag.FlagSet, name string) error {
flag := f.Lookup(name)
if flag == nil {
return fmt.Errorf("%w: %q", ErrUndefined, name)
}
if _, ok := flag.Value.(*LogFormat); !ok {
return fmt.Errorf("%w, want: format, got: %s", ErrInvalidType, flag.Value.Type())
}
fbody := fmt.Sprintf(fbodyLogFormat, strings.Join(LogFormatChoices(), " "))
return utils.BashCompletion(cmd, f, name, fnameLogFormat, fbody)
}
// LogOutput represents a log output flag value.
type LogOutput struct {
Logger *logrus.Logger
Filename string
Out *os.File
}
// NewLogOutput returns new log output flag Value.
func NewLogOutput(logger *logrus.Logger) *LogOutput {
logger.Out = os.Stdout
return &LogOutput{
Logger: logger,
Out: os.Stdout,
}
}
func (l *LogOutput) String() string {
if l.Out == os.Stdout {
return "<stdout>"
} else if l.Out == os.Stderr {
return "<stderr>"
}
return l.Out.Name()
}
// Set opens the file and sets the Out of the Logger.
func (l *LogOutput) Set(value string) error {
// Close any file we have been writing to.
if l.Out != nil && l.Out != os.Stdout && l.Out != os.Stderr {
if err := l.Out.Close(); err != nil {
return fmt.Errorf("unable to close output %q: %w", l.Out.Name(), err)
}
}
// If the value is not an empty string it is a file name.
if len(value) > 0 {
file, err := os.Create(value)
if err != nil {
return fmt.Errorf("unable to create output %q: %w", value, err)
}
l.Out = file
} else {
l.Out = os.Stdout
}
l.Logger.Out = l.Out
return nil
}
// Type returns a log output value type.
func (l *LogOutput) Type() string {
return "output"
}