myxce/src/frontend/apiserver/utils/logger.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)
}
}