Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance logger pkg #1709

Merged
merged 1 commit into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 111 additions & 128 deletions src/core/common/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand All @@ -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
}
29 changes: 27 additions & 2 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()

Expand Down