package logger import ( "cursor-api-proxy/internal/config" "cursor-api-proxy/internal/pool" "fmt" "os" "path/filepath" "strings" "time" ) const ( cReset = "\x1b[0m" cBold = "\x1b[1m" cDim = "\x1b[2m" cCyan = "\x1b[36m" cBCyan = "\x1b[1;96m" cGreen = "\x1b[32m" cBGreen = "\x1b[1;92m" cYellow = "\x1b[33m" cMagenta = "\x1b[35m" cBMagenta = "\x1b[1;95m" cRed = "\x1b[31m" cGray = "\x1b[90m" cWhite = "\x1b[97m" ) var roleStyle = map[string]string{ "system": cYellow, "user": cCyan, "assistant": cGreen, } var roleEmoji = map[string]string{ "system": "๐Ÿ”ง", "user": "๐Ÿ‘ค", "assistant": "๐Ÿค–", } func ts() string { return cGray + time.Now().UTC().Format("15:04:05") + cReset } func tsDate() string { return cGray + time.Now().UTC().Format("2006-01-02 15:04:05") + cReset } func truncate(s string, max int) string { if len(s) <= max { return s } head := int(float64(max) * 0.6) tail := max - head omitted := len(s) - head - tail return s[:head] + fmt.Sprintf("%s โ€ฆ (%d chars omitted) โ€ฆ ", cDim, omitted) + s[len(s)-tail:] + cReset } func hr(ch string, length int) string { return cGray + strings.Repeat(ch, length) + cReset } type TrafficMessage struct { Role string Content string } func LogDebug(format string, args ...interface{}) { msg := fmt.Sprintf(format, args...) fmt.Printf("%s %s[DEBUG]%s %s\n", ts(), cGray, cReset, msg) } func LogServerStart(version, scheme, host string, port int, cfg config.BridgeConfig) { provider := cfg.Provider if provider == "" { provider = "cursor" } fmt.Printf("\n%s%sโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—%s\n", cBold, cBCyan, cReset) fmt.Printf("%s%s cursor-api-proxy %sv%s%s%s%s ready%s\n", cBold, cBCyan, cReset, cBold, cWhite, version, cBCyan, cReset) fmt.Printf("%s%sโ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•%s\n\n", cBold, cBCyan, cReset) url := fmt.Sprintf("%s://%s:%d", scheme, host, port) fmt.Printf(" %sโ—%s listening %s%s%s\n", cBGreen, cReset, cBold, url, cReset) fmt.Printf(" %sโ–ธ%s provider %s%s%s\n", cCyan, cReset, cBold, provider, cReset) fmt.Printf(" %sโ–ธ%s agent %s%s%s\n", cCyan, cReset, cDim, cfg.AgentBin, cReset) fmt.Printf(" %sโ–ธ%s workspace %s%s%s\n", cCyan, cReset, cDim, cfg.Workspace, cReset) fmt.Printf(" %sโ–ธ%s model %s%s%s\n", cCyan, cReset, cDim, cfg.DefaultModel, cReset) fmt.Printf(" %sโ–ธ%s mode %s%s%s\n", cCyan, cReset, cDim, cfg.Mode, cReset) fmt.Printf(" %sโ–ธ%s timeout %s%d ms%s\n", cCyan, cReset, cDim, cfg.TimeoutMs, cReset) // ้กฏ็คบ Gemini Web Provider ็›ธ้—œ่จญๅฎš if provider == "gemini-web" { fmt.Printf(" %sโ–ธ%s gemini-dir %s%s%s\n", cCyan, cReset, cDim, cfg.GeminiAccountDir, cReset) fmt.Printf(" %sโ–ธ%s max-sess %s%d%s\n", cCyan, cReset, cDim, cfg.GeminiMaxSessions, cReset) } flags := []string{} if cfg.Force { flags = append(flags, "force") } if cfg.ApproveMcps { flags = append(flags, "approve-mcps") } if cfg.MaxMode { flags = append(flags, "max-mode") } if cfg.Verbose { flags = append(flags, "verbose") } if cfg.ChatOnlyWorkspace { flags = append(flags, "chat-only") } if cfg.RequiredKey != "" { flags = append(flags, "api-key-required") } if len(flags) > 0 { fmt.Printf(" %sโ–ธ%s flags %s%s%s\n", cCyan, cReset, cYellow, strings.Join(flags, " ยท "), cReset) } if len(cfg.ConfigDirs) > 0 { fmt.Printf(" %sโ–ธ%s pool %s%d accounts%s\n", cCyan, cReset, cBGreen, len(cfg.ConfigDirs), cReset) } fmt.Println() } func LogShutdown(sig string) { fmt.Printf("\n%s %sโŠ˜ %s received โ€” shutting down gracefullyโ€ฆ%s\n", tsDate(), cYellow, sig, cReset) } func LogRequestStart(method, pathname, model string, timeoutMs int, isStream bool) { modeTag := fmt.Sprintf("%ssync%s", cDim, cReset) if isStream { modeTag = fmt.Sprintf("%sโšก stream%s", cBCyan, cReset) } fmt.Printf("%s %sโ–ถ%s %s %s %s timeout:%dms %s\n", ts(), cBCyan, cReset, method, pathname, model, timeoutMs, modeTag) } func LogRequestDone(method, pathname, model string, latencyMs int64, code int) { statusColor := cBGreen if code != 0 { statusColor = cRed } fmt.Printf("%s %sโ– %s %s %s %s %s%dms exit:%d%s\n", ts(), statusColor, cReset, method, pathname, model, cDim, latencyMs, code, cReset) } func LogRequestTimeout(method, pathname, model string, timeoutMs int) { fmt.Printf("%s %sโฑ%s %s %s %s %stimed-out after %dms%s\n", ts(), cRed, cReset, method, pathname, model, cRed, timeoutMs, cReset) } func LogClientDisconnect(method, pathname, model string, latencyMs int64) { fmt.Printf("%s %sโšก%s %s %s %s %sclient disconnected after %dms%s\n", ts(), cYellow, cReset, method, pathname, model, cYellow, latencyMs, cReset) } func LogStreamChunk(model string, text string, chunkNum int) { preview := truncate(strings.ReplaceAll(text, "\n", "โ†ต "), 120) fmt.Printf("%s %sโ–ธ%s #%d %s%s%s\n", ts(), cDim, cReset, chunkNum, cWhite, preview, cReset) } func LogRawLine(line string) { preview := truncate(strings.ReplaceAll(line, "\n", "โ†ต "), 200) fmt.Printf("%s %sโ”‚%s %sraw%s %s\n", ts(), cGray, cReset, cDim, cReset, preview) } func LogIncoming(method, pathname, remoteAddress string) { methodColor := cBCyan switch method { case "POST": methodColor = cBMagenta case "GET": methodColor = cBCyan case "DELETE": methodColor = cRed } fmt.Printf("%s %s%s%s%s %s%s%s %s(%s)%s\n", ts(), methodColor, cBold, method, cReset, cWhite, pathname, cReset, cDim, remoteAddress, cReset, ) } func LogAccountAssigned(configDir string) { if configDir == "" { return } name := filepath.Base(configDir) fmt.Printf("%s %sโ†’%s account %s%s%s\n", ts(), cBCyan, cReset, cBold, name, cReset) } func LogAccountStats(verbose bool, stats []pool.AccountStat) { if !verbose || len(stats) == 0 { return } now := time.Now().UnixMilli() fmt.Printf("%sโ”Œโ”€ Account Stats %sโ”%s\n", cGray, strings.Repeat("โ”€", 44), cReset) for _, s := range stats { name := fmt.Sprintf("%-20s", filepath.Base(s.ConfigDir)) active := fmt.Sprintf("%sactive:0%s", cDim, cReset) if s.ActiveRequests > 0 { active = fmt.Sprintf("%sactive:%d%s", cBCyan, s.ActiveRequests, cReset) } total := fmt.Sprintf("total:%s%d%s", cBold, s.TotalRequests, cReset) ok := fmt.Sprintf("%sok:%d%s", cGreen, s.TotalSuccess, cReset) errStr := fmt.Sprintf("%serr:0%s", cDim, cReset) if s.TotalErrors > 0 { errStr = fmt.Sprintf("%serr:%d%s", cRed, s.TotalErrors, cReset) } rl := fmt.Sprintf("%srl:0%s", cDim, cReset) if s.TotalRateLimits > 0 { rl = fmt.Sprintf("%srl:%d%s", cYellow, s.TotalRateLimits, cReset) } avg := "avg:-" if s.TotalRequests > 0 { avg = fmt.Sprintf("avg:%dms", s.TotalLatencyMs/int64(s.TotalRequests)) } status := fmt.Sprintf("%sโœ“%s", cGreen, cReset) if s.IsRateLimited { recovers := time.UnixMilli(s.RateLimitUntil).UTC().Format(time.RFC3339) _ = now status = fmt.Sprintf("%sโ›” rate-limited (recovers %s)%s", cRed, recovers, cReset) } fmt.Printf(" %s%s%s %s %s %s %s %s %s%s%s %s\n", cBold, name, cReset, active, total, ok, errStr, rl, cDim, avg, cReset, status) } fmt.Printf("%sโ””%sโ”˜%s\n", cGray, strings.Repeat("โ”€", 60), cReset) } func LogTrafficRequest(verbose bool, model string, messages []TrafficMessage, isStream bool) { if !verbose { return } modeTag := fmt.Sprintf("%ssync%s", cDim, cReset) if isStream { modeTag = fmt.Sprintf("%sโšก stream%s", cBCyan, cReset) } modelStr := fmt.Sprintf("%sโœฆ %s%s", cBMagenta, model, cReset) fmt.Println(hr("โ”€", 60)) fmt.Printf("%s ๐Ÿ“ค %s%sREQUEST%s %s %s\n", ts(), cBCyan, cBold, cReset, modelStr, modeTag) for _, m := range messages { roleColor := cWhite if c, ok := roleStyle[m.Role]; ok { roleColor = c } emoji := "๐Ÿ’ฌ" if e, ok := roleEmoji[m.Role]; ok { emoji = e } label := fmt.Sprintf("%s%s[%s]%s", roleColor, cBold, m.Role, cReset) charCount := fmt.Sprintf("%s(%d chars)%s", cDim, len(m.Content), cReset) preview := truncate(strings.ReplaceAll(m.Content, "\n", "โ†ต "), 280) fmt.Printf(" %s %s %s\n", emoji, label, charCount) fmt.Printf(" %s%s%s\n", cDim, preview, cReset) } } func LogTrafficResponse(verbose bool, model, text string, isStream bool) { if !verbose { return } modeTag := fmt.Sprintf("%ssync%s", cDim, cReset) if isStream { modeTag = fmt.Sprintf("%sโšก stream%s", cBGreen, cReset) } modelStr := fmt.Sprintf("%sโœฆ %s%s", cBMagenta, model, cReset) charCount := fmt.Sprintf("%s%d%s%s chars%s", cBold, len(text), cReset, cDim, cReset) preview := truncate(strings.ReplaceAll(text, "\n", "โ†ต "), 480) fmt.Printf("%s ๐Ÿ“ฅ %s%sRESPONSE%s %s %s %s\n", ts(), cBGreen, cBold, cReset, modelStr, modeTag, charCount) fmt.Printf(" ๐Ÿค– %s%s%s\n", cGreen, preview, cReset) fmt.Println(hr("โ”€", 60)) } func AppendSessionLine(logPath, method, pathname, remoteAddress string, statusCode int) { line := fmt.Sprintf("%s %s %s %s %d\n", time.Now().UTC().Format(time.RFC3339), method, pathname, remoteAddress, statusCode) dir := filepath.Dir(logPath) if err := os.MkdirAll(dir, 0755); err == nil { f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err == nil { _, _ = f.WriteString(line) f.Close() } } } func LogTruncation(originalLen, finalLen int) { fmt.Printf("%s %sโš  prompt truncated%s %s(%d โ†’ %d chars, tail preserved)%s\n", ts(), cYellow, cReset, cDim, originalLen, finalLen, cReset) } func LogAgentError(logPath, method, pathname, remoteAddress string, exitCode int, stderr string) string { errMsg := fmt.Sprintf("Cursor CLI failed (exit %d): %s", exitCode, strings.TrimSpace(stderr)) fmt.Fprintf(os.Stderr, "%s %sโœ— agent error%s %s%s%s\n", ts(), cRed, cReset, cDim, errMsg, cReset) truncated := strings.TrimSpace(stderr) if len(truncated) > 200 { truncated = truncated[:200] } truncated = strings.ReplaceAll(truncated, "\n", " ") line := fmt.Sprintf("%s ERROR %s %s %s agent_exit_%d %s\n", time.Now().UTC().Format(time.RFC3339), method, pathname, remoteAddress, exitCode, truncated) if f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { _, _ = f.WriteString(line) f.Close() } return errMsg }