Skip to content
Snippets Groups Projects
logging.go 4 KiB
Newer Older
package orusapi

import (
	"fmt"
	"io"

	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
// NewLoggingOptions creates a LoggingOptions.
func NewLoggingOptions(log *zerolog.Logger, output io.Writer) (*LoggingOptions, error) {
	var o LoggingOptions
	if err := o.Setup(log, output); err != nil {
		return nil, err
	}
Axel Prel's avatar
Axel Prel committed

	return &o, nil
}

// MustLoggingOptions panic if err is not nil.
func MustLoggingOptions(o *LoggingOptions, err error) *LoggingOptions {
	if err != nil {
		panic(err)
	}
Axel Prel's avatar
Axel Prel committed

// DefaultLogger ...
func DefaultLogger(output io.Writer) zerolog.Logger {
	return zerolog.
		New(output).
		With().
		Timestamp().
		Logger().
		Level(zerolog.WarnLevel)
}

// LoggingOptions holds the logging options.
type LoggingOptions struct {
Axel Prel's avatar
Axel Prel committed
	Level   func(string) error `long:"level" env:"LEVEL" ini-name:"log-level" choice:"trace" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"fatal" choice:"panic" choice:"auto" default:"auto" description:"log level. 'auto' selects 'info' when stdout is a tty, 'error' otherwise."` //nolint:lll
	Format  func(string) error `long:"format" env:"FORMAT" ini-name:"log-format" choice:"json" choice:"pretty" choice:"auto" default:"auto" description:"Logs format. 'auto' selects 'pretty' if stdout is a tty."`                                                                                        //nolint:lll
	Verbose func()             `short:"v" long:"verbose" no-ini:"t" description:"Increase log verbosity. Can be repeated"`

	logFinalOutput io.Writer                   `no-flag:"t"`
	logOutput      io.Writer                   `no-flag:"t"`
	wrappedOutput  io.Writer                   `no-flag:"t"`
	logWrappers    []func(io.Writer) io.Writer `no-flag:"t"`
	log            *zerolog.Logger             `no-flag:"t"`
}

// Logger returns the latest configured logger.
func (o *LoggingOptions) Logger() zerolog.Logger {
	return *o.log
}

func (o *LoggingOptions) Output() io.Writer {
	return o.wrappedOutput
}

func (o *LoggingOptions) resetOutput() {
	out := o.logOutput
	for _, wrapper := range o.logWrappers {
		out = wrapper(out)
	}
	o.wrappedOutput = out
	*o.log = o.log.Output(out)
}

// AddLogWrapper adds a log wrapper to the stack.
Christophe de Vienne's avatar
Christophe de Vienne committed
func (o *LoggingOptions) AddLogWrapper(wrapper func(io.Writer) io.Writer) {
	o.logWrappers = append(o.logWrappers, wrapper)
	o.resetOutput()
}

// SetMinLoggingLevel makes sure the logging level is not under a given value.
func (o *LoggingOptions) SetMinLoggingLevel(level zerolog.Level) {
	if level < o.log.GetLevel() {
		*o.log = log.Level(level)
	}
}

// Setup ...
func (o *LoggingOptions) Setup(log *zerolog.Logger, output io.Writer) error {
	logLevelAutoLocked := false

	o.logFinalOutput = output

	o.log = log
	*o.log = DefaultLogger(output)

	o.Format = func(format string) error {
		if format == "auto" {
Axel Prel's avatar
Axel Prel committed
			if outputFile, hasFd := o.logFinalOutput.(interface{ Fd() uintptr }); hasFd &&
				term.IsTerminal(int(outputFile.Fd())) {
				format = "pretty"
			} else {
				format = "json"
			}
		}
		switch format {
		case "pretty":
			o.logOutput = ConsoleWriter{Out: o.logFinalOutput}
		case "json":
			o.logOutput = o.logFinalOutput
		default:
			return fmt.Errorf("invalid log-format: %s", format)
		}
		o.resetOutput()
Axel Prel's avatar
Axel Prel committed

		return nil
	}
	o.Verbose = func() {
		*o.log = o.log.Level(o.log.GetLevel() - zerolog.Level(1))
	}
	o.Level = func(value string) error {
		if value == "auto" {
			if logLevelAutoLocked {
				// The current call is at best redondant, at worse called by
				// default after some potential --verbose that would be ignored
				return nil
			}
Axel Prel's avatar
Axel Prel committed
			if outputFile, hasFd := o.logFinalOutput.(interface{ Fd() uintptr }); hasFd &&
				term.IsTerminal(int(outputFile.Fd())) {
				value = zerolog.LevelInfoValue
Axel Prel's avatar
Axel Prel committed
				value = zerolog.LevelWarnValue
			}
		}

		level, err := zerolog.ParseLevel(value)
		if err != nil {
			return err
		}
		*o.log = o.log.Level(level)
Axel Prel's avatar
Axel Prel committed

		return nil
	}
	if err := o.Format("auto"); err != nil {
		return err
	}
	if err := o.Level("auto"); err != nil {
		return err
	}
	logLevelAutoLocked = true
Axel Prel's avatar
Axel Prel committed