You've already forked management-refactor
mirror of
https://github.com/netbirdio/management-refactor.git
synced 2026-05-22 17:12:59 -07:00
213 lines
4.9 KiB
Go
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
|
|
}
|