blocky/log/logger.go

191 lines
4.1 KiB
Go

package log
//go:generate go run github.com/abice/go-enum -f=$GOFILE --marshal --names
import (
"fmt"
"io"
"strings"
"sync"
"sync/atomic"
"github.com/creasty/defaults"
"github.com/mattn/go-colorable"
"github.com/sirupsen/logrus"
prefixed "github.com/x-cray/logrus-prefixed-formatter"
"golang.org/x/exp/maps"
)
const prefixField = "prefix"
// Logger is the global logging instance
//
//nolint:gochecknoglobals
var (
logger *logrus.Logger
initDone atomic.Bool
)
// FormatType format for logging ENUM(
// text // logging as text
// json // JSON format
// )
type FormatType int
// Config defines all logging configurations
type Config struct {
Level logrus.Level `yaml:"level" default:"info"`
Format FormatType `yaml:"format" default:"text"`
Privacy bool `yaml:"privacy" default:"false"`
Timestamp bool `yaml:"timestamp" default:"true"`
}
// DefaultConfig returns a new Config initialized with default values.
func DefaultConfig() *Config {
cfg := new(Config)
defaults.MustSet(cfg)
return cfg
}
//nolint:gochecknoinits
func init() {
if !initDone.CompareAndSwap(false, true) {
return
}
newLogger := logrus.New()
ConfigureLogger(newLogger, DefaultConfig())
logger = newLogger
}
// Log returns the global logger
func Log() *logrus.Logger {
return logger
}
// PrefixedLog return the global logger with prefix
func PrefixedLog(prefix string) *logrus.Entry {
return logger.WithField(prefixField, prefix)
}
// WithPrefix adds the given prefix to the logger.
func WithPrefix(logger *logrus.Entry, prefix string) *logrus.Entry {
if existingPrefix, ok := logger.Data[prefixField]; ok {
prefix = fmt.Sprintf("%s.%s", existingPrefix, prefix)
}
return logger.WithField(prefixField, prefix)
}
// EscapeInput removes line breaks from input
func EscapeInput(input string) string {
result := strings.ReplaceAll(input, "\n", "")
result = strings.ReplaceAll(result, "\r", "")
return result
}
// Configure applies configuration to the global logger.
func Configure(cfg *Config) {
ConfigureLogger(logger, cfg)
}
// Configure applies configuration to the given logger.
func ConfigureLogger(logger *logrus.Logger, cfg *Config) {
logger.SetLevel(cfg.Level)
switch cfg.Format {
case FormatTypeText:
logFormatter := &prefixed.TextFormatter{
TimestampFormat: "2006-01-02 15:04:05",
FullTimestamp: true,
ForceFormatting: true,
ForceColors: false,
QuoteEmptyFields: true,
DisableTimestamp: !cfg.Timestamp,
}
logFormatter.SetColorScheme(&prefixed.ColorScheme{
PrefixStyle: "blue+b",
TimestampStyle: "white+h",
})
logger.SetFormatter(logFormatter)
// Windows does not support ANSI colors
logger.SetOutput(colorable.NewColorableStdout())
case FormatTypeJson:
logger.SetFormatter(&logrus.JSONFormatter{})
}
}
// Silence disables the logger output
func Silence() {
initDone.Store(true)
logger = logrus.New()
logger.SetFormatter(nopFormatter{}) // skip expensive formatting
// not actually needed but doesn't hurt
logger.SetOutput(io.Discard)
}
type nopFormatter struct{}
func (f nopFormatter) Format(*logrus.Entry) ([]byte, error) {
return nil, nil
}
func WithIndent(log *logrus.Entry, prefix string, callback func(*logrus.Entry)) {
undo := indentMessages(prefix, log.Logger)
defer undo()
callback(log)
}
// indentMessages modifies a logger and adds `prefix` to all messages.
//
// The returned function must be called to remove the prefix.
func indentMessages(prefix string, logger *logrus.Logger) func() {
if _, ok := logger.Formatter.(*prefixed.TextFormatter); !ok {
// log is not plaintext, do nothing
return func() {}
}
oldHooks := maps.Clone(logger.Hooks)
logger.AddHook(prefixMsgHook{
prefix: prefix,
})
var once sync.Once
return func() {
once.Do(func() {
logger.ReplaceHooks(oldHooks)
})
}
}
type prefixMsgHook struct {
prefix string
}
// Levels implements `logrus.Hook`.
func (h prefixMsgHook) Levels() []logrus.Level {
return logrus.AllLevels
}
// Fire implements `logrus.Hook`.
func (h prefixMsgHook) Fire(entry *logrus.Entry) error {
entry.Message = h.prefix + entry.Message
return nil
}