init
This commit is contained in:
532
server/pkg/logger/logger.go
Normal file
532
server/pkg/logger/logger.go
Normal file
@@ -0,0 +1,532 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
// LogConfig 日志配置
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
Filename string `mapstructure:"filename"`
|
||||
MaxSize int `mapstructure:"maxSize"`
|
||||
MaxAge int `mapstructure:"maxAge"`
|
||||
MaxBackups int `mapstructure:"maxBackups"`
|
||||
EnableConsole bool `mapstructure:"enableConsole"`
|
||||
EnableFile bool `mapstructure:"enableFile"`
|
||||
Format string `mapstructure:"format"`
|
||||
EnableCaller bool `mapstructure:"enableCaller"`
|
||||
EnableOperation bool `mapstructure:"enableOperation"`
|
||||
EnablePerf bool `mapstructure:"enablePerf"`
|
||||
PerfThreshold int64 `mapstructure:"perfThreshold"`
|
||||
}
|
||||
|
||||
var (
|
||||
logConfig LogConfig
|
||||
)
|
||||
|
||||
var (
|
||||
log *logrus.Logger
|
||||
isDevelopment bool
|
||||
)
|
||||
|
||||
// ContextKey 上下文键类型
|
||||
type ContextKey string
|
||||
|
||||
const (
|
||||
// RequestIDKey 请求ID键
|
||||
RequestIDKey ContextKey = "request_id"
|
||||
// UserIDKey 用户ID键
|
||||
UserIDKey ContextKey = "user_id"
|
||||
// OperationKey 操作类型键
|
||||
OperationKey ContextKey = "operation"
|
||||
// ModuleKey 模块名称键
|
||||
ModuleKey ContextKey = "module"
|
||||
)
|
||||
|
||||
// CustomFormatter 自定义日志格式器
|
||||
type CustomFormatter struct {
|
||||
TimestampFormat string
|
||||
Environment string
|
||||
}
|
||||
|
||||
// Format 格式化日志
|
||||
func (f *CustomFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
timestamp := entry.Time.Format(f.TimestampFormat)
|
||||
|
||||
// 获取调用者信息
|
||||
caller := ""
|
||||
if entry.HasCaller() {
|
||||
caller = fmt.Sprintf("%s:%d", filepath.Base(entry.Caller.File), entry.Caller.Line)
|
||||
}
|
||||
|
||||
// 开发环境:可读性更好的文本格式
|
||||
var msg strings.Builder
|
||||
msg.WriteString(fmt.Sprintf("[%s] %s %s",
|
||||
timestamp,
|
||||
strings.ToUpper(entry.Level.String()),
|
||||
entry.Message))
|
||||
|
||||
if caller != "" {
|
||||
msg.WriteString(fmt.Sprintf(" (%s)", caller))
|
||||
}
|
||||
|
||||
// 添加字段信息
|
||||
if len(entry.Data) > 0 {
|
||||
msg.WriteString(" |")
|
||||
for k, v := range entry.Data {
|
||||
msg.WriteString(fmt.Sprintf(" %s=%v", k, v))
|
||||
}
|
||||
}
|
||||
msg.WriteString("\n")
|
||||
|
||||
return []byte(msg.String()), nil
|
||||
}
|
||||
|
||||
// Init 初始化日志
|
||||
func Init(cfg LogConfig) {
|
||||
log = logrus.New()
|
||||
logConfig = cfg // 存储配置
|
||||
|
||||
// 判断是否为开发环境
|
||||
isDevelopment = os.Getenv("GIN_MODE") != "release"
|
||||
|
||||
// 设置日志级别
|
||||
level, err := logrus.ParseLevel(cfg.Level)
|
||||
if err != nil {
|
||||
level = logrus.InfoLevel
|
||||
}
|
||||
log.SetLevel(level)
|
||||
|
||||
// 设置调用者信息
|
||||
log.SetReportCaller(cfg.EnableCaller)
|
||||
|
||||
// 设置日志格式化器
|
||||
var formatter logrus.Formatter
|
||||
if cfg.Format == "json" {
|
||||
// 生产环境使用JSON格式
|
||||
formatter = &logrus.JSONFormatter{
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
DisableHTMLEscape: true, // 禁用HTML转义,避免特殊字符问题
|
||||
FieldMap: logrus.FieldMap{
|
||||
logrus.FieldKeyTime: "timestamp",
|
||||
logrus.FieldKeyLevel: "level",
|
||||
logrus.FieldKeyMsg: "message",
|
||||
logrus.FieldKeyFunc: "caller",
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// 开发环境使用文本格式
|
||||
formatter = &CustomFormatter{
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
Environment: "development",
|
||||
}
|
||||
}
|
||||
log.SetFormatter(formatter)
|
||||
|
||||
// 配置输出
|
||||
var outputs []io.Writer
|
||||
|
||||
// 控制台输出
|
||||
if cfg.EnableConsole {
|
||||
outputs = append(outputs, os.Stdout)
|
||||
}
|
||||
|
||||
// 文件输出
|
||||
if cfg.EnableFile && cfg.Filename != "" {
|
||||
// 确保日志目录存在
|
||||
logDir := filepath.Dir(cfg.Filename)
|
||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||
log.Fatalf("创建日志目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 配置lumberjack进行日志轮转
|
||||
lumberjackLogger := &lumberjack.Logger{
|
||||
Filename: cfg.Filename,
|
||||
MaxSize: cfg.MaxSize, // MB
|
||||
MaxAge: cfg.MaxAge, // days
|
||||
MaxBackups: cfg.MaxBackups, // files
|
||||
LocalTime: true,
|
||||
Compress: true,
|
||||
}
|
||||
outputs = append(outputs, lumberjackLogger)
|
||||
}
|
||||
|
||||
// 设置输出
|
||||
if len(outputs) > 0 {
|
||||
if len(outputs) == 1 {
|
||||
log.SetOutput(outputs[0])
|
||||
} else {
|
||||
log.SetOutput(io.MultiWriter(outputs...))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetLogger 获取日志实例
|
||||
func GetLogger() *logrus.Logger {
|
||||
if log == nil {
|
||||
log = logrus.New()
|
||||
}
|
||||
return log
|
||||
}
|
||||
|
||||
// GenerateRequestID 生成请求ID
|
||||
func GenerateRequestID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
// WithContext 从上下文创建带有上下文信息的日志条目
|
||||
func WithContext(ctx context.Context) *logrus.Entry {
|
||||
entry := GetLogger().WithFields(logrus.Fields{})
|
||||
|
||||
if requestID := ctx.Value(RequestIDKey); requestID != nil {
|
||||
entry = entry.WithField("request_id", requestID)
|
||||
}
|
||||
|
||||
if userID := ctx.Value(UserIDKey); userID != nil {
|
||||
entry = entry.WithField("user_id", userID)
|
||||
}
|
||||
|
||||
if operation := ctx.Value(OperationKey); operation != nil {
|
||||
entry = entry.WithField("operation", operation)
|
||||
}
|
||||
|
||||
if module := ctx.Value(ModuleKey); module != nil {
|
||||
entry = entry.WithField("module", module)
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
// Debug 调试日志
|
||||
func Debug(args ...interface{}) {
|
||||
GetLogger().Debug(args...)
|
||||
}
|
||||
|
||||
// Debugf 格式化调试日志
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
GetLogger().Debugf(format, args...)
|
||||
}
|
||||
|
||||
// DebugWithContext 带上下文的调试日志
|
||||
func DebugWithContext(ctx context.Context, args ...interface{}) {
|
||||
WithContext(ctx).Debug(args...)
|
||||
}
|
||||
|
||||
// DebugfWithContext 带上下文的格式化调试日志
|
||||
func DebugfWithContext(ctx context.Context, format string, args ...interface{}) {
|
||||
WithContext(ctx).Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Info 信息日志
|
||||
func Info(args ...interface{}) {
|
||||
GetLogger().Info(args...)
|
||||
}
|
||||
|
||||
// Infof 格式化信息日志
|
||||
func Infof(format string, args ...interface{}) {
|
||||
GetLogger().Infof(format, args...)
|
||||
}
|
||||
|
||||
// InfoWithContext 带上下文的信息日志
|
||||
func InfoWithContext(ctx context.Context, args ...interface{}) {
|
||||
WithContext(ctx).Info(args...)
|
||||
}
|
||||
|
||||
// InfofWithContext 带上下文的格式化信息日志
|
||||
func InfofWithContext(ctx context.Context, format string, args ...interface{}) {
|
||||
WithContext(ctx).Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warn 警告日志
|
||||
func Warn(args ...interface{}) {
|
||||
GetLogger().Warn(args...)
|
||||
}
|
||||
|
||||
// Warnf 格式化警告日志
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
GetLogger().Warnf(format, args...)
|
||||
}
|
||||
|
||||
// WarnWithContext 带上下文的警告日志
|
||||
func WarnWithContext(ctx context.Context, args ...interface{}) {
|
||||
WithContext(ctx).Warn(args...)
|
||||
}
|
||||
|
||||
// WarnfWithContext 带上下文的格式化警告日志
|
||||
func WarnfWithContext(ctx context.Context, format string, args ...interface{}) {
|
||||
WithContext(ctx).Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Error 错误日志
|
||||
func Error(args ...interface{}) {
|
||||
GetLogger().Error(args...)
|
||||
}
|
||||
|
||||
// Errorf 格式化错误日志
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
GetLogger().Errorf(format, args...)
|
||||
}
|
||||
|
||||
// ErrorWithContext 带上下文的错误日志
|
||||
func ErrorWithContext(ctx context.Context, args ...interface{}) {
|
||||
WithContext(ctx).Error(args...)
|
||||
}
|
||||
|
||||
// ErrorfWithContext 带上下文的格式化错误日志
|
||||
func ErrorfWithContext(ctx context.Context, format string, args ...interface{}) {
|
||||
WithContext(ctx).Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Fatal 致命错误日志
|
||||
func Fatal(args ...interface{}) {
|
||||
GetLogger().Fatal(args...)
|
||||
}
|
||||
|
||||
// Fatalf 格式化致命错误日志
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
GetLogger().Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// WithField 添加字段
|
||||
func WithField(key string, value interface{}) *logrus.Entry {
|
||||
return GetLogger().WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields 添加多个字段
|
||||
func WithFields(fields logrus.Fields) *logrus.Entry {
|
||||
return GetLogger().WithFields(fields)
|
||||
}
|
||||
|
||||
// WithError 添加错误字段
|
||||
func WithError(err error) *logrus.Entry {
|
||||
return GetLogger().WithError(err)
|
||||
}
|
||||
|
||||
// LogRequest 记录请求日志
|
||||
func LogRequest(method, path, ip, userAgent string, statusCode int, duration int64) {
|
||||
GetLogger().WithFields(logrus.Fields{
|
||||
"type": "http_request",
|
||||
"method": method,
|
||||
"path": path,
|
||||
"ip": ip,
|
||||
"user_agent": userAgent,
|
||||
"status": statusCode,
|
||||
"duration": duration,
|
||||
"timestamp": time.Now().Unix(),
|
||||
}).Info("HTTP Request")
|
||||
}
|
||||
|
||||
// LogRequestWithContext 带上下文的请求日志
|
||||
func LogRequestWithContext(ctx context.Context, method, path, ip, userAgent string, statusCode int, duration int64) {
|
||||
WithContext(ctx).WithFields(logrus.Fields{
|
||||
"type": "http_request",
|
||||
"method": method,
|
||||
"path": path,
|
||||
"ip": ip,
|
||||
"user_agent": userAgent,
|
||||
"status": statusCode,
|
||||
"duration": duration,
|
||||
"timestamp": time.Now().Unix(),
|
||||
}).Info("HTTP Request")
|
||||
}
|
||||
|
||||
// LogError 记录错误日志
|
||||
func LogError(err error, context map[string]interface{}) {
|
||||
entry := GetLogger().WithError(err).WithField("type", "application_error")
|
||||
|
||||
// 添加错误堆栈信息
|
||||
if pc, file, line, ok := runtime.Caller(1); ok {
|
||||
entry = entry.WithFields(logrus.Fields{
|
||||
"error_file": filepath.Base(file),
|
||||
"error_line": line,
|
||||
"error_func": runtime.FuncForPC(pc).Name(),
|
||||
})
|
||||
}
|
||||
|
||||
if context != nil {
|
||||
entry = entry.WithFields(context)
|
||||
}
|
||||
|
||||
entry.Error("Application Error")
|
||||
}
|
||||
|
||||
// LogErrorWithContext 带上下文的错误日志
|
||||
func LogErrorWithContext(ctx context.Context, err error, context map[string]interface{}) {
|
||||
entry := WithContext(ctx).WithError(err).WithField("type", "application_error")
|
||||
|
||||
// 添加错误堆栈信息
|
||||
if pc, file, line, ok := runtime.Caller(1); ok {
|
||||
entry = entry.WithFields(logrus.Fields{
|
||||
"error_file": filepath.Base(file),
|
||||
"error_line": line,
|
||||
"error_func": runtime.FuncForPC(pc).Name(),
|
||||
})
|
||||
}
|
||||
|
||||
if context != nil {
|
||||
entry = entry.WithFields(context)
|
||||
}
|
||||
|
||||
entry.Error("Application Error")
|
||||
}
|
||||
|
||||
// LogOperation 记录操作日志
|
||||
func LogOperation(userID uint, userType, operation, resource, method, path, ip string, requestData, responseData interface{}, statusCode int, duration time.Duration) {
|
||||
if !logConfig.EnableOperation {
|
||||
return
|
||||
}
|
||||
|
||||
GetLogger().WithFields(logrus.Fields{
|
||||
"type": "operation",
|
||||
"user_id": userID,
|
||||
"user_type": userType,
|
||||
"operation": operation,
|
||||
"resource": resource,
|
||||
"method": method,
|
||||
"path": path,
|
||||
"ip": ip,
|
||||
"request_data": requestData,
|
||||
"response_data": responseData,
|
||||
"status_code": statusCode,
|
||||
"duration": duration.Milliseconds(),
|
||||
"timestamp": time.Now().Unix(),
|
||||
}).Info("User Operation")
|
||||
}
|
||||
|
||||
// LogOperationWithContext 带上下文的操作日志
|
||||
func LogOperationWithContext(ctx context.Context, userID uint, userType, operation, resource, method, path, ip string, requestData, responseData interface{}, statusCode int, duration time.Duration) {
|
||||
if !logConfig.EnableOperation {
|
||||
return
|
||||
}
|
||||
|
||||
WithContext(ctx).WithFields(logrus.Fields{
|
||||
"type": "operation",
|
||||
"user_id": userID,
|
||||
"user_type": userType,
|
||||
"operation": operation,
|
||||
"resource": resource,
|
||||
"method": method,
|
||||
"path": path,
|
||||
"ip": ip,
|
||||
"request_data": requestData,
|
||||
"response_data": responseData,
|
||||
"status_code": statusCode,
|
||||
"duration": duration.Milliseconds(),
|
||||
"timestamp": time.Now().Unix(),
|
||||
}).Info("User Operation")
|
||||
}
|
||||
|
||||
// LogPerformance 记录性能日志
|
||||
func LogPerformance(operation string, duration time.Duration, details map[string]interface{}) {
|
||||
if !logConfig.EnablePerf {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否超过阈值
|
||||
if duration.Milliseconds() < logConfig.PerfThreshold {
|
||||
return
|
||||
}
|
||||
|
||||
entry := GetLogger().WithFields(logrus.Fields{
|
||||
"type": "performance",
|
||||
"operation": operation,
|
||||
"duration": duration.Milliseconds(),
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
|
||||
if details != nil {
|
||||
entry = entry.WithFields(details)
|
||||
}
|
||||
|
||||
// 根据耗时判断日志级别
|
||||
if duration > 5*time.Second {
|
||||
entry.Error("Slow Operation Detected")
|
||||
} else if duration > 1*time.Second {
|
||||
entry.Warn("Performance Warning")
|
||||
} else {
|
||||
entry.Info("Performance Log")
|
||||
}
|
||||
}
|
||||
|
||||
// LogPerformanceWithContext 带上下文的性能日志
|
||||
func LogPerformanceWithContext(ctx context.Context, operation string, duration time.Duration, details map[string]interface{}) {
|
||||
if !logConfig.EnablePerf {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否超过阈值
|
||||
if duration.Milliseconds() < logConfig.PerfThreshold {
|
||||
return
|
||||
}
|
||||
|
||||
entry := WithContext(ctx).WithFields(logrus.Fields{
|
||||
"type": "performance",
|
||||
"operation": operation,
|
||||
"duration": duration.Milliseconds(),
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
|
||||
if details != nil {
|
||||
entry = entry.WithFields(details)
|
||||
}
|
||||
|
||||
// 根据耗时判断日志级别
|
||||
if duration > 5*time.Second {
|
||||
entry.Error("Slow Operation Detected")
|
||||
} else if duration > 1*time.Second {
|
||||
entry.Warn("Performance Warning")
|
||||
} else {
|
||||
entry.Info("Performance Log")
|
||||
}
|
||||
}
|
||||
|
||||
// LogDatabase 记录数据库操作日志
|
||||
func LogDatabase(operation, table string, duration time.Duration, rowsAffected int64, query string) {
|
||||
entry := GetLogger().WithFields(logrus.Fields{
|
||||
"type": "database",
|
||||
"operation": operation,
|
||||
"table": table,
|
||||
"duration": duration.Milliseconds(),
|
||||
"rows_affected": rowsAffected,
|
||||
"query": query,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
|
||||
// 根据耗时判断日志级别
|
||||
if duration > 2*time.Second {
|
||||
entry.Warn("Slow Database Query")
|
||||
} else {
|
||||
entry.Debug("Database Operation")
|
||||
}
|
||||
}
|
||||
|
||||
// LogDatabaseWithContext 带上下文的数据库操作日志
|
||||
func LogDatabaseWithContext(ctx context.Context, operation, table string, duration time.Duration, rowsAffected int64, query string) {
|
||||
entry := WithContext(ctx).WithFields(logrus.Fields{
|
||||
"type": "database",
|
||||
"operation": operation,
|
||||
"table": table,
|
||||
"duration": duration.Milliseconds(),
|
||||
"rows_affected": rowsAffected,
|
||||
"query": query,
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
|
||||
// 根据耗时判断日志级别
|
||||
if duration > 2*time.Second {
|
||||
entry.Warn("Slow Database Query")
|
||||
} else {
|
||||
entry.Debug("Database Operation")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user