188 lines
5.5 KiB
Go
188 lines
5.5 KiB
Go
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)
|
|
}
|
|
}
|