From 7f8960823bab5fd5ea0651eabe7de1498c6404a7 Mon Sep 17 00:00:00 2001 From: "tao.chen" <93983997+taochen-ct@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:48:35 +0800 Subject: [PATCH] =?UTF-8?q?config:=20GIN=5FMODE=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E9=85=8D=E7=BD=AE=20+=20Lo?= =?UTF-8?q?ad()=20=E8=87=AA=E5=8A=A8=E5=8A=A0=E8=BD=BD=20.env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main.go 硬编码 gin.ReleaseMode 改成读 cfg.GinMode(默认 release), 方便生产/调试切换 debug/release/test。Load() 开头先调 loadDotenv (".env"): 缺文件静默跳过,shell env 优先于 .env,跳过注释/空行/ 无 '=' 行并 slog.Warn。仅用标准库,不引新依赖。 验证: - go build / go vet / go test 全部通过 - shell env 覆盖 .env (LISTEN_ADDR=:19090 覆盖 .env 的 :18080) - 仅 .env 时正确加载 (监听 18080, GIN_MODE=debug 输出 [GIN-debug]) - 无 .env 且无 shell env 时清晰报错 ADMIN_TOKENS is not set Co-Authored-By: Claude --- .env.example | 3 ++ internal/config/config.go | 79 ++++++++++++++++++++++++++++++++++++++- main.go | 2 +- 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 9829512..4abd333 100644 --- a/.env.example +++ b/.env.example @@ -31,6 +31,9 @@ LOG_LEVEL=info # Log format: text (terminal) or json (log file; default: text) LOG_FORMAT=text +# Gin mode: debug, release, test (default: release) +GIN_MODE=release + # Analyzer heuristic thresholds (defaults shown) ANALYZER_DATA_SKEW_RATIO=3.0 ANALYZER_GC_PRESSURE_RATIO=0.1 diff --git a/internal/config/config.go b/internal/config/config.go index 6319fc1..22e5737 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,7 +2,9 @@ package config import ( + "bufio" "fmt" + "log/slog" "os" "path/filepath" "strconv" @@ -23,6 +25,7 @@ type Config struct { LogDir string LogLevel string LogFormat string + GinMode string AnalyzerDataSkewRatio float64 AnalyzerGCPressureRatio float64 AnalyzerBottleneckShuffleGB float64 @@ -31,6 +34,10 @@ type Config struct { // Load reads configuration from environment variables and returns a populated Config. // Required variables (ADMIN_TOKENS, AGENT_TOKEN) produce clear errors when missing. func Load() (*Config, error) { + if err := loadDotenv(".env"); err != nil { + return nil, err + } + cfg := &Config{ ListenAddr: ":8080", DataDir: "./data", @@ -40,6 +47,7 @@ func Load() (*Config, error) { LogDir: "./data/logs", LogLevel: "info", LogFormat: "text", + GinMode: "release", AnalyzerDataSkewRatio: 3.0, AnalyzerGCPressureRatio: 0.1, AnalyzerBottleneckShuffleGB: 50.0, @@ -75,6 +83,7 @@ func Load() (*Config, error) { cfg.LogDir = envString("LOG_DIR", cfg.LogDir) cfg.LogLevel = envString("LOG_LEVEL", cfg.LogLevel) cfg.LogFormat = envString("LOG_FORMAT", cfg.LogFormat) + cfg.GinMode = envString("GIN_MODE", cfg.GinMode) cfg.AnalyzerDataSkewRatio, err = parseFloat64("ANALYZER_DATA_SKEW_RATIO", cfg.AnalyzerDataSkewRatio) if err != nil { @@ -97,6 +106,9 @@ func Load() (*Config, error) { if err := validateLogFormat(cfg.LogFormat); err != nil { return nil, err } + if err := validateGinMode(cfg.GinMode); err != nil { + return nil, err + } cfg.SQLitePath = filepath.Join(cfg.DataDir, "spark-mcp.db") @@ -192,6 +204,70 @@ func validateLogFormat(format string) error { return fmt.Errorf("config: invalid LOG_FORMAT %q, want text/json", format) } +func validateGinMode(mode string) error { + switch mode { + case "debug", "release", "test": + return nil + } + return fmt.Errorf("config: invalid GIN_MODE %q, want debug/release/test", mode) +} + +// loadDotenv reads KEY=VALUE pairs from path and sets them via os.Setenv only +// when the variable is not already defined. Missing files are ignored. +func loadDotenv(path string) error { + f, err := os.Open(path) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf("config: failed to open %s: %w", path, err) + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + eq := strings.Index(line, "=") + if eq < 0 { + slog.Warn("config: .env line without '=', skipping", "line", line) + continue + } + + key := strings.TrimSpace(line[:eq]) + value := strings.TrimSpace(line[eq+1:]) + value = dequote(value) + + if key == "" { + slog.Warn("config: .env line with empty key, skipping", "line", line) + continue + } + + if os.Getenv(key) == "" { + if err := os.Setenv(key, value); err != nil { + return fmt.Errorf("config: failed to set env %s: %w", key, err) + } + } + } + + if err := scanner.Err(); err != nil { + return fmt.Errorf("config: failed to read %s: %w", path, err) + } + return nil +} + +func dequote(s string) string { + if len(s) >= 2 { + if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') { + return s[1 : len(s)-1] + } + } + return s +} + // String returns a human-readable representation of the configuration. // Sensitive values (AdminTokens and AgentToken) are summarized, not printed. func (c *Config) String() string { @@ -206,7 +282,7 @@ func (c *Config) String() string { "ListenAddr=%s DataDir=%s SQLitePath=%s AdminTokens=%s AgentToken=%s "+ "HTTPClientTimeout=%s MaxResponseBytes=%d SparkSubmitTimeout=%s "+ "LogDir=%s LogLevel=%s LogFormat=%s AnalyzerDataSkewRatio=%.1f "+ - "AnalyzerGCPressureRatio=%.1f AnalyzerBottleneckShuffleGB=%.1f", + "AnalyzerGCPressureRatio=%.1f AnalyzerBottleneckShuffleGB=%.1f GinMode=%s", c.ListenAddr, c.DataDir, c.SQLitePath, @@ -221,5 +297,6 @@ func (c *Config) String() string { c.AnalyzerDataSkewRatio, c.AnalyzerGCPressureRatio, c.AnalyzerBottleneckShuffleGB, + c.GinMode, ) } diff --git a/main.go b/main.go index 9236e7c..ef0e03d 100644 --- a/main.go +++ b/main.go @@ -65,7 +65,7 @@ func run() error { defer db.Close() logger.Info("storage.open", "path", cfg.SQLitePath) - gin.SetMode(gin.ReleaseMode) + gin.SetMode(cfg.GinMode) r := gin.New() r.Use(gin.Recovery())