From cf734f7e7b2b2269a944f6ab89dfe9606dd3f2aa Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Fri, 26 Sep 2025 00:54:52 +0800 Subject: [PATCH] feat(logging): introduce centralized logging with custom format and Gin integration - Implemented a global logger with structured formatting for consistent log output. - Added support for rotating log files using Lumberjack. - Integrated new logging functionality with Gin HTTP server for unified log handling. - Replaced direct `log.Info` calls with `fmt.Printf` in non-critical paths to simplify core functionality. --- cmd/server/main.go | 95 ++------------ config.example.yaml | 3 + .../api/handlers/management/auth_files.go | 40 +++--- internal/api/server.go | 10 +- internal/auth/gemini/gemini_auth.go | 18 +-- internal/auth/qwen/qwen_auth.go | 4 +- internal/browser/browser.go | 2 +- internal/cmd/login.go | 4 +- internal/config/config.go | 4 + internal/logging/global_logger.go | 117 ++++++++++++++++++ internal/misc/credentials.go | 3 +- internal/provider/gemini-web/client.go | 4 +- internal/provider/gemini-web/media.go | 2 +- internal/util/ssh_helper.go | 2 +- internal/watcher/watcher.go | 4 +- sdk/auth/claude.go | 14 +-- sdk/auth/codex.go | 14 +-- sdk/auth/gemini.go | 3 +- sdk/auth/qwen.go | 12 +- sdk/cliproxy/service.go | 2 +- 20 files changed, 209 insertions(+), 148 deletions(-) create mode 100644 internal/logging/global_logger.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 22b43d0a..06c96999 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -4,106 +4,30 @@ package main import ( - "bytes" "flag" "fmt" - "io" "os" "path/filepath" "strings" - "github.com/gin-gonic/gin" "github.com/router-for-me/CLIProxyAPI/v6/internal/cmd" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" _ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator" "github.com/router-for-me/CLIProxyAPI/v6/internal/util" sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth" log "github.com/sirupsen/logrus" - "gopkg.in/natefinch/lumberjack.v2" ) var ( - Version = "dev" - Commit = "none" - BuildDate = "unknown" - logWriter *lumberjack.Logger - ginInfoWriter *io.PipeWriter - ginErrorWriter *io.PipeWriter + Version = "dev" + Commit = "none" + BuildDate = "unknown" ) -// LogFormatter defines a custom log format for logrus. -// This formatter adds timestamp, log level, and source location information -// to each log entry for better debugging and monitoring. -type LogFormatter struct { -} - -// Format renders a single log entry with custom formatting. -// It includes timestamp, log level, source file and line number, and the log message. -func (m *LogFormatter) Format(entry *log.Entry) ([]byte, error) { - var b *bytes.Buffer - if entry.Buffer != nil { - b = entry.Buffer - } else { - b = &bytes.Buffer{} - } - - timestamp := entry.Time.Format("2006-01-02 15:04:05") - var newLog string - // Ensure message doesn't carry trailing newlines; formatter appends one. - msg := strings.TrimRight(entry.Message, "\r\n") - // Customize the log format to include timestamp, level, caller file/line, and message. - newLog = fmt.Sprintf("[%s] [%s] [%s:%d] %s\n", timestamp, entry.Level, filepath.Base(entry.Caller.File), entry.Caller.Line, msg) - - b.WriteString(newLog) - return b.Bytes(), nil -} - -// init initializes the logger configuration. -// It sets up the custom log formatter, enables caller reporting, -// and configures the log output destination. +// init initializes the shared logger setup. func init() { - logDir := "logs" - if err := os.MkdirAll(logDir, 0755); err != nil { - _, _ = fmt.Fprintf(os.Stderr, "failed to create log directory: %v\n", err) - os.Exit(1) - } - - logWriter = &lumberjack.Logger{ - Filename: filepath.Join(logDir, "main.log"), - MaxSize: 10, - MaxBackups: 0, - MaxAge: 0, - Compress: false, - } - - log.SetOutput(logWriter) - // Enable reporting the caller function's file and line number. - log.SetReportCaller(true) - // Set the custom log formatter. - log.SetFormatter(&LogFormatter{}) - - ginInfoWriter = log.StandardLogger().Writer() - gin.DefaultWriter = ginInfoWriter - ginErrorWriter = log.StandardLogger().WriterLevel(log.ErrorLevel) - gin.DefaultErrorWriter = ginErrorWriter - gin.DebugPrintFunc = func(format string, values ...interface{}) { - // Trim trailing newlines from Gin's formatted messages to avoid blank lines. - // Gin's debug prints usually include a trailing "\n"; our formatter also appends one. - // Removing it here ensures a single newline per entry. - format = strings.TrimRight(format, "\r\n") - log.StandardLogger().Infof(format, values...) - } - log.RegisterExitHandler(func() { - if logWriter != nil { - _ = logWriter.Close() - } - if ginInfoWriter != nil { - _ = ginInfoWriter.Close() - } - if ginErrorWriter != nil { - _ = ginErrorWriter.Close() - } - }) + logging.SetupBaseLogger() } // main is the entry point of the application. @@ -111,7 +35,6 @@ func init() { // service based on the provided flags (login, codex-login, or server mode). func main() { fmt.Printf("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s\n", Version, Commit, BuildDate) - log.Infof("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s", Version, Commit, BuildDate) // Command-line flags to control the application's behavior. var login bool @@ -189,6 +112,12 @@ func main() { log.Fatalf("failed to load config: %v", err) } + if err = logging.ConfigureLogOutput(cfg.LoggingToFile); err != nil { + log.Fatalf("failed to configure log output: %v", err) + } + + log.Infof("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s", Version, Commit, BuildDate) + // Set the log level based on the configuration. util.SetLogLevel(cfg) diff --git a/config.example.yaml b/config.example.yaml index 3ec9f088..5ac25bc6 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -18,6 +18,9 @@ auth-dir: "~/.cli-proxy-api" # Enable debug logging debug: false +# When true, write application logs to rotating files instead of stdout +logging-to-file: true + # Proxy URL. Supports socks5/http/https protocols. Example: socks5://user:pass@192.168.1.1:1080/ proxy-url: "" diff --git a/internal/api/handlers/management/auth_files.go b/internal/api/handlers/management/auth_files.go index 6c90db2c..47501b8d 100644 --- a/internal/api/handlers/management/auth_files.go +++ b/internal/api/handlers/management/auth_files.go @@ -359,7 +359,7 @@ func (h *Handler) saveTokenRecord(ctx context.Context, record *sdkAuth.TokenReco func (h *Handler) RequestAnthropicToken(c *gin.Context) { ctx := context.Background() - log.Info("Initializing Claude authentication...") + fmt.Println("Initializing Claude authentication...") // Generate PKCE codes pkceCodes, err := claude.GeneratePKCECodes() @@ -407,7 +407,7 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { } } - log.Info("Waiting for authentication callback...") + fmt.Println("Waiting for authentication callback...") // Wait up to 5 minutes resultMap, errWait := waitForFile(waitFile, 5*time.Minute) if errWait != nil { @@ -509,11 +509,11 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) { return } - log.Infof("Authentication successful! Token saved to %s", savedPath) + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) if bundle.APIKey != "" { - log.Info("API key obtained and saved") + fmt.Println("API key obtained and saved") } - log.Info("You can now use Claude services through this CLI") + fmt.Println("You can now use Claude services through this CLI") delete(oauthStatus, state) }() @@ -527,7 +527,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { // Optional project ID from query projectID := c.Query("project_id") - log.Info("Initializing Google authentication...") + fmt.Println("Initializing Google authentication...") // OAuth2 configuration (mirrors internal/auth/gemini) conf := &oauth2.Config{ @@ -549,7 +549,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { go func() { // Wait for callback file written by server route waitFile := filepath.Join(h.cfg.AuthDir, fmt.Sprintf(".oauth-gemini-%s.oauth", state)) - log.Info("Waiting for authentication callback...") + fmt.Println("Waiting for authentication callback...") deadline := time.Now().Add(5 * time.Minute) var authCode string for { @@ -618,9 +618,9 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { email := gjson.GetBytes(bodyBytes, "email").String() if email != "" { - log.Infof("Authenticated user email: %s", email) + fmt.Printf("Authenticated user email: %s\n", email) } else { - log.Info("Failed to get user email from token") + fmt.Println("Failed to get user email from token") oauthStatus[state] = "Failed to get user email from token" } @@ -657,7 +657,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { oauthStatus[state] = "Failed to get authenticated client" return } - log.Info("Authentication successful.") + fmt.Println("Authentication successful.") record := &sdkAuth.TokenRecord{ Provider: "gemini", @@ -676,7 +676,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) { } delete(oauthStatus, state) - log.Infof("You can now use Gemini CLI services through this CLI; token saved to %s", savedPath) + fmt.Printf("You can now use Gemini CLI services through this CLI; token saved to %s\n", savedPath) }() oauthStatus[state] = "" @@ -737,14 +737,14 @@ func (h *Handler) CreateGeminiWebToken(c *gin.Context) { return } - log.Infof("Successfully saved Gemini Web token to: %s", savedPath) + fmt.Printf("Successfully saved Gemini Web token to: %s\n", savedPath) c.JSON(http.StatusOK, gin.H{"status": "ok", "file": filepath.Base(savedPath)}) } func (h *Handler) RequestCodexToken(c *gin.Context) { ctx := context.Background() - log.Info("Initializing Codex authentication...") + fmt.Println("Initializing Codex authentication...") // Generate PKCE codes pkceCodes, err := codex.GeneratePKCECodes() @@ -884,11 +884,11 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { log.Fatalf("Failed to save authentication tokens: %v", errSave) return } - log.Infof("Authentication successful! Token saved to %s", savedPath) + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) if bundle.APIKey != "" { - log.Info("API key obtained and saved") + fmt.Println("API key obtained and saved") } - log.Info("You can now use Codex services through this CLI") + fmt.Println("You can now use Codex services through this CLI") delete(oauthStatus, state) }() @@ -899,7 +899,7 @@ func (h *Handler) RequestCodexToken(c *gin.Context) { func (h *Handler) RequestQwenToken(c *gin.Context) { ctx := context.Background() - log.Info("Initializing Qwen authentication...") + fmt.Println("Initializing Qwen authentication...") state := fmt.Sprintf("gem-%d", time.Now().UnixNano()) // Initialize Qwen auth service @@ -914,7 +914,7 @@ func (h *Handler) RequestQwenToken(c *gin.Context) { authURL := deviceFlow.VerificationURIComplete go func() { - log.Info("Waiting for authentication...") + fmt.Println("Waiting for authentication...") tokenData, errPollForToken := qwenAuth.PollForToken(deviceFlow.DeviceCode, deviceFlow.CodeVerifier) if errPollForToken != nil { oauthStatus[state] = "Authentication failed" @@ -939,8 +939,8 @@ func (h *Handler) RequestQwenToken(c *gin.Context) { return } - log.Infof("Authentication successful! Token saved to %s", savedPath) - log.Info("You can now use Qwen services through this CLI") + fmt.Printf("Authentication successful! Token saved to %s\n", savedPath) + fmt.Println("You can now use Qwen services through this CLI") delete(oauthStatus, state) }() diff --git a/internal/api/server.go b/internal/api/server.go index 4cb54eac..30711882 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -452,6 +452,14 @@ func (s *Server) UpdateClients(cfg *config.Config) { log.Debugf("request logging updated from %t to %t", s.cfg.RequestLog, cfg.RequestLog) } + if s.cfg.LoggingToFile != cfg.LoggingToFile { + if err := logging.ConfigureLogOutput(cfg.LoggingToFile); err != nil { + log.Errorf("failed to reconfigure log output: %v", err) + } else { + log.Debugf("logging_to_file updated from %t to %t", s.cfg.LoggingToFile, cfg.LoggingToFile) + } + } + // Update log level dynamically when debug flag changes if s.cfg.Debug != cfg.Debug { util.SetLogLevel(cfg) @@ -477,7 +485,7 @@ func (s *Server) UpdateClients(cfg *config.Config) { } total := authFiles + glAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + openAICompatCount - log.Infof("server clients and configuration updated: %d clients (%d auth files + %d GL API keys + %d Claude API keys + %d Codex keys + %d OpenAI-compat)", + fmt.Printf("server clients and configuration updated: %d clients (%d auth files + %d GL API keys + %d Claude API keys + %d Codex keys + %d OpenAI-compat)\n", total, authFiles, glAPIKeyCount, diff --git a/internal/auth/gemini/gemini_auth.go b/internal/auth/gemini/gemini_auth.go index cfb943dd..a6ac4507 100644 --- a/internal/auth/gemini/gemini_auth.go +++ b/internal/auth/gemini/gemini_auth.go @@ -107,7 +107,7 @@ func (g *GeminiAuth) GetAuthenticatedClient(ctx context.Context, ts *GeminiToken // If no token is found in storage, initiate the web-based OAuth flow. if ts.Token == nil { - log.Info("Could not load token from file, starting OAuth flow.") + fmt.Printf("Could not load token from file, starting OAuth flow.\n") token, err = g.getTokenFromWeb(ctx, conf, noBrowser...) if err != nil { return nil, fmt.Errorf("failed to get token from web: %w", err) @@ -169,9 +169,9 @@ func (g *GeminiAuth) createTokenStorage(ctx context.Context, config *oauth2.Conf emailResult := gjson.GetBytes(bodyBytes, "email") if emailResult.Exists() && emailResult.Type == gjson.String { - log.Infof("Authenticated user email: %s", emailResult.String()) + fmt.Printf("Authenticated user email: %s\n", emailResult.String()) } else { - log.Info("Failed to get user email from token") + fmt.Println("Failed to get user email from token") } var ifToken map[string]any @@ -246,19 +246,19 @@ func (g *GeminiAuth) getTokenFromWeb(ctx context.Context, config *oauth2.Config, authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent")) if len(noBrowser) == 1 && !noBrowser[0] { - log.Info("Opening browser for authentication...") + fmt.Println("Opening browser for authentication...") // Check if browser is available if !browser.IsAvailable() { log.Warn("No browser available on this system") util.PrintSSHTunnelInstructions(8085) - log.Infof("Please manually open this URL in your browser:\n\n%s\n", authURL) + fmt.Printf("Please manually open this URL in your browser:\n\n%s\n", authURL) } else { if err := browser.OpenURL(authURL); err != nil { authErr := codex.NewAuthenticationError(codex.ErrBrowserOpenFailed, err) log.Warn(codex.GetUserFriendlyMessage(authErr)) util.PrintSSHTunnelInstructions(8085) - log.Infof("Please manually open this URL in your browser:\n\n%s\n", authURL) + fmt.Printf("Please manually open this URL in your browser:\n\n%s\n", authURL) // Log platform info for debugging platformInfo := browser.GetPlatformInfo() @@ -269,10 +269,10 @@ func (g *GeminiAuth) getTokenFromWeb(ctx context.Context, config *oauth2.Config, } } else { util.PrintSSHTunnelInstructions(8085) - log.Infof("Please open this URL in your browser:\n\n%s\n", authURL) + fmt.Printf("Please open this URL in your browser:\n\n%s\n", authURL) } - log.Info("Waiting for authentication callback...") + fmt.Println("Waiting for authentication callback...") // Wait for the authorization code or an error. var authCode string @@ -296,6 +296,6 @@ func (g *GeminiAuth) getTokenFromWeb(ctx context.Context, config *oauth2.Config, return nil, fmt.Errorf("failed to exchange token: %w", err) } - log.Info("Authentication successful.") + fmt.Println("Authentication successful.") return token, nil } diff --git a/internal/auth/qwen/qwen_auth.go b/internal/auth/qwen/qwen_auth.go index 94340644..9a554745 100644 --- a/internal/auth/qwen/qwen_auth.go +++ b/internal/auth/qwen/qwen_auth.go @@ -260,7 +260,7 @@ func (qa *QwenAuth) PollForToken(deviceCode, codeVerifier string) (*QwenTokenDat switch errorType { case "authorization_pending": // User has not yet approved the authorization request. Continue polling. - log.Infof("Polling attempt %d/%d...\n", attempt+1, maxAttempts) + fmt.Printf("Polling attempt %d/%d...\n\n", attempt+1, maxAttempts) time.Sleep(pollInterval) continue case "slow_down": @@ -269,7 +269,7 @@ func (qa *QwenAuth) PollForToken(deviceCode, codeVerifier string) (*QwenTokenDat if pollInterval > 10*time.Second { pollInterval = 10 * time.Second } - log.Infof("Server requested to slow down, increasing poll interval to %v\n", pollInterval) + fmt.Printf("Server requested to slow down, increasing poll interval to %v\n\n", pollInterval) time.Sleep(pollInterval) continue case "expired_token": diff --git a/internal/browser/browser.go b/internal/browser/browser.go index 85ab180d..b24dc5e1 100644 --- a/internal/browser/browser.go +++ b/internal/browser/browser.go @@ -21,7 +21,7 @@ import ( // Returns: // - An error if the URL cannot be opened, otherwise nil. func OpenURL(url string) error { - log.Infof("Attempting to open URL in browser: %s", url) + fmt.Printf("Attempting to open URL in browser: %s\n", url) // Try using the open-golang library first err := open.Run(url) diff --git a/internal/cmd/login.go b/internal/cmd/login.go index dd71afe9..95c13e25 100644 --- a/internal/cmd/login.go +++ b/internal/cmd/login.go @@ -62,8 +62,8 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) { } if savedPath != "" { - log.Infof("Authentication saved to %s", savedPath) + fmt.Printf("Authentication saved to %s\n", savedPath) } - log.Info("Gemini authentication successful!") + fmt.Println("Gemini authentication successful!") } diff --git a/internal/config/config.go b/internal/config/config.go index 7b09fe6d..5dcb7e08 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,9 @@ type Config struct { // Debug enables or disables debug-level logging and other debug features. Debug bool `yaml:"debug" json:"debug"` + // LoggingToFile controls whether application logs are written to rotating files or stdout. + LoggingToFile bool `yaml:"logging-to-file" json:"logging-to-file"` + // ProxyURL is the URL of an optional proxy server to use for outbound requests. ProxyURL string `yaml:"proxy-url" json:"proxy-url"` @@ -202,6 +205,7 @@ func LoadConfig(configFile string) (*Config, error) { // Unmarshal the YAML data into the Config struct. var config Config // Set defaults before unmarshal so that absent keys keep defaults. + config.LoggingToFile = true config.GeminiWeb.Context = true if err = yaml.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("failed to parse config file: %w", err) diff --git a/internal/logging/global_logger.go b/internal/logging/global_logger.go new file mode 100644 index 00000000..75505343 --- /dev/null +++ b/internal/logging/global_logger.go @@ -0,0 +1,117 @@ +package logging + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "gopkg.in/natefinch/lumberjack.v2" +) + +var ( + setupOnce sync.Once + writerMu sync.Mutex + logWriter *lumberjack.Logger + ginInfoWriter *io.PipeWriter + ginErrorWriter *io.PipeWriter +) + +// LogFormatter defines a custom log format for logrus. +// This formatter adds timestamp, level, and source location to each log entry. +type LogFormatter struct{} + +// Format renders a single log entry with custom formatting. +func (m *LogFormatter) Format(entry *log.Entry) ([]byte, error) { + var buffer *bytes.Buffer + if entry.Buffer != nil { + buffer = entry.Buffer + } else { + buffer = &bytes.Buffer{} + } + + timestamp := entry.Time.Format("2006-01-02 15:04:05") + message := strings.TrimRight(entry.Message, "\r\n") + formatted := fmt.Sprintf("[%s] [%s] [%s:%d] %s\n", timestamp, entry.Level, filepath.Base(entry.Caller.File), entry.Caller.Line, message) + buffer.WriteString(formatted) + + return buffer.Bytes(), nil +} + +// SetupBaseLogger configures the shared logrus instance and Gin writers. +// It is safe to call multiple times; initialization happens only once. +func SetupBaseLogger() { + setupOnce.Do(func() { + log.SetOutput(os.Stdout) + log.SetReportCaller(true) + log.SetFormatter(&LogFormatter{}) + + ginInfoWriter = log.StandardLogger().Writer() + gin.DefaultWriter = ginInfoWriter + ginErrorWriter = log.StandardLogger().WriterLevel(log.ErrorLevel) + gin.DefaultErrorWriter = ginErrorWriter + gin.DebugPrintFunc = func(format string, values ...interface{}) { + format = strings.TrimRight(format, "\r\n") + log.StandardLogger().Infof(format, values...) + } + + log.RegisterExitHandler(closeLogOutputs) + }) +} + +// ConfigureLogOutput switches the global log destination between rotating files and stdout. +func ConfigureLogOutput(loggingToFile bool) error { + SetupBaseLogger() + + writerMu.Lock() + defer writerMu.Unlock() + + if loggingToFile { + const logDir = "logs" + if err := os.MkdirAll(logDir, 0o755); err != nil { + return fmt.Errorf("logging: failed to create log directory: %w", err) + } + if logWriter != nil { + _ = logWriter.Close() + } + logWriter = &lumberjack.Logger{ + Filename: filepath.Join(logDir, "main.log"), + MaxSize: 10, + MaxBackups: 0, + MaxAge: 0, + Compress: false, + } + log.SetOutput(logWriter) + return nil + } + + if logWriter != nil { + _ = logWriter.Close() + logWriter = nil + } + log.SetOutput(os.Stdout) + return nil +} + +func closeLogOutputs() { + writerMu.Lock() + defer writerMu.Unlock() + + if logWriter != nil { + _ = logWriter.Close() + logWriter = nil + } + if ginInfoWriter != nil { + _ = ginInfoWriter.Close() + ginInfoWriter = nil + } + if ginErrorWriter != nil { + _ = ginErrorWriter.Close() + ginErrorWriter = nil + } +} diff --git a/internal/misc/credentials.go b/internal/misc/credentials.go index 8d36e913..b6324863 100644 --- a/internal/misc/credentials.go +++ b/internal/misc/credentials.go @@ -1,6 +1,7 @@ package misc import ( + "fmt" "path/filepath" "strings" @@ -15,7 +16,7 @@ func LogSavingCredentials(path string) { return } // Use filepath.Clean so logs remain stable even if callers pass redundant separators. - log.Infof("Saving credentials to %s", filepath.Clean(path)) + fmt.Printf("Saving credentials to %s\n", filepath.Clean(path)) } // LogCredentialSeparator adds a visual separator to group auth/key processing logs. diff --git a/internal/provider/gemini-web/client.go b/internal/provider/gemini-web/client.go index 4ce9a263..f2a8cf2a 100644 --- a/internal/provider/gemini-web/client.go +++ b/internal/provider/gemini-web/client.go @@ -147,7 +147,7 @@ func getAccessToken(baseCookies map[string]string, proxy string, verbose bool, i if len(matches) >= 2 { token := matches[1] if verbose { - log.Infof("Gemini access token acquired.") + fmt.Println("Gemini access token acquired.") } return token, mergedCookies, nil } @@ -280,7 +280,7 @@ func (c *GeminiClient) Init(timeoutSec float64, verbose bool) error { c.Timeout = time.Duration(timeoutSec * float64(time.Second)) if verbose { - log.Infof("Gemini client initialized successfully.") + fmt.Println("Gemini client initialized successfully.") } return nil } diff --git a/internal/provider/gemini-web/media.go b/internal/provider/gemini-web/media.go index 585eff90..c5dbba87 100644 --- a/internal/provider/gemini-web/media.go +++ b/internal/provider/gemini-web/media.go @@ -136,7 +136,7 @@ func (i Image) Save(path string, filename string, cookies map[string]string, ver return "", err } if verbose { - log.Infof("Image saved as %s", dest) + fmt.Printf("Image saved as %s\n", dest) } abspath, _ := filepath.Abs(dest) return abspath, nil diff --git a/internal/util/ssh_helper.go b/internal/util/ssh_helper.go index 017bf3b8..2f81fcb3 100644 --- a/internal/util/ssh_helper.go +++ b/internal/util/ssh_helper.go @@ -120,7 +120,7 @@ func GetIPAddress() string { func PrintSSHTunnelInstructions(port int) { ipAddress := GetIPAddress() border := "================================================================================" - log.Infof("To authenticate from a remote machine, an SSH tunnel may be required.") + fmt.Println("To authenticate from a remote machine, an SSH tunnel may be required.") fmt.Println(border) fmt.Println(" Run one of the following commands on your local machine (NOT the server):") fmt.Println() diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 5a82849e..7aa9dc5c 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -380,7 +380,7 @@ func (w *Watcher) handleEvent(event fsnotify.Event) { log.Debugf("config file content unchanged (hash match), skipping reload") return } - log.Infof("config file changed, reloading: %s", w.configPath) + fmt.Printf("config file changed, reloading: %s\n", w.configPath) if w.reloadConfig() { w.clientsMutex.Lock() w.lastConfigHash = newHash @@ -390,7 +390,7 @@ func (w *Watcher) handleEvent(event fsnotify.Event) { } // Handle auth directory changes incrementally (.json only) - log.Infof("auth file changed (%s): %s, processing incrementally", event.Op.String(), filepath.Base(event.Name)) + fmt.Printf("auth file changed (%s): %s, processing incrementally\n", event.Op.String(), filepath.Base(event.Name)) if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write { w.addOrUpdateClient(event.Name) } else if event.Op&fsnotify.Remove == fsnotify.Remove { diff --git a/sdk/auth/claude.go b/sdk/auth/claude.go index 1856d61f..df042640 100644 --- a/sdk/auth/claude.go +++ b/sdk/auth/claude.go @@ -80,22 +80,22 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt state = returnedState if !opts.NoBrowser { - log.Info("Opening browser for Claude authentication") + fmt.Println("Opening browser for Claude authentication") if !browser.IsAvailable() { log.Warn("No browser available; please open the URL manually") util.PrintSSHTunnelInstructions(a.CallbackPort) - log.Infof("Visit the following URL to continue authentication:\n%s", authURL) + fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } else if err = browser.OpenURL(authURL); err != nil { log.Warnf("Failed to open browser automatically: %v", err) util.PrintSSHTunnelInstructions(a.CallbackPort) - log.Infof("Visit the following URL to continue authentication:\n%s", authURL) + fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } } else { util.PrintSSHTunnelInstructions(a.CallbackPort) - log.Infof("Visit the following URL to continue authentication:\n%s", authURL) + fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } - log.Info("Waiting for Claude authentication callback...") + fmt.Println("Waiting for Claude authentication callback...") result, err := oauthServer.WaitForCallback(5 * time.Minute) if err != nil { @@ -131,9 +131,9 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt "email": tokenStorage.Email, } - log.Info("Claude authentication successful") + fmt.Println("Claude authentication successful") if authBundle.APIKey != "" { - log.Info("Claude API key obtained and stored") + fmt.Println("Claude API key obtained and stored") } return &TokenRecord{ diff --git a/sdk/auth/codex.go b/sdk/auth/codex.go index c95a7705..24bb549d 100644 --- a/sdk/auth/codex.go +++ b/sdk/auth/codex.go @@ -79,22 +79,22 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts } if !opts.NoBrowser { - log.Info("Opening browser for Codex authentication") + fmt.Println("Opening browser for Codex authentication") if !browser.IsAvailable() { log.Warn("No browser available; please open the URL manually") util.PrintSSHTunnelInstructions(a.CallbackPort) - log.Infof("Visit the following URL to continue authentication:\n%s", authURL) + fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } else if err = browser.OpenURL(authURL); err != nil { log.Warnf("Failed to open browser automatically: %v", err) util.PrintSSHTunnelInstructions(a.CallbackPort) - log.Infof("Visit the following URL to continue authentication:\n%s", authURL) + fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } } else { util.PrintSSHTunnelInstructions(a.CallbackPort) - log.Infof("Visit the following URL to continue authentication:\n%s", authURL) + fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } - log.Info("Waiting for Codex authentication callback...") + fmt.Println("Waiting for Codex authentication callback...") result, err := oauthServer.WaitForCallback(5 * time.Minute) if err != nil { @@ -130,9 +130,9 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts "email": tokenStorage.Email, } - log.Info("Codex authentication successful") + fmt.Println("Codex authentication successful") if authBundle.APIKey != "" { - log.Info("Codex API key obtained and stored") + fmt.Println("Codex API key obtained and stored") } return &TokenRecord{ diff --git a/sdk/auth/gemini.go b/sdk/auth/gemini.go index d080d20e..420465e6 100644 --- a/sdk/auth/gemini.go +++ b/sdk/auth/gemini.go @@ -8,7 +8,6 @@ import ( "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini" // legacy client removed "github.com/router-for-me/CLIProxyAPI/v6/internal/config" - log "github.com/sirupsen/logrus" ) // GeminiAuthenticator implements the login flow for Google Gemini CLI accounts. @@ -57,7 +56,7 @@ func (a *GeminiAuthenticator) Login(ctx context.Context, cfg *config.Config, opt "project_id": ts.ProjectID, } - log.Info("Gemini authentication successful") + fmt.Println("Gemini authentication successful") return &TokenRecord{ Provider: a.Provider(), diff --git a/sdk/auth/qwen.go b/sdk/auth/qwen.go index 7d9ab828..7940660c 100644 --- a/sdk/auth/qwen.go +++ b/sdk/auth/qwen.go @@ -51,19 +51,19 @@ func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts authURL := deviceFlow.VerificationURIComplete if !opts.NoBrowser { - log.Info("Opening browser for Qwen authentication") + fmt.Println("Opening browser for Qwen authentication") if !browser.IsAvailable() { log.Warn("No browser available; please open the URL manually") - log.Infof("Visit the following URL to continue authentication:\n%s", authURL) + fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } else if err = browser.OpenURL(authURL); err != nil { log.Warnf("Failed to open browser automatically: %v", err) - log.Infof("Visit the following URL to continue authentication:\n%s", authURL) + fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } } else { - log.Infof("Visit the following URL to continue authentication:\n%s", authURL) + fmt.Printf("Visit the following URL to continue authentication:\n%s\n", authURL) } - log.Info("Waiting for Qwen authentication...") + fmt.Println("Waiting for Qwen authentication...") tokenData, err := authSvc.PollForToken(deviceFlow.DeviceCode, deviceFlow.CodeVerifier) if err != nil { @@ -101,7 +101,7 @@ func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts "email": tokenStorage.Email, } - log.Info("Qwen authentication successful") + fmt.Println("Qwen authentication successful") return &TokenRecord{ Provider: a.Provider(), diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 314d82d7..4876dada 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -331,7 +331,7 @@ func (s *Service) Run(ctx context.Context) error { }() time.Sleep(100 * time.Millisecond) - log.Info("API server started successfully") + fmt.Println("API server started successfully") if s.hooks.OnAfterStart != nil { s.hooks.OnAfterStart(s)