opencode-cursor-agent/internal/logger/logger.go

185 lines
5.8 KiB
Go
Raw Normal View History

2026-03-30 14:09:15 +00:00
package logger
import (
"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(time.RFC3339Nano) + 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 LogIncoming(method, pathname, remoteAddress string) {
fmt.Printf("[%s] Incoming: %s %s (from %s)\n", time.Now().UTC().Format(time.RFC3339), method, pathname, remoteAddress)
}
func LogAccountAssigned(configDir string) {
if configDir == "" {
return
}
name := filepath.Base(configDir)
fmt.Printf("[%s] %s→ account%s %s%s%s\n", time.Now().UTC().Format(time.RFC3339), 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("%sdim%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("%sdim%serr:0%s", cDim, "", cReset)
if s.TotalErrors > 0 {
errStr = fmt.Sprintf("%serr:%d%s", cRed, s.TotalErrors, cReset)
}
rl := fmt.Sprintf("%sdim%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("%sdim%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("%sdim%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 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] Agent error: %s\n", time.Now().UTC().Format(time.RFC3339), errMsg)
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
}