Files
Pascal Fischer 3c82465202 hook event store
2025-05-27 09:51:28 +02:00

213 lines
4.9 KiB
Go

package logging
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
"sync"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"google.golang.org/grpc/grpclog"
"gopkg.in/natefinch/lumberjack.v2"
)
// global map of package paths to *logrus.Logger
var (
mu sync.RWMutex
loggers = map[string]*logrus.Logger{}
)
// Init reads the logging config from a YAML file and sets up per-package loggers.
func Init(configFilePath string) error {
v := viper.New()
v.SetConfigFile(configFilePath)
// Read the YAML
if err := v.ReadInConfig(); err != nil {
return fmt.Errorf("failed to read logging config: %w", err)
}
var cfg LoggingConfig
if err := v.Unmarshal(&cfg); err != nil {
return fmt.Errorf("failed to unmarshal logging config: %w", err)
}
mu.Lock()
defer mu.Unlock()
// For each package in our config, create a logger at the given level
for pkgPath, levelStr := range cfg.LogLevels {
l := logrus.New() // each package gets its own *logrus.Logger
l.SetLevel(parseLogrusLevel(levelStr))
// Optionally, set the formatter, output, etc.:
// l.SetFormatter(&logrus.JSONFormatter{})
// l.SetOutput(os.Stdout)
loggers[pkgPath] = l
}
// Optionally, define a default logger for packages not explicitly listed
if _, ok := loggers["default"]; !ok {
defaultLogger := logrus.New()
defaultLogger.SetLevel(logrus.InfoLevel)
loggers["default"] = defaultLogger
}
return nil
}
// parseLogrusLevel is a helper that converts a string (e.g. "debug") to a logrus.Level.
func parseLogrusLevel(levelStr string) logrus.Level {
switch strings.ToLower(levelStr) {
case "trace":
return logrus.TraceLevel
case "debug":
return logrus.DebugLevel
case "info":
return logrus.InfoLevel
case "warn", "warning":
return logrus.WarnLevel
case "error":
return logrus.ErrorLevel
case "fatal":
return logrus.FatalLevel
case "panic":
return logrus.PanicLevel
}
// default
return logrus.InfoLevel
}
// LoggerFor returns a *logrus.Logger for the specified package path.
// If there's no explicit logger, we return the "default" logger.
func LoggerFor(pkgPath string) *logrus.Logger {
mu.RLock()
defer mu.RUnlock()
if l, ok := loggers[pkgPath]; ok {
return l
}
if l, ok := loggers["default"]; ok {
return l
}
logrus.Printf("No logger configured for %q; using fallback (info-level) logger.\n", pkgPath)
fallback := logrus.New()
fallback.SetLevel(logrus.InfoLevel)
return fallback
}
func LoggerForThisPackage() *logrus.Logger {
pc, _, _, ok := runtime.Caller(1)
if !ok {
return LoggerFor("default")
}
fn := runtime.FuncForPC(pc)
if fn == nil {
return LoggerFor("default")
}
fullFuncName := fn.Name()
pkgPath := parsePackageFromFuncName(fullFuncName)
return LoggerFor(pkgPath)
}
func parsePackageFromFuncName(funcName string) string {
parts := strings.Split(funcName, "/")
if len(parts) == 0 {
return "default"
}
last := parts[len(parts)-1]
base := strings.Join(parts[:len(parts)-1], "/")
dotIdx := strings.IndexByte(last, '.')
var pkgName string
if dotIdx == -1 {
pkgName = last
} else {
pkgName = last[:dotIdx]
}
return base + "/" + pkgName
}
// InitLog parses and sets log-level input
func InitLog(logLevel string, logPath string) error {
level, err := logrus.ParseLevel(logLevel)
if err != nil {
logrus.Errorf("Failed parsing log-level %s: %s", logLevel, err)
return err
}
customOutputs := []string{"console", "syslog"}
if logPath != "" && !slices.Contains(customOutputs, logPath) {
maxLogSize := getLogMaxSize()
lumberjackLogger := &lumberjack.Logger{
// Log file absolute path, os agnostic
Filename: filepath.ToSlash(logPath),
MaxSize: maxLogSize, // MB
MaxBackups: 10,
MaxAge: 30, // days
Compress: true,
}
logrus.SetOutput(io.Writer(lumberjackLogger))
} else if logPath == "syslog" {
AddSyslogHook()
}
//nolint:gocritic
if os.Getenv("NB_LOG_FORMAT") == "json" {
SetJSONFormatter(logrus.StandardLogger())
} else if logPath == "syslog" {
SetSyslogFormatter(logrus.StandardLogger())
} else {
SetTextFormatter(logrus.StandardLogger())
}
logrus.SetLevel(level)
setGRPCLibLogger()
return nil
}
func setGRPCLibLogger() {
logOut := logrus.StandardLogger().Writer()
if os.Getenv("GRPC_GO_LOG_SEVERITY_LEVEL") != "info" {
grpclog.SetLoggerV2(grpclog.NewLoggerV2(io.Discard, logOut, logOut))
return
}
var v int
vLevel := os.Getenv("GRPC_GO_LOG_VERBOSITY_LEVEL")
if vl, err := strconv.Atoi(vLevel); err == nil {
v = vl
}
grpclog.SetLoggerV2(grpclog.NewLoggerV2WithVerbosity(logOut, logOut, logOut, v))
}
func getLogMaxSize() int {
if sizeVar, ok := os.LookupEnv("NB_LOG_MAX_SIZE_MB"); ok {
size, err := strconv.ParseInt(sizeVar, 10, 64)
if err != nil {
logrus.Errorf("Failed parsing log-size %s: %s. Should be just an integer", sizeVar, err)
return defaultLogSize
}
logrus.Infof("Setting log file max size to %d MB", size)
return int(size)
}
return defaultLogSize
}