Files
ai_dianshang/server/pkg/logger/logger.go
2025-11-17 14:11:46 +08:00

532 lines
14 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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")
}
}