package utils import ( "errors" "fmt" "os" "path/filepath" "sync" "time" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" "gopkg.in/yaml.v2" ) var ( Log *zap.Logger once sync.Once config Config ) type Config struct { Logging struct { Level string `yaml:"level"` Encoding string `yaml:"encoding"` OutputPath string `yaml:"outputPath"` File struct { MaxSize int `yaml:"maxSize"` MaxBackups int `yaml:"maxBackups"` MaxAge int `yaml:"maxAge"` Compress bool `yaml:"compress"` } `yaml:"file"` EncoderConfig struct { MessageKey string `yaml:"messageKey"` LevelKey string `yaml:"levelKey"` TimeKey string `yaml:"timeKey"` NameKey string `yaml:"nameKey"` CallerKey string `yaml:"callerKey"` FunctionKey string `yaml:"functionKey"` StacktraceKey string `yaml:"stacktraceKey"` EncodeTime string `yaml:"timeEncoder"` EncodeLevel string `yaml:"levelEncoder"` } `yaml:"encoderConfig"` } `yaml:"logging"` } func InitLogger() error { var err error once.Do(func() { err = loadLoggerConfig() }) return err } func loadLoggerConfig() error { // read logger configuration file configPath := filepath.Join(Global_TopDir, "config", "logger.yaml") // validate the yaml file format data, err := ValidateYamlFormat(configPath) if err != nil { DiagnoseLogCreationError("Fail to call DiagnoseLogCreationError with error", err) return err } if data == nil { err = errors.New("invalid logger.yaml file") DiagnoseLogCreationError("Fail to call ValidateYamlFormat and data is nil", err) return err } if err := yaml.Unmarshal(data, &config); err != nil { DiagnoseLogCreationError("Failed to parse logger configuration file", err) return err } // Handle both formats for OutputPath if !filepath.IsAbs(config.Logging.OutputPath) { // If OutputPath is relative, prepend the logs directory config.Logging.OutputPath = filepath.Join(Global_TopDir, "logs", config.Logging.OutputPath) } // Get the directory from the absolute path logFilePath := config.Logging.OutputPath // it store the absolute path logDir := filepath.Dir(logFilePath) // Extract the directory // Create the directory if it does not exist if err := os.MkdirAll(logDir, 0755); err != nil { DiagnoseLogCreationError("Failed to create logs directory", err) return err } Log, err = createLogger(config) if err != nil { DiagnoseLogCreationError("Failed to create logger", err) return err } return nil } func createLogger(config Config) (*zap.Logger, error) { configLevel, err := zapcore.ParseLevel(config.Logging.Level) if err != nil { return nil, err } // create log file and set the permission file, err := os.OpenFile(config.Logging.OutputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return nil, err } file.Close() // configure lumberjack for log rotation logWriter := &lumberjack.Logger{ Filename: config.Logging.OutputPath, MaxSize: config.Logging.File.MaxSize, MaxBackups: config.Logging.File.MaxBackups, MaxAge: config.Logging.File.MaxAge, Compress: config.Logging.File.Compress, } // Create cores for different log levels core := zapcore.NewCore( zapcore.NewConsoleEncoder(zapcore.EncoderConfig{ MessageKey: config.Logging.EncoderConfig.MessageKey, LevelKey: config.Logging.EncoderConfig.LevelKey, TimeKey: config.Logging.EncoderConfig.TimeKey, NameKey: config.Logging.EncoderConfig.NameKey, CallerKey: config.Logging.EncoderConfig.CallerKey, StacktraceKey: "", // disable stacktrace EncodeTime: zapcore.ISO8601TimeEncoder, EncodeLevel: zapcore.CapitalLevelEncoder, EncodeCaller: zapcore.ShortCallerEncoder, ConsoleSeparator: " ", }), zapcore.AddSync(logWriter), configLevel, ) logger := zap.New(core, zap.AddCaller(), // caller info zap.AddStacktrace(zap.ErrorLevel), // show error stacktrace zap.AddCallerSkip(1), // skip one layer of caller ) return logger, nil } func Info(msg string, fields ...zap.Field) { Log.WithOptions(zap.WithCaller(false)).Info(msg, fields...) } func Error(msg string, fields ...zap.Field) { Log.WithOptions(zap.WithCaller(false)).Error(msg, fields...) } func Warn(msg string, fields ...zap.Field) { Log.WithOptions(zap.WithCaller(false)).Warn(msg, fields...) } func Debug(msg string, fields ...zap.Field) { Log.WithOptions(zap.WithCaller(true)).Debug(msg, fields...) } func Fatal(msg string, fields ...zap.Field) { Log.WithOptions(zap.WithCaller(false)).Fatal(msg, fields...) } // DiagnoseLogCreationError is a helper function to diagnose the error when creating log file func DiagnoseLogCreationError(context string, err error) { // construct the error message logMsg := fmt.Sprintf("[%s] %s: %v\n", time.Now().Format("2006-01-02 15:04:05"), context, err) // define logger_error.log file path logFilePath := filepath.Join(Global_TopDir, "logs", "logger_error.log") // open log file f, fileErr := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if fileErr != nil { fmt.Printf("Failed to open logger_error.log: %v\n", fileErr) // if failed to open log file, print the error message to console fmt.Print(logMsg) return } defer f.Close() // write the error message if _, writeErr := f.WriteString(logMsg); writeErr != nil { fmt.Printf("Failed to write to logger_error.log: %v\n", writeErr) // if failed to write to log file, print the error message to console fmt.Print(logMsg) } }