mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 04:20:50 +08:00
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.
This commit is contained in:
@@ -4,106 +4,30 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/cmd"
|
"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/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/translator"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
|
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/natefinch/lumberjack.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "dev"
|
Version = "dev"
|
||||||
Commit = "none"
|
Commit = "none"
|
||||||
BuildDate = "unknown"
|
BuildDate = "unknown"
|
||||||
logWriter *lumberjack.Logger
|
|
||||||
ginInfoWriter *io.PipeWriter
|
|
||||||
ginErrorWriter *io.PipeWriter
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogFormatter defines a custom log format for logrus.
|
// init initializes the shared logger setup.
|
||||||
// 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.
|
|
||||||
func init() {
|
func init() {
|
||||||
logDir := "logs"
|
logging.SetupBaseLogger()
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// main is the entry point of the application.
|
// 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).
|
// service based on the provided flags (login, codex-login, or server mode).
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Printf("CLIProxyAPI Version: %s, Commit: %s, BuiltAt: %s\n", Version, Commit, BuildDate)
|
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.
|
// Command-line flags to control the application's behavior.
|
||||||
var login bool
|
var login bool
|
||||||
@@ -189,6 +112,12 @@ func main() {
|
|||||||
log.Fatalf("failed to load config: %v", err)
|
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.
|
// Set the log level based on the configuration.
|
||||||
util.SetLogLevel(cfg)
|
util.SetLogLevel(cfg)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ auth-dir: "~/.cli-proxy-api"
|
|||||||
# Enable debug logging
|
# Enable debug logging
|
||||||
debug: false
|
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. Supports socks5/http/https protocols. Example: socks5://user:pass@192.168.1.1:1080/
|
||||||
proxy-url: ""
|
proxy-url: ""
|
||||||
|
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ func (h *Handler) saveTokenRecord(ctx context.Context, record *sdkAuth.TokenReco
|
|||||||
func (h *Handler) RequestAnthropicToken(c *gin.Context) {
|
func (h *Handler) RequestAnthropicToken(c *gin.Context) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
log.Info("Initializing Claude authentication...")
|
fmt.Println("Initializing Claude authentication...")
|
||||||
|
|
||||||
// Generate PKCE codes
|
// Generate PKCE codes
|
||||||
pkceCodes, err := claude.GeneratePKCECodes()
|
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
|
// Wait up to 5 minutes
|
||||||
resultMap, errWait := waitForFile(waitFile, 5*time.Minute)
|
resultMap, errWait := waitForFile(waitFile, 5*time.Minute)
|
||||||
if errWait != nil {
|
if errWait != nil {
|
||||||
@@ -509,11 +509,11 @@ func (h *Handler) RequestAnthropicToken(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Authentication successful! Token saved to %s", savedPath)
|
fmt.Printf("Authentication successful! Token saved to %s\n", savedPath)
|
||||||
if bundle.APIKey != "" {
|
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)
|
delete(oauthStatus, state)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -527,7 +527,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) {
|
|||||||
// Optional project ID from query
|
// Optional project ID from query
|
||||||
projectID := c.Query("project_id")
|
projectID := c.Query("project_id")
|
||||||
|
|
||||||
log.Info("Initializing Google authentication...")
|
fmt.Println("Initializing Google authentication...")
|
||||||
|
|
||||||
// OAuth2 configuration (mirrors internal/auth/gemini)
|
// OAuth2 configuration (mirrors internal/auth/gemini)
|
||||||
conf := &oauth2.Config{
|
conf := &oauth2.Config{
|
||||||
@@ -549,7 +549,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) {
|
|||||||
go func() {
|
go func() {
|
||||||
// Wait for callback file written by server route
|
// Wait for callback file written by server route
|
||||||
waitFile := filepath.Join(h.cfg.AuthDir, fmt.Sprintf(".oauth-gemini-%s.oauth", state))
|
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)
|
deadline := time.Now().Add(5 * time.Minute)
|
||||||
var authCode string
|
var authCode string
|
||||||
for {
|
for {
|
||||||
@@ -618,9 +618,9 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) {
|
|||||||
|
|
||||||
email := gjson.GetBytes(bodyBytes, "email").String()
|
email := gjson.GetBytes(bodyBytes, "email").String()
|
||||||
if email != "" {
|
if email != "" {
|
||||||
log.Infof("Authenticated user email: %s", email)
|
fmt.Printf("Authenticated user email: %s\n", email)
|
||||||
} else {
|
} 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"
|
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"
|
oauthStatus[state] = "Failed to get authenticated client"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("Authentication successful.")
|
fmt.Println("Authentication successful.")
|
||||||
|
|
||||||
record := &sdkAuth.TokenRecord{
|
record := &sdkAuth.TokenRecord{
|
||||||
Provider: "gemini",
|
Provider: "gemini",
|
||||||
@@ -676,7 +676,7 @@ func (h *Handler) RequestGeminiCLIToken(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete(oauthStatus, state)
|
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] = ""
|
oauthStatus[state] = ""
|
||||||
@@ -737,14 +737,14 @@ func (h *Handler) CreateGeminiWebToken(c *gin.Context) {
|
|||||||
return
|
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)})
|
c.JSON(http.StatusOK, gin.H{"status": "ok", "file": filepath.Base(savedPath)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) RequestCodexToken(c *gin.Context) {
|
func (h *Handler) RequestCodexToken(c *gin.Context) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
log.Info("Initializing Codex authentication...")
|
fmt.Println("Initializing Codex authentication...")
|
||||||
|
|
||||||
// Generate PKCE codes
|
// Generate PKCE codes
|
||||||
pkceCodes, err := codex.GeneratePKCECodes()
|
pkceCodes, err := codex.GeneratePKCECodes()
|
||||||
@@ -884,11 +884,11 @@ func (h *Handler) RequestCodexToken(c *gin.Context) {
|
|||||||
log.Fatalf("Failed to save authentication tokens: %v", errSave)
|
log.Fatalf("Failed to save authentication tokens: %v", errSave)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Infof("Authentication successful! Token saved to %s", savedPath)
|
fmt.Printf("Authentication successful! Token saved to %s\n", savedPath)
|
||||||
if bundle.APIKey != "" {
|
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)
|
delete(oauthStatus, state)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -899,7 +899,7 @@ func (h *Handler) RequestCodexToken(c *gin.Context) {
|
|||||||
func (h *Handler) RequestQwenToken(c *gin.Context) {
|
func (h *Handler) RequestQwenToken(c *gin.Context) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
log.Info("Initializing Qwen authentication...")
|
fmt.Println("Initializing Qwen authentication...")
|
||||||
|
|
||||||
state := fmt.Sprintf("gem-%d", time.Now().UnixNano())
|
state := fmt.Sprintf("gem-%d", time.Now().UnixNano())
|
||||||
// Initialize Qwen auth service
|
// Initialize Qwen auth service
|
||||||
@@ -914,7 +914,7 @@ func (h *Handler) RequestQwenToken(c *gin.Context) {
|
|||||||
authURL := deviceFlow.VerificationURIComplete
|
authURL := deviceFlow.VerificationURIComplete
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Info("Waiting for authentication...")
|
fmt.Println("Waiting for authentication...")
|
||||||
tokenData, errPollForToken := qwenAuth.PollForToken(deviceFlow.DeviceCode, deviceFlow.CodeVerifier)
|
tokenData, errPollForToken := qwenAuth.PollForToken(deviceFlow.DeviceCode, deviceFlow.CodeVerifier)
|
||||||
if errPollForToken != nil {
|
if errPollForToken != nil {
|
||||||
oauthStatus[state] = "Authentication failed"
|
oauthStatus[state] = "Authentication failed"
|
||||||
@@ -939,8 +939,8 @@ func (h *Handler) RequestQwenToken(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Authentication successful! Token saved to %s", savedPath)
|
fmt.Printf("Authentication successful! Token saved to %s\n", savedPath)
|
||||||
log.Info("You can now use Qwen services through this CLI")
|
fmt.Println("You can now use Qwen services through this CLI")
|
||||||
delete(oauthStatus, state)
|
delete(oauthStatus, state)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
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
|
// Update log level dynamically when debug flag changes
|
||||||
if s.cfg.Debug != cfg.Debug {
|
if s.cfg.Debug != cfg.Debug {
|
||||||
util.SetLogLevel(cfg)
|
util.SetLogLevel(cfg)
|
||||||
@@ -477,7 +485,7 @@ func (s *Server) UpdateClients(cfg *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
total := authFiles + glAPIKeyCount + claudeAPIKeyCount + codexAPIKeyCount + openAICompatCount
|
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,
|
total,
|
||||||
authFiles,
|
authFiles,
|
||||||
glAPIKeyCount,
|
glAPIKeyCount,
|
||||||
|
|||||||
@@ -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 no token is found in storage, initiate the web-based OAuth flow.
|
||||||
if ts.Token == nil {
|
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...)
|
token, err = g.getTokenFromWeb(ctx, conf, noBrowser...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get token from web: %w", err)
|
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")
|
emailResult := gjson.GetBytes(bodyBytes, "email")
|
||||||
if emailResult.Exists() && emailResult.Type == gjson.String {
|
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 {
|
} else {
|
||||||
log.Info("Failed to get user email from token")
|
fmt.Println("Failed to get user email from token")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ifToken map[string]any
|
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"))
|
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent"))
|
||||||
|
|
||||||
if len(noBrowser) == 1 && !noBrowser[0] {
|
if len(noBrowser) == 1 && !noBrowser[0] {
|
||||||
log.Info("Opening browser for authentication...")
|
fmt.Println("Opening browser for authentication...")
|
||||||
|
|
||||||
// Check if browser is available
|
// Check if browser is available
|
||||||
if !browser.IsAvailable() {
|
if !browser.IsAvailable() {
|
||||||
log.Warn("No browser available on this system")
|
log.Warn("No browser available on this system")
|
||||||
util.PrintSSHTunnelInstructions(8085)
|
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 {
|
} else {
|
||||||
if err := browser.OpenURL(authURL); err != nil {
|
if err := browser.OpenURL(authURL); err != nil {
|
||||||
authErr := codex.NewAuthenticationError(codex.ErrBrowserOpenFailed, err)
|
authErr := codex.NewAuthenticationError(codex.ErrBrowserOpenFailed, err)
|
||||||
log.Warn(codex.GetUserFriendlyMessage(authErr))
|
log.Warn(codex.GetUserFriendlyMessage(authErr))
|
||||||
util.PrintSSHTunnelInstructions(8085)
|
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
|
// Log platform info for debugging
|
||||||
platformInfo := browser.GetPlatformInfo()
|
platformInfo := browser.GetPlatformInfo()
|
||||||
@@ -269,10 +269,10 @@ func (g *GeminiAuth) getTokenFromWeb(ctx context.Context, config *oauth2.Config,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
util.PrintSSHTunnelInstructions(8085)
|
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.
|
// Wait for the authorization code or an error.
|
||||||
var authCode string
|
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)
|
return nil, fmt.Errorf("failed to exchange token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Authentication successful.")
|
fmt.Println("Authentication successful.")
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ func (qa *QwenAuth) PollForToken(deviceCode, codeVerifier string) (*QwenTokenDat
|
|||||||
switch errorType {
|
switch errorType {
|
||||||
case "authorization_pending":
|
case "authorization_pending":
|
||||||
// User has not yet approved the authorization request. Continue polling.
|
// 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)
|
time.Sleep(pollInterval)
|
||||||
continue
|
continue
|
||||||
case "slow_down":
|
case "slow_down":
|
||||||
@@ -269,7 +269,7 @@ func (qa *QwenAuth) PollForToken(deviceCode, codeVerifier string) (*QwenTokenDat
|
|||||||
if pollInterval > 10*time.Second {
|
if pollInterval > 10*time.Second {
|
||||||
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)
|
time.Sleep(pollInterval)
|
||||||
continue
|
continue
|
||||||
case "expired_token":
|
case "expired_token":
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - An error if the URL cannot be opened, otherwise nil.
|
// - An error if the URL cannot be opened, otherwise nil.
|
||||||
func OpenURL(url string) error {
|
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
|
// Try using the open-golang library first
|
||||||
err := open.Run(url)
|
err := open.Run(url)
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ func DoLogin(cfg *config.Config, projectID string, options *LoginOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if savedPath != "" {
|
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!")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ type Config struct {
|
|||||||
// Debug enables or disables debug-level logging and other debug features.
|
// Debug enables or disables debug-level logging and other debug features.
|
||||||
Debug bool `yaml:"debug" json:"debug"`
|
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 is the URL of an optional proxy server to use for outbound requests.
|
||||||
ProxyURL string `yaml:"proxy-url" json:"proxy-url"`
|
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.
|
// Unmarshal the YAML data into the Config struct.
|
||||||
var config Config
|
var config Config
|
||||||
// Set defaults before unmarshal so that absent keys keep defaults.
|
// Set defaults before unmarshal so that absent keys keep defaults.
|
||||||
|
config.LoggingToFile = true
|
||||||
config.GeminiWeb.Context = true
|
config.GeminiWeb.Context = true
|
||||||
if err = yaml.Unmarshal(data, &config); err != nil {
|
if err = yaml.Unmarshal(data, &config); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
||||||
|
|||||||
117
internal/logging/global_logger.go
Normal file
117
internal/logging/global_logger.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package misc
|
package misc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ func LogSavingCredentials(path string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Use filepath.Clean so logs remain stable even if callers pass redundant separators.
|
// 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.
|
// LogCredentialSeparator adds a visual separator to group auth/key processing logs.
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ func getAccessToken(baseCookies map[string]string, proxy string, verbose bool, i
|
|||||||
if len(matches) >= 2 {
|
if len(matches) >= 2 {
|
||||||
token := matches[1]
|
token := matches[1]
|
||||||
if verbose {
|
if verbose {
|
||||||
log.Infof("Gemini access token acquired.")
|
fmt.Println("Gemini access token acquired.")
|
||||||
}
|
}
|
||||||
return token, mergedCookies, nil
|
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))
|
c.Timeout = time.Duration(timeoutSec * float64(time.Second))
|
||||||
if verbose {
|
if verbose {
|
||||||
log.Infof("Gemini client initialized successfully.")
|
fmt.Println("Gemini client initialized successfully.")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func (i Image) Save(path string, filename string, cookies map[string]string, ver
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if verbose {
|
if verbose {
|
||||||
log.Infof("Image saved as %s", dest)
|
fmt.Printf("Image saved as %s\n", dest)
|
||||||
}
|
}
|
||||||
abspath, _ := filepath.Abs(dest)
|
abspath, _ := filepath.Abs(dest)
|
||||||
return abspath, nil
|
return abspath, nil
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func GetIPAddress() string {
|
|||||||
func PrintSSHTunnelInstructions(port int) {
|
func PrintSSHTunnelInstructions(port int) {
|
||||||
ipAddress := GetIPAddress()
|
ipAddress := GetIPAddress()
|
||||||
border := "================================================================================"
|
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(border)
|
||||||
fmt.Println(" Run one of the following commands on your local machine (NOT the server):")
|
fmt.Println(" Run one of the following commands on your local machine (NOT the server):")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ func (w *Watcher) handleEvent(event fsnotify.Event) {
|
|||||||
log.Debugf("config file content unchanged (hash match), skipping reload")
|
log.Debugf("config file content unchanged (hash match), skipping reload")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Infof("config file changed, reloading: %s", w.configPath)
|
fmt.Printf("config file changed, reloading: %s\n", w.configPath)
|
||||||
if w.reloadConfig() {
|
if w.reloadConfig() {
|
||||||
w.clientsMutex.Lock()
|
w.clientsMutex.Lock()
|
||||||
w.lastConfigHash = newHash
|
w.lastConfigHash = newHash
|
||||||
@@ -390,7 +390,7 @@ func (w *Watcher) handleEvent(event fsnotify.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle auth directory changes incrementally (.json only)
|
// 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 {
|
if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
w.addOrUpdateClient(event.Name)
|
w.addOrUpdateClient(event.Name)
|
||||||
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
||||||
|
|||||||
@@ -80,22 +80,22 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt
|
|||||||
state = returnedState
|
state = returnedState
|
||||||
|
|
||||||
if !opts.NoBrowser {
|
if !opts.NoBrowser {
|
||||||
log.Info("Opening browser for Claude authentication")
|
fmt.Println("Opening browser for Claude authentication")
|
||||||
if !browser.IsAvailable() {
|
if !browser.IsAvailable() {
|
||||||
log.Warn("No browser available; please open the URL manually")
|
log.Warn("No browser available; please open the URL manually")
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
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 {
|
} else if err = browser.OpenURL(authURL); err != nil {
|
||||||
log.Warnf("Failed to open browser automatically: %v", err)
|
log.Warnf("Failed to open browser automatically: %v", err)
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
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 {
|
} else {
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
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)
|
result, err := oauthServer.WaitForCallback(5 * time.Minute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -131,9 +131,9 @@ func (a *ClaudeAuthenticator) Login(ctx context.Context, cfg *config.Config, opt
|
|||||||
"email": tokenStorage.Email,
|
"email": tokenStorage.Email,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Claude authentication successful")
|
fmt.Println("Claude authentication successful")
|
||||||
if authBundle.APIKey != "" {
|
if authBundle.APIKey != "" {
|
||||||
log.Info("Claude API key obtained and stored")
|
fmt.Println("Claude API key obtained and stored")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &TokenRecord{
|
return &TokenRecord{
|
||||||
|
|||||||
@@ -79,22 +79,22 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !opts.NoBrowser {
|
if !opts.NoBrowser {
|
||||||
log.Info("Opening browser for Codex authentication")
|
fmt.Println("Opening browser for Codex authentication")
|
||||||
if !browser.IsAvailable() {
|
if !browser.IsAvailable() {
|
||||||
log.Warn("No browser available; please open the URL manually")
|
log.Warn("No browser available; please open the URL manually")
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
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 {
|
} else if err = browser.OpenURL(authURL); err != nil {
|
||||||
log.Warnf("Failed to open browser automatically: %v", err)
|
log.Warnf("Failed to open browser automatically: %v", err)
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
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 {
|
} else {
|
||||||
util.PrintSSHTunnelInstructions(a.CallbackPort)
|
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)
|
result, err := oauthServer.WaitForCallback(5 * time.Minute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -130,9 +130,9 @@ func (a *CodexAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
"email": tokenStorage.Email,
|
"email": tokenStorage.Email,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Codex authentication successful")
|
fmt.Println("Codex authentication successful")
|
||||||
if authBundle.APIKey != "" {
|
if authBundle.APIKey != "" {
|
||||||
log.Info("Codex API key obtained and stored")
|
fmt.Println("Codex API key obtained and stored")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &TokenRecord{
|
return &TokenRecord{
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
|
||||||
// legacy client removed
|
// legacy client removed
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GeminiAuthenticator implements the login flow for Google Gemini CLI accounts.
|
// 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,
|
"project_id": ts.ProjectID,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Gemini authentication successful")
|
fmt.Println("Gemini authentication successful")
|
||||||
|
|
||||||
return &TokenRecord{
|
return &TokenRecord{
|
||||||
Provider: a.Provider(),
|
Provider: a.Provider(),
|
||||||
|
|||||||
@@ -51,19 +51,19 @@ func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
authURL := deviceFlow.VerificationURIComplete
|
authURL := deviceFlow.VerificationURIComplete
|
||||||
|
|
||||||
if !opts.NoBrowser {
|
if !opts.NoBrowser {
|
||||||
log.Info("Opening browser for Qwen authentication")
|
fmt.Println("Opening browser for Qwen authentication")
|
||||||
if !browser.IsAvailable() {
|
if !browser.IsAvailable() {
|
||||||
log.Warn("No browser available; please open the URL manually")
|
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 {
|
} else if err = browser.OpenURL(authURL); err != nil {
|
||||||
log.Warnf("Failed to open browser automatically: %v", err)
|
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 {
|
} 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)
|
tokenData, err := authSvc.PollForToken(deviceFlow.DeviceCode, deviceFlow.CodeVerifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,7 +101,7 @@ func (a *QwenAuthenticator) Login(ctx context.Context, cfg *config.Config, opts
|
|||||||
"email": tokenStorage.Email,
|
"email": tokenStorage.Email,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Qwen authentication successful")
|
fmt.Println("Qwen authentication successful")
|
||||||
|
|
||||||
return &TokenRecord{
|
return &TokenRecord{
|
||||||
Provider: a.Provider(),
|
Provider: a.Provider(),
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ func (s *Service) Run(ctx context.Context) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
log.Info("API server started successfully")
|
fmt.Println("API server started successfully")
|
||||||
|
|
||||||
if s.hooks.OnAfterStart != nil {
|
if s.hooks.OnAfterStart != nil {
|
||||||
s.hooks.OnAfterStart(s)
|
s.hooks.OnAfterStart(s)
|
||||||
|
|||||||
Reference in New Issue
Block a user