From c600519fa456f60bd7477334f6bfbf931930dc33 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Tue, 9 Dec 2025 17:16:30 +0800 Subject: [PATCH] refactor(logging): replace log.Fatalf with log.Errorf and add error handling paths --- cmd/server/main.go | 51 ++++++++++++------- .../api/handlers/management/auth_files.go | 39 ++++++++------ internal/auth/gemini/gemini_auth.go | 9 +++- internal/cmd/login.go | 21 ++++---- internal/cmd/run.go | 5 +- internal/cmd/vertex_import.go | 12 ++--- 6 files changed, 85 insertions(+), 52 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index bbf500e7..aec51ab8 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -139,7 +139,8 @@ func main() { wd, err := os.Getwd() if err != nil { - log.Fatalf("failed to get working directory: %v", err) + log.Errorf("failed to get working directory: %v", err) + return } // Load environment variables from .env if present. @@ -233,13 +234,15 @@ func main() { }) cancel() if err != nil { - log.Fatalf("failed to initialize postgres token store: %v", err) + log.Errorf("failed to initialize postgres token store: %v", err) + return } examplePath := filepath.Join(wd, "config.example.yaml") ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) if errBootstrap := pgStoreInst.Bootstrap(ctx, examplePath); errBootstrap != nil { cancel() - log.Fatalf("failed to bootstrap postgres-backed config: %v", errBootstrap) + log.Errorf("failed to bootstrap postgres-backed config: %v", errBootstrap) + return } cancel() configFilePath = pgStoreInst.ConfigPath() @@ -262,7 +265,8 @@ func main() { if strings.Contains(resolvedEndpoint, "://") { parsed, errParse := url.Parse(resolvedEndpoint) if errParse != nil { - log.Fatalf("failed to parse object store endpoint %q: %v", objectStoreEndpoint, errParse) + log.Errorf("failed to parse object store endpoint %q: %v", objectStoreEndpoint, errParse) + return } switch strings.ToLower(parsed.Scheme) { case "http": @@ -270,10 +274,12 @@ func main() { case "https": useSSL = true default: - log.Fatalf("unsupported object store scheme %q (only http and https are allowed)", parsed.Scheme) + log.Errorf("unsupported object store scheme %q (only http and https are allowed)", parsed.Scheme) + return } if parsed.Host == "" { - log.Fatalf("object store endpoint %q is missing host information", objectStoreEndpoint) + log.Errorf("object store endpoint %q is missing host information", objectStoreEndpoint) + return } resolvedEndpoint = parsed.Host if parsed.Path != "" && parsed.Path != "/" { @@ -292,13 +298,15 @@ func main() { } objectStoreInst, err = store.NewObjectTokenStore(objCfg) if err != nil { - log.Fatalf("failed to initialize object token store: %v", err) + log.Errorf("failed to initialize object token store: %v", err) + return } examplePath := filepath.Join(wd, "config.example.yaml") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) if errBootstrap := objectStoreInst.Bootstrap(ctx, examplePath); errBootstrap != nil { cancel() - log.Fatalf("failed to bootstrap object-backed config: %v", errBootstrap) + log.Errorf("failed to bootstrap object-backed config: %v", errBootstrap) + return } cancel() configFilePath = objectStoreInst.ConfigPath() @@ -323,7 +331,8 @@ func main() { gitStoreInst = store.NewGitTokenStore(gitStoreRemoteURL, gitStoreUser, gitStorePassword) gitStoreInst.SetBaseDir(authDir) if errRepo := gitStoreInst.EnsureRepository(); errRepo != nil { - log.Fatalf("failed to prepare git token store: %v", errRepo) + log.Errorf("failed to prepare git token store: %v", errRepo) + return } configFilePath = gitStoreInst.ConfigPath() if configFilePath == "" { @@ -332,17 +341,21 @@ func main() { if _, statErr := os.Stat(configFilePath); errors.Is(statErr, fs.ErrNotExist) { examplePath := filepath.Join(wd, "config.example.yaml") if _, errExample := os.Stat(examplePath); errExample != nil { - log.Fatalf("failed to find template config file: %v", errExample) + log.Errorf("failed to find template config file: %v", errExample) + return } if errCopy := misc.CopyConfigTemplate(examplePath, configFilePath); errCopy != nil { - log.Fatalf("failed to bootstrap git-backed config: %v", errCopy) + log.Errorf("failed to bootstrap git-backed config: %v", errCopy) + return } if errCommit := gitStoreInst.PersistConfig(context.Background()); errCommit != nil { - log.Fatalf("failed to commit initial git-backed config: %v", errCommit) + log.Errorf("failed to commit initial git-backed config: %v", errCommit) + return } log.Infof("git-backed config initialized from template: %s", configFilePath) } else if statErr != nil { - log.Fatalf("failed to inspect git-backed config: %v", statErr) + log.Errorf("failed to inspect git-backed config: %v", statErr) + return } cfg, err = config.LoadConfigOptional(configFilePath, isCloudDeploy) if err == nil { @@ -355,13 +368,15 @@ func main() { } else { wd, err = os.Getwd() if err != nil { - log.Fatalf("failed to get working directory: %v", err) + log.Errorf("failed to get working directory: %v", err) + return } configFilePath = filepath.Join(wd, "config.yaml") cfg, err = config.LoadConfigOptional(configFilePath, isCloudDeploy) } if err != nil { - log.Fatalf("failed to load config: %v", err) + log.Errorf("failed to load config: %v", err) + return } if cfg == nil { cfg = &config.Config{} @@ -391,7 +406,8 @@ func main() { coreauth.SetQuotaCooldownDisabled(cfg.DisableCooling) if err = logging.ConfigureLogOutput(cfg.LoggingToFile); err != nil { - log.Fatalf("failed to configure log output: %v", err) + log.Errorf("failed to configure log output: %v", err) + return } log.Infof("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s", buildinfo.Version, buildinfo.Commit, buildinfo.BuildDate) @@ -400,7 +416,8 @@ func main() { util.SetLogLevel(cfg) if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(cfg.AuthDir); errResolveAuthDir != nil { - log.Fatalf("failed to resolve auth directory: %v", errResolveAuthDir) + log.Errorf("failed to resolve auth directory: %v", errResolveAuthDir) + return } else { cfg.AuthDir = resolvedAuthDir } diff --git a/internal/api/handlers/management/auth_files.go b/internal/api/handlers/management/auth_files.go index 6f77fda9..e626af47 100644 --- a/internal/api/handlers/management/auth_files.go +++ b/internal/api/handlers/management/auth_files.go @@ -713,14 +713,16 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { // Generate PKCE codes pkceCodes, err := claude.GeneratePKCECodes() if err != nil { - log.Fatalf("Failed to generate PKCE codes: %v", err) + log.Errorf("Failed to generate PKCE codes: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate PKCE codes"}) return } // Generate random state parameter state, err := misc.GenerateRandomState() if err != nil { - log.Fatalf("Failed to generate state parameter: %v", err) + log.Errorf("Failed to generate state parameter: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate state parameter"}) return } @@ -730,7 +732,8 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { // Generate authorization URL (then override redirect_uri to reuse server port) authURL, state, err := anthropicAuth.GenerateAuthURL(state, pkceCodes) if err != nil { - log.Fatalf("Failed to generate authorization URL: %v", err) + log.Errorf("Failed to generate authorization URL: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate authorization url"}) return } @@ -872,7 +875,7 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { } savedPath, errSave := h.saveTokenRecord(ctx, record) if errSave != nil { - log.Fatalf("Failed to save authentication tokens: %v", errSave) + log.Errorf("Failed to save authentication tokens: %v", errSave) oauthStatus[state] = "Failed to save authentication tokens" return } @@ -1045,7 +1048,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { gemAuth := geminiAuth.NewGeminiAuth() gemClient, errGetClient := gemAuth.GetAuthenticatedClient(ctx, &ts, h.cfg, true) if errGetClient != nil { - log.Fatalf("failed to get authenticated client: %v", errGetClient) + log.Errorf("failed to get authenticated client: %v", errGetClient) oauthStatus[state] = "Failed to get authenticated client" return } @@ -1110,7 +1113,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { } savedPath, errSave := h.saveTokenRecord(ctx, record) if errSave != nil { - log.Fatalf("Failed to save token to file: %v", errSave) + log.Errorf("Failed to save token to file: %v", errSave) oauthStatus[state] = "Failed to save token to file" return } @@ -1131,14 +1134,16 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { // Generate PKCE codes pkceCodes, err := codex.GeneratePKCECodes() if err != nil { - log.Fatalf("Failed to generate PKCE codes: %v", err) + log.Errorf("Failed to generate PKCE codes: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate PKCE codes"}) return } // Generate random state parameter state, err := misc.GenerateRandomState() if err != nil { - log.Fatalf("Failed to generate state parameter: %v", err) + log.Errorf("Failed to generate state parameter: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate state parameter"}) return } @@ -1148,7 +1153,8 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { // Generate authorization URL authURL, err := openaiAuth.GenerateAuthURL(state, pkceCodes) if err != nil { - log.Fatalf("Failed to generate authorization URL: %v", err) + log.Errorf("Failed to generate authorization URL: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate authorization url"}) return } @@ -1283,7 +1289,7 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { savedPath, errSave := h.saveTokenRecord(ctx, record) if errSave != nil { oauthStatus[state] = "Failed to save authentication tokens" - log.Fatalf("Failed to save authentication tokens: %v", errSave) + log.Errorf("Failed to save authentication tokens: %v", errSave) return } fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) @@ -1318,7 +1324,8 @@ func (h *Handler) RequestAntigravityToken(c *gin.Context) { state, errState := misc.GenerateRandomState() if errState != nil { - log.Fatalf("Failed to generate state parameter: %v", errState) + log.Errorf("Failed to generate state parameter: %v", errState) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate state parameter"}) return } @@ -1514,7 +1521,7 @@ func (h *Handler) RequestAntigravityToken(c *gin.Context) { } savedPath, errSave := h.saveTokenRecord(ctx, record) if errSave != nil { - log.Fatalf("Failed to save token to file: %v", errSave) + log.Errorf("Failed to save token to file: %v", errSave) oauthStatus[state] = "Failed to save token to file" return } @@ -1543,7 +1550,8 @@ func (h *Handler) RequestQwenToken(c *gin.Context) { // Generate authorization URL deviceFlow, err := qwenAuth.InitiateDeviceFlow(ctx) if err != nil { - log.Fatalf("Failed to generate authorization URL: %v", err) + log.Errorf("Failed to generate authorization URL: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate authorization url"}) return } authURL := deviceFlow.VerificationURIComplete @@ -1570,7 +1578,7 @@ func (h *Handler) RequestQwenToken(c *gin.Context) { } savedPath, errSave := h.saveTokenRecord(ctx, record) if errSave != nil { - log.Fatalf("Failed to save authentication tokens: %v", errSave) + log.Errorf("Failed to save authentication tokens: %v", errSave) oauthStatus[state] = "Failed to save authentication tokens" return } @@ -1674,7 +1682,7 @@ func (h *Handler) RequestIFlowToken(c *gin.Context) { savedPath, errSave := h.saveTokenRecord(ctx, record) if errSave != nil { oauthStatus[state] = "Failed to save authentication tokens" - log.Fatalf("Failed to save authentication tokens: %v", errSave) + log.Errorf("Failed to save authentication tokens: %v", errSave) return } @@ -2103,6 +2111,7 @@ func checkCloudAPIIsEnabled(ctx context.Context, httpClient *http.Client, projec continue } } + _ = resp.Body.Close() return false, fmt.Errorf("project activation required: %s", errMessage) } return true, nil diff --git a/internal/auth/gemini/gemini_auth.go b/internal/auth/gemini/gemini_auth.go index a6ac4507..f173c95f 100644 --- a/internal/auth/gemini/gemini_auth.go +++ b/internal/auth/gemini/gemini_auth.go @@ -76,7 +76,8 @@ func (g *GeminiAuth) GetAuthenticatedClient(ctx context.Context, ts *GeminiToken auth := &proxy.Auth{User: username, Password: password} dialer, errSOCKS5 := proxy.SOCKS5("tcp", proxyURL.Host, auth, proxy.Direct) if errSOCKS5 != nil { - log.Fatalf("create SOCKS5 dialer failed: %v", errSOCKS5) + log.Errorf("create SOCKS5 dialer failed: %v", errSOCKS5) + return nil, fmt.Errorf("create SOCKS5 dialer failed: %w", errSOCKS5) } transport = &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { @@ -238,7 +239,11 @@ func (g *GeminiAuth) getTokenFromWeb(ctx context.Context, config *oauth2.Config, // Start the server in a goroutine. go func() { if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { - log.Fatalf("ListenAndServe(): %v", err) + log.Errorf("ListenAndServe(): %v", err) + select { + case errChan <- err: + default: + } } }() diff --git a/internal/cmd/login.go b/internal/cmd/login.go index 5e5159aa..de01cec5 100644 --- a/internal/cmd/login.go +++ b/internal/cmd/login.go @@ -65,20 +65,20 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) { authenticator := sdkAuth.NewGeminiAuthenticator() record, errLogin := authenticator.Login(ctx, cfg, loginOpts) if errLogin != nil { - log.Fatalf("Gemini authentication failed: %v", errLogin) + log.Errorf("Gemini authentication failed: %v", errLogin) return } storage, okStorage := record.Storage.(*gemini.GeminiTokenStorage) if !okStorage || storage == nil { - log.Fatal("Gemini authentication failed: unsupported token storage") + log.Error("Gemini authentication failed: unsupported token storage") return } geminiAuth := gemini.NewGeminiAuth() httpClient, errClient := geminiAuth.GetAuthenticatedClient(ctx, storage, cfg, options.NoBrowser) if errClient != nil { - log.Fatalf("Gemini authentication failed: %v", errClient) + log.Errorf("Gemini authentication failed: %v", errClient) return } @@ -86,7 +86,7 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) { projects, errProjects := fetchGCPProjects(ctx, httpClient) if errProjects != nil { - log.Fatalf("Failed to get project list: %v", errProjects) + log.Errorf("Failed to get project list: %v", errProjects) return } @@ -98,11 +98,11 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) { selectedProjectID := promptForProjectSelection(projects, strings.TrimSpace(projectID), promptFn) projectSelections, errSelection := resolveProjectSelections(selectedProjectID, projects) if errSelection != nil { - log.Fatalf("Invalid project selection: %v", errSelection) + log.Errorf("Invalid project selection: %v", errSelection) return } if len(projectSelections) == 0 { - log.Fatal("No project selected; aborting login.") + log.Error("No project selected; aborting login.") return } @@ -116,7 +116,7 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) { showProjectSelectionHelp(storage.Email, projects) return } - log.Fatalf("Failed to complete user setup: %v", errSetup) + log.Errorf("Failed to complete user setup: %v", errSetup) return } finalID := strings.TrimSpace(storage.ProjectID) @@ -133,11 +133,11 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) { for _, pid := range activatedProjects { isChecked, errCheck := checkCloudAPIIsEnabled(ctx, httpClient, pid) if errCheck != nil { - log.Fatalf("Failed to check if Cloud AI API is enabled for %s: %v", pid, errCheck) + log.Errorf("Failed to check if Cloud AI API is enabled for %s: %v", pid, errCheck) return } if !isChecked { - log.Fatalf("Failed to check if Cloud AI API is enabled for project %s. If you encounter an error message, please create an issue.", pid) + log.Errorf("Failed to check if Cloud AI API is enabled for project %s. If you encounter an error message, please create an issue.", pid) return } } @@ -153,7 +153,7 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) { savedPath, errSave := store.Save(ctx, record) if errSave != nil { - log.Fatalf("Failed to save token to file: %v", errSave) + log.Errorf("Failed to save token to file: %v", errSave) return } @@ -555,6 +555,7 @@ func checkCloudAPIIsEnabled(ctx context.Context, httpClient *http.Client, projec continue } } + _ = resp.Body.Close() return false, fmt.Errorf("project activation required: %s", errMessage) } return true, nil diff --git a/internal/cmd/run.go b/internal/cmd/run.go index e2f6ee80..1e968126 100644 --- a/internal/cmd/run.go +++ b/internal/cmd/run.go @@ -45,12 +45,13 @@ func StartService(cfg *config.Config, configPath string, localPassword string) { service, err := builder.Build() if err != nil { - log.Fatalf("failed to build proxy service: %v", err) + log.Errorf("failed to build proxy service: %v", err) + return } err = service.Run(runCtx) if err != nil && !errors.Is(err, context.Canceled) { - log.Fatalf("proxy service exited with error: %v", err) + log.Errorf("proxy service exited with error: %v", err) } } diff --git a/internal/cmd/vertex_import.go b/internal/cmd/vertex_import.go index ebb32d0c..32d782d8 100644 --- a/internal/cmd/vertex_import.go +++ b/internal/cmd/vertex_import.go @@ -29,30 +29,30 @@ func DoVertexImport(cfg *config.Config, keyPath string) { } rawPath := strings.TrimSpace(keyPath) if rawPath == "" { - log.Fatalf("vertex-import: missing service account key path") + log.Errorf("vertex-import: missing service account key path") return } data, errRead := os.ReadFile(rawPath) if errRead != nil { - log.Fatalf("vertex-import: read file failed: %v", errRead) + log.Errorf("vertex-import: read file failed: %v", errRead) return } var sa map[string]any if errUnmarshal := json.Unmarshal(data, &sa); errUnmarshal != nil { - log.Fatalf("vertex-import: invalid service account json: %v", errUnmarshal) + log.Errorf("vertex-import: invalid service account json: %v", errUnmarshal) return } // Validate and normalize private_key before saving normalizedSA, errFix := vertex.NormalizeServiceAccountMap(sa) if errFix != nil { - log.Fatalf("vertex-import: %v", errFix) + log.Errorf("vertex-import: %v", errFix) return } sa = normalizedSA email, _ := sa["client_email"].(string) projectID, _ := sa["project_id"].(string) if strings.TrimSpace(projectID) == "" { - log.Fatalf("vertex-import: project_id missing in service account json") + log.Errorf("vertex-import: project_id missing in service account json") return } if strings.TrimSpace(email) == "" { @@ -92,7 +92,7 @@ func DoVertexImport(cfg *config.Config, keyPath string) { } path, errSave := store.Save(context.Background(), record) if errSave != nil { - log.Fatalf("vertex-import: save credential failed: %v", errSave) + log.Errorf("vertex-import: save credential failed: %v", errSave) return } fmt.Printf("Vertex credentials imported: %s\n", path)