diff --git a/.env.example b/.env.example index a0cc7ff..065f69f 100644 --- a/.env.example +++ b/.env.example @@ -50,6 +50,21 @@ DISABLE_COMPRESSION=true # 缓冲区大小(字节) BUFFER_SIZE=32768 +# =========================================== +# 日志配置 +# =========================================== +# 日志级别 (debug, info, warn, error) +LOG_LEVEL=info + +# 日志格式 (text, json) +LOG_FORMAT=text + +# 启用文件日志 +LOG_ENABLE_FILE=false + +# 日志文件路径 +LOG_FILE_PATH=logs/app.log + # =========================================== # 认证配置 # =========================================== @@ -65,11 +80,4 @@ ENABLE_CORS=true # 允许的来源(逗号分隔,* 表示允许所有) ALLOWED_ORIGINS=* -# =========================================== -# 性能配置 -# =========================================== -# HTTP 连接池最大连接数 -MAX_SOCKETS=50 -# HTTP 连接池最大空闲连接数 -MAX_FREE_SOCKETS=10 diff --git a/cmd/main.go b/cmd/main.go index 0ffb1d0..4e2e723 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -6,9 +6,11 @@ package main import ( "context" "fmt" + "io" "net/http" "os" "os/signal" + "path/filepath" "syscall" "time" @@ -19,18 +21,15 @@ import ( ) func main() { - // 设置日志格式 - logrus.SetFormatter(&logrus.TextFormatter{ - FullTimestamp: true, - ForceColors: true, - }) - // 加载配置 cfg, err := config.LoadConfig() if err != nil { - logrus.Fatalf("❌ 配置加载失败: %v", err) + logrus.Fatalf("加载配置失败: %v", err) } + // 配置日志 + setupLogger(cfg) + // 显示启动信息 displayStartupInfo(cfg) @@ -96,3 +95,45 @@ func displayStartupInfo(cfg *config.Config) { config.DisplayConfig(cfg) logrus.Info("") } + +// setupLogger 配置日志系统 +func setupLogger(cfg *config.Config) { + // 设置日志级别 + level, err := logrus.ParseLevel(cfg.Log.Level) + if err != nil { + logrus.Warnf("无效的日志级别 '%s',使用默认级别 info", cfg.Log.Level) + level = logrus.InfoLevel + } + logrus.SetLevel(level) + + // 设置日志格式 + switch cfg.Log.Format { + case "json": + logrus.SetFormatter(&logrus.JSONFormatter{ + TimestampFormat: "2006-01-02 15:04:05", + }) + default: + logrus.SetFormatter(&logrus.TextFormatter{ + FullTimestamp: true, + ForceColors: true, + TimestampFormat: "2006-01-02 15:04:05", + }) + } + + // 配置文件日志 + if cfg.Log.EnableFile { + // 创建日志目录 + if err := os.MkdirAll(filepath.Dir(cfg.Log.FilePath), 0755); err != nil { + logrus.Warnf("创建日志目录失败: %v", err) + } else { + // 打开日志文件 + file, err := os.OpenFile(cfg.Log.FilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + logrus.Warnf("打开日志文件失败: %v", err) + } else { + // 同时输出到控制台和文件 + logrus.SetOutput(io.MultiWriter(os.Stdout, file)) + } + } + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 1c0663c..b3217be 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -74,6 +74,14 @@ type PerformanceConfig struct { BufferSize int `json:"bufferSize"` } +// LogConfig 日志配置 +type LogConfig struct { + Level string `json:"level"` // debug, info, warn, error + Format string `json:"format"` // text, json + EnableFile bool `json:"enableFile"` // 是否启用文件日志 + FilePath string `json:"filePath"` // 日志文件路径 +} + // Config 应用配置 type Config struct { Server ServerConfig `json:"server"` @@ -82,6 +90,7 @@ type Config struct { Auth AuthConfig `json:"auth"` CORS CORSConfig `json:"cors"` Performance PerformanceConfig `json:"performance"` + Log LogConfig `json:"log"` } // Global config instance @@ -123,6 +132,12 @@ func LoadConfig() (*Config, error) { DisableCompression: parseBoolean(os.Getenv("DISABLE_COMPRESSION"), true), BufferSize: parseInteger(os.Getenv("BUFFER_SIZE"), 32*1024), }, + Log: LogConfig{ + Level: getEnvOrDefault("LOG_LEVEL", "info"), + Format: getEnvOrDefault("LOG_FORMAT", "text"), + EnableFile: parseBoolean(os.Getenv("LOG_ENABLE_FILE"), false), + FilePath: getEnvOrDefault("LOG_FILE_PATH", "logs/app.log"), + }, } // 验证配置 @@ -205,6 +220,19 @@ func DisplayConfig(config *Config) { } logrus.Infof(" CORS: %s", corsStatus) logrus.Infof(" 连接池: %d/%d", config.Performance.MaxSockets, config.Performance.MaxFreeSockets) + + keepAliveStatus := "已启用" + if !config.Performance.EnableKeepAlive { + keepAliveStatus = "已禁用" + } + logrus.Infof(" Keep-Alive: %s", keepAliveStatus) + + compressionStatus := "已启用" + if config.Performance.DisableCompression { + compressionStatus = "已禁用" + } + logrus.Infof(" 压缩: %s", compressionStatus) + logrus.Infof(" 缓冲区大小: %d bytes", config.Performance.BufferSize) } // 辅助函数 diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 2035462..bc46ba0 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -51,10 +51,15 @@ func NewProxyServer() (*ProxyServer, error) { IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, - DisableCompression: true, // 禁用压缩以减少CPU开销,让上游处理 + DisableCompression: config.AppConfig.Performance.DisableCompression, ForceAttemptHTTP2: true, - WriteBufferSize: 32 * 1024, // 32KB写缓冲 - ReadBufferSize: 32 * 1024, // 32KB读缓冲 + WriteBufferSize: config.AppConfig.Performance.BufferSize, + ReadBufferSize: config.AppConfig.Performance.BufferSize, + } + + // 配置 Keep-Alive + if !config.AppConfig.Performance.EnableKeepAlive { + transport.DisableKeepAlives = true } httpClient := &http.Client{ diff --git a/scripts/validate_config.go b/scripts/validate_config.go new file mode 100644 index 0000000..7aacfc0 --- /dev/null +++ b/scripts/validate_config.go @@ -0,0 +1,116 @@ +// 配置验证脚本 +package main + +import ( + "fmt" + "reflect" + + "github.com/joho/godotenv" + "github.com/sirupsen/logrus" + + "gpt-load/internal/config" +) + +func main() { + // 加载测试配置 + if err := godotenv.Load("test_config.env"); err != nil { + logrus.Warnf("无法加载测试配置文件: %v", err) + } + + // 加载配置 + cfg, err := config.LoadConfig() + if err != nil { + logrus.Fatalf("配置加载失败: %v", err) + } + + fmt.Println("🔍 配置验证报告") + fmt.Println("=" * 50) + + // 验证服务器配置 + fmt.Printf("📡 服务器配置:\n") + fmt.Printf(" Host: %s\n", cfg.Server.Host) + fmt.Printf(" Port: %d\n", cfg.Server.Port) + fmt.Println() + + // 验证密钥配置 + fmt.Printf("🔑 密钥配置:\n") + fmt.Printf(" 文件路径: %s\n", cfg.Keys.FilePath) + fmt.Printf(" 起始索引: %d\n", cfg.Keys.StartIndex) + fmt.Printf(" 黑名单阈值: %d\n", cfg.Keys.BlacklistThreshold) + fmt.Println() + + // 验证 OpenAI 配置 + fmt.Printf("🤖 OpenAI 配置:\n") + fmt.Printf(" Base URL: %s\n", cfg.OpenAI.BaseURL) + fmt.Printf(" 超时时间: %dms\n", cfg.OpenAI.Timeout) + fmt.Println() + + // 验证认证配置 + fmt.Printf("🔐 认证配置:\n") + fmt.Printf(" 启用状态: %t\n", cfg.Auth.Enabled) + if cfg.Auth.Enabled { + fmt.Printf(" 密钥长度: %d\n", len(cfg.Auth.Key)) + } + fmt.Println() + + // 验证 CORS 配置 + fmt.Printf("🌐 CORS 配置:\n") + fmt.Printf(" 启用状态: %t\n", cfg.CORS.Enabled) + fmt.Printf(" 允许来源: %v\n", cfg.CORS.AllowedOrigins) + fmt.Println() + + // 验证性能配置 + fmt.Printf("⚡ 性能配置:\n") + fmt.Printf(" 最大连接数: %d\n", cfg.Performance.MaxSockets) + fmt.Printf(" 最大空闲连接数: %d\n", cfg.Performance.MaxFreeSockets) + fmt.Printf(" Keep-Alive: %t\n", cfg.Performance.EnableKeepAlive) + fmt.Printf(" 禁用压缩: %t\n", cfg.Performance.DisableCompression) + fmt.Printf(" 缓冲区大小: %d bytes\n", cfg.Performance.BufferSize) + fmt.Println() + + // 验证日志配置 + fmt.Printf("📝 日志配置:\n") + fmt.Printf(" 日志级别: %s\n", cfg.Log.Level) + fmt.Printf(" 日志格式: %s\n", cfg.Log.Format) + fmt.Printf(" 文件日志: %t\n", cfg.Log.EnableFile) + if cfg.Log.EnableFile { + fmt.Printf(" 文件路径: %s\n", cfg.Log.FilePath) + } + fmt.Println() + + // 检查配置完整性 + fmt.Printf("✅ 配置完整性检查:\n") + checkConfigCompleteness(cfg) + + fmt.Println("🎉 配置验证完成!") +} + +func checkConfigCompleteness(cfg *config.Config) { + v := reflect.ValueOf(cfg).Elem() + t := reflect.TypeOf(cfg).Elem() + + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + fieldType := t.Field(i) + + if field.Kind() == reflect.Struct { + checkStruct(field, fieldType.Name) + } + } +} + +func checkStruct(v reflect.Value, name string) { + t := v.Type() + + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + fieldType := t.Field(i) + + // 检查字段是否为零值 + if field.IsZero() && fieldType.Name != "Enabled" { + fmt.Printf(" ⚠️ %s.%s 为零值\n", name, fieldType.Name) + } else { + fmt.Printf(" ✅ %s.%s 已配置\n", name, fieldType.Name) + } + } +}