From e260786c1a4046c48ad594b6c4f10b63b9e8157f Mon Sep 17 00:00:00 2001 From: Yunkon Kim Date: Tue, 13 Aug 2024 13:26:42 +0900 Subject: [PATCH] Enhance logger pkg * Set permission to read log file in container volume * Set relative path with line number by zerolog.CallerMarshalFunc - This will make it easy to access the lines in the file that were logged in the IDE. - I expect this will ensure that the log format remains consistent across different execution environments. --- src/core/common/logger/logger.go | 239 ++++++++++++++----------------- src/main.go | 29 +++- 2 files changed, 138 insertions(+), 130 deletions(-) diff --git a/src/core/common/logger/logger.go b/src/core/common/logger/logger.go index 2b8b7d3bd..e43018d41 100644 --- a/src/core/common/logger/logger.go +++ b/src/core/common/logger/logger.go @@ -2,130 +2,158 @@ package logger import ( "os" + "path/filepath" + "runtime" "strconv" - "strings" - "time" + "sync" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/spf13/viper" "gopkg.in/natefinch/lumberjack.v2" ) var ( sharedLogFile *lumberjack.Logger + once sync.Once ) -func init() { - - // Map environment variable names to config file key names - envPrefix := "TB" - viper.SetEnvPrefix(envPrefix) - replacer := strings.NewReplacer(".", "_") - viper.SetEnvKeyReplacer(replacer) +type Config struct { + LogLevel string + LogWriter string + LogFilePath string + MaxSize int + MaxBackups int + MaxAge int + Compress bool +} - viper.AutomaticEnv() +func init() { - // Set config values - logLevel := viper.GetString("loglevel") - env := viper.GetString("node.env") + // For consistent log format across different running environments (e.g., local, Docker, Kubernetes) + // Set the caller field to the relative path from the project root + _, b, _, _ := runtime.Caller(0) + projectRoot := filepath.Join(filepath.Dir(b), "../../../../") // predict the project root directory from the current file having init() function + + zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string { + // relative path from the project root + relPath, err := filepath.Rel(projectRoot, file) + if err != nil { + return filepath.Base(file) + ":" + strconv.Itoa(line) // return the original file path with line number if the relative path cannot be resolved + } + return relPath + ":" + strconv.Itoa(line) + } +} - // Set the global logger to use JSON format. - zerolog.TimeFieldFormat = time.RFC3339 +// NewLogger initializes a new logger with default values if not provided +func NewLogger(config Config) *zerolog.Logger { + // Apply default values if not provided + if config.LogLevel == "" { + config.LogLevel = "debug" + } + if config.LogWriter == "" { + config.LogWriter = "console" + } + if config.LogFilePath == "" { + config.LogFilePath = "./log/app.log" + } + if config.MaxSize == 0 { + config.MaxSize = 10 // in MB + } + if config.MaxBackups == 0 { + config.MaxBackups = 3 + } + if config.MaxAge == 0 { + config.MaxAge = 30 // in days + } - // Get log file configuration from environment variables - logFilePath, maxSize, maxBackups, maxAge, compress := getLogFileConfig() + // Initialize shared log file for log rotation once + once.Do(func() { + sharedLogFile = &lumberjack.Logger{ + Filename: config.LogFilePath, + MaxSize: config.MaxSize, + MaxBackups: config.MaxBackups, + MaxAge: config.MaxAge, + Compress: config.Compress, + } + + // Ensure the log file exists before changing its permissions + if _, err := os.Stat(config.LogFilePath); os.IsNotExist(err) { + // Create the log file if it does not exist + file, err := os.Create(config.LogFilePath) + if err != nil { + log.Fatal().Msgf("Failed to create log file: %v", err) + } + file.Close() + } + + // Change file permissions to -rw-r--r-- + if err := os.Chmod(config.LogFilePath, 0644); err != nil { + log.Fatal().Msgf("Failed to change file permissions: %v", err) + } + }) + + level := getLogLevel(config.LogLevel) + logger := configureWriter(config.LogWriter, level) + + // Log a message to confirm logger setup + logger.Info(). + Str("logLevel", level.String()). + Msg("New logger created") - // Initialize a shared log file with lumberjack to manage log rotation - sharedLogFile = &lumberjack.Logger{ - Filename: logFilePath, - MaxSize: maxSize, - MaxBackups: maxBackups, - MaxAge: maxAge, - Compress: compress, - } + return logger +} - // Set the log level - var level zerolog.Level +// getLogLevel returns the zerolog.Level based on the string level +func getLogLevel(logLevel string) zerolog.Level { switch logLevel { case "trace": - level = zerolog.TraceLevel + return zerolog.TraceLevel case "debug": - level = zerolog.DebugLevel + return zerolog.DebugLevel case "info": - level = zerolog.InfoLevel + return zerolog.InfoLevel case "warn": - level = zerolog.WarnLevel + return zerolog.WarnLevel case "error": - level = zerolog.ErrorLevel + return zerolog.ErrorLevel case "fatal": - level = zerolog.FatalLevel + return zerolog.FatalLevel case "panic": - level = zerolog.PanicLevel + return zerolog.PanicLevel default: - log.Warn().Msgf("Invalid TB_LOGLEVEL value: %s. Using default value: info", logLevel) - level = zerolog.InfoLevel + log.Warn().Msgf("Invalid log level: %s. Using default value: info", logLevel) + return zerolog.InfoLevel } - - logger := NewLogger(level) - - // Set global logger - log.Logger = *logger - - // Check the execution environment from the environment variable - // Log a message - log.Info(). - Str("logLevel", level.String()). - Str("env", env). - Int("maxSize", maxSize). - Int("maxBackups", maxBackups). - Int("maxAge", maxAge). - Bool("compress", compress). - Msg("Global logger initialized") } -// Create a new logger -func NewLogger(level zerolog.Level) *zerolog.Logger { - - // Set config values - logwriter := viper.GetString("logwriter") - - // Multi-writer setup: logs to both file and console - multi := zerolog.MultiLevelWriter( - sharedLogFile, - zerolog.ConsoleWriter{Out: os.Stdout}, - ) - +// configureWriter sets up the logger based on the writer type +func configureWriter(logWriter string, level zerolog.Level) *zerolog.Logger { var logger zerolog.Logger + multi := zerolog.MultiLevelWriter(sharedLogFile, zerolog.ConsoleWriter{Out: os.Stdout}) - // Check the execution environment from the environment variable - // Configure the log output - if logwriter == "both" { - // Apply file to the global logger + switch logWriter { + case "both": logger = zerolog.New(multi).Level(level).With().Timestamp().Caller().Logger() - } else if logwriter == "file" { - // Apply file writer to the global logger + case "file": logger = zerolog.New(sharedLogFile).Level(level).With().Timestamp().Caller().Logger() - } else if logwriter == "stdout" { - // Apply ConsoleWriter to the global logger + case "stdout": logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).Level(level).With().Timestamp().Caller().Logger() - } else { - log.Warn().Msgf("Invalid TB_LOGWRITER value: %s. Using default value: both", logwriter) - // Apply multi-writer to the global logger + default: + log.Warn().Msgf("Invalid log writer: %s. Using default value: both", logWriter) logger = zerolog.New(multi).Level(level).With().Timestamp().Caller().Logger() } - // Log a message - logger.Info(). - Str("logLevel", level.String()). - Msg("New logger created") + logSetupInfo(logger, logWriter) + return &logger +} - if logwriter == "file" { +// logSetupInfo logs the logger setup details +func logSetupInfo(logger zerolog.Logger, logWriter string) { + if logWriter == "file" { logger.Info(). Str("logFilePath", sharedLogFile.Filename). Msg("Single-write setup (logs to file only)") - - } else if logwriter == "stdout" { + } else if logWriter == "stdout" { logger.Info(). Str("ConsoleWriter", "os.Stdout"). Msg("Single-write setup (logs to console only)") @@ -135,49 +163,4 @@ func NewLogger(level zerolog.Level) *zerolog.Logger { Str("ConsoleWriter", "os.Stdout"). Msg("Multi-writes setup (logs to both file and console)") } - - return &logger -} - -// Get log file configuration from environment variables -func getLogFileConfig() (string, int, int, int, bool) { - - // Set config values - logFilePath := viper.GetString("logfile.path") - - // Default: ./log/tumblebug.log - if logFilePath == "" { - log.Warn().Msg("TB_LOGFILE_PATH is not set. Using default value: ./log/tumblebug.log") - logFilePath = "./log/tumblebug.log" - } - - // Default: 10 MB - maxSize, err := strconv.Atoi(viper.GetString("logfile.maxsize")) - if err != nil { - log.Warn().Msgf("Invalid TB_LOGFILE_MAXSIZE value: %s. Using default value: 10 MB", viper.GetString("logfile.maxsize")) - maxSize = 10 - } - - // Default: 3 backups - maxBackups, err := strconv.Atoi(viper.GetString("logfile.maxbackups")) - if err != nil { - log.Warn().Msgf("Invalid TB_LOGFILE_MAXBACKUPS value: %s. Using default value: 3 backups", viper.GetString("logfile.maxbackups")) - maxBackups = 3 - } - - // Default: 30 days - maxAge, err := strconv.Atoi(viper.GetString("logfile.maxage")) - if err != nil { - log.Warn().Msgf("Invalid TB_LOGFILE_MAXAGE value: %s. Using default value: 30 days", viper.GetString("logfile.maxage")) - maxAge = 30 - } - - // Default: false - compress, err := strconv.ParseBool(viper.GetString("logfile.compress")) - if err != nil { - log.Warn().Msgf("Invalid TB_LOGFILE_COMPRESS value: %s. Using default value: false", viper.GetString("logfile.compress")) - compress = false - } - - return logFilePath, maxSize, maxBackups, maxAge, compress } diff --git a/src/main.go b/src/main.go index 942aa4002..cb6fecdf0 100644 --- a/src/main.go +++ b/src/main.go @@ -27,8 +27,7 @@ import ( "sync" "time" - // Black import (_) is for running a package's init() function without using its other contents. - _ "github.com/cloud-barista/cb-tumblebug/src/core/common/logger" + "github.com/cloud-barista/cb-tumblebug/src/core/common/logger" "github.com/cloud-barista/cb-tumblebug/src/kvstore/etcd" "github.com/cloud-barista/cb-tumblebug/src/kvstore/kvstore" "github.com/rs/zerolog/log" @@ -73,6 +72,32 @@ func init() { common.UpdateGlobalVariable(common.TerrariumRestUrl) common.UpdateGlobalVariable(common.StrAutocontrolDurationMs) + // Initialize the logger + logLevel := common.NVL(os.Getenv("TB_LOGLEVEL"), "debug") + logWriter := common.NVL(os.Getenv("TB_LOGWRITER"), "both") + logFilePath := common.NVL(os.Getenv("TB_LOGFILE_PATH"), "./log/tumblebug.log") + logMaxSizeStr := common.NVL(os.Getenv("TB_LOGFILE_MAXSIZE"), "10") + logMaxSize, _ := strconv.Atoi(logMaxSizeStr) + logMaxBackupsStr := common.NVL(os.Getenv("TB_LOGFILE_MAXBACKUPS"), "3") + logMaxBackups, _ := strconv.Atoi(logMaxBackupsStr) + logMaxAgeStr := common.NVL(os.Getenv("TB_LOGFILE_MAXAGE"), "3") + logMaxAge, _ := strconv.Atoi(logMaxAgeStr) + logCompressStr := common.NVL(os.Getenv("TB_LOGFILE_COMPRESS"), "false") + logCompress := (logCompressStr == "true") + + logger := logger.NewLogger(logger.Config{ + LogLevel: logLevel, + LogWriter: logWriter, + LogFilePath: logFilePath, + MaxSize: logMaxSize, + MaxBackups: logMaxBackups, + MaxAge: logMaxAge, + Compress: logCompress, + }) + + // Set the global logger + log.Logger = *logger + // load config //masterConfigInfos = confighandler.GetMasterConfigInfos()