fix logs
This commit is contained in:
parent
eaa9af41a4
commit
974f2f2bb5
|
|
@ -0,0 +1,21 @@
|
|||
# 由 make env 自動產生,請勿手動編輯
|
||||
CURSOR_BRIDGE_HOST=127.0.0.1
|
||||
CURSOR_BRIDGE_PORT=8766
|
||||
CURSOR_BRIDGE_API_KEY=
|
||||
CURSOR_BRIDGE_TIMEOUT_MS=3600000
|
||||
CURSOR_BRIDGE_MULTI_PORT=false
|
||||
CURSOR_BRIDGE_VERBOSE=false
|
||||
CURSOR_AGENT_BIN=agent
|
||||
CURSOR_AGENT_NODE=
|
||||
CURSOR_AGENT_SCRIPT=
|
||||
CURSOR_BRIDGE_DEFAULT_MODEL=auto
|
||||
CURSOR_BRIDGE_STRICT_MODEL=true
|
||||
CURSOR_BRIDGE_MAX_MODE=true
|
||||
CURSOR_BRIDGE_FORCE=false
|
||||
CURSOR_BRIDGE_APPROVE_MCPS=false
|
||||
CURSOR_BRIDGE_WORKSPACE=
|
||||
CURSOR_BRIDGE_CHAT_ONLY_WORKSPACE=true
|
||||
CURSOR_CONFIG_DIRS=
|
||||
CURSOR_BRIDGE_TLS_CERT=
|
||||
CURSOR_BRIDGE_TLS_KEY=
|
||||
CURSOR_BRIDGE_SESSIONS_LOG=
|
||||
6
Makefile
6
Makefile
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
# ── 伺服器設定 ─────────────────────────────────
|
||||
HOST ?= 127.0.0.1
|
||||
PORT ?= 8765
|
||||
PORT ?= 8766
|
||||
API_KEY ?=
|
||||
TIMEOUT_MS ?= 300000
|
||||
TIMEOUT_MS ?= 3600000
|
||||
MULTI_PORT ?= false
|
||||
VERBOSE ?= false
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ AGENT_NODE ?=
|
|||
AGENT_SCRIPT ?=
|
||||
DEFAULT_MODEL ?= auto
|
||||
STRICT_MODEL ?= true
|
||||
MAX_MODE ?= false
|
||||
MAX_MODE ?= true
|
||||
FORCE ?= false
|
||||
APPROVE_MCPS ?= false
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,26 @@ func discoverAccountDirs(homeDir string) []string {
|
|||
return dirs
|
||||
}
|
||||
|
||||
func OsEnvToMap() EnvSource {
|
||||
func parseDotEnv(path string) EnvSource {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
m := make(EnvSource)
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func OsEnvToMap(cwdHint ...string) EnvSource {
|
||||
m := make(EnvSource)
|
||||
for _, kv := range os.Environ() {
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
|
|
@ -149,6 +168,22 @@ func OsEnvToMap() EnvSource {
|
|||
m[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
cwd := ""
|
||||
if len(cwdHint) > 0 && cwdHint[0] != "" {
|
||||
cwd = cwdHint[0]
|
||||
} else {
|
||||
cwd, _ = os.Getwd()
|
||||
}
|
||||
|
||||
if dotenv := parseDotEnv(filepath.Join(cwd, ".env")); dotenv != nil {
|
||||
for k, v := range dotenv {
|
||||
if _, exists := m[k]; !exists {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,8 +96,7 @@ func HandleChatCompletions(w http.ResponseWriter, r *http.Request, cfg config.Br
|
|||
return
|
||||
}
|
||||
if fit.Truncated {
|
||||
fmt.Printf("[%s] Windows: prompt truncated for CreateProcess limit (%d -> %d chars, tail preserved).\n",
|
||||
time.Now().UTC().Format(time.RFC3339), fit.OriginalLength, fit.FinalPromptLength)
|
||||
logger.LogTruncation(fit.OriginalLength, fit.FinalPromptLength)
|
||||
}
|
||||
|
||||
cmdArgs := fit.Args
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"cursor-api-proxy/internal/config"
|
||||
"cursor-api-proxy/internal/pool"
|
||||
"fmt"
|
||||
"os"
|
||||
|
|
@ -38,7 +39,11 @@ var roleEmoji = map[string]string{
|
|||
}
|
||||
|
||||
func ts() string {
|
||||
return cGray + time.Now().UTC().Format(time.RFC3339Nano) + cReset
|
||||
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 {
|
||||
|
|
@ -60,8 +65,66 @@ type TrafficMessage struct {
|
|||
Content string
|
||||
}
|
||||
|
||||
func LogServerStart(version, scheme, host string, port int, cfg config.BridgeConfig) {
|
||||
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 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)
|
||||
|
||||
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 LogIncoming(method, pathname, remoteAddress string) {
|
||||
fmt.Printf("[%s] Incoming: %s %s (from %s)\n", time.Now().UTC().Format(time.RFC3339), method, pathname, remoteAddress)
|
||||
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) {
|
||||
|
|
@ -69,7 +132,7 @@ func LogAccountAssigned(configDir string) {
|
|||
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)
|
||||
fmt.Printf("%s %s→%s account %s%s%s\n", ts(), cBCyan, cReset, cBold, name, cReset)
|
||||
}
|
||||
|
||||
func LogAccountStats(verbose bool, stats []pool.AccountStat) {
|
||||
|
|
@ -80,17 +143,17 @@ func LogAccountStats(verbose bool, stats []pool.AccountStat) {
|
|||
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)
|
||||
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("%sdim%serr:0%s", cDim, "", 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("%sdim%srl:0%s", cDim, "", cReset)
|
||||
rl := fmt.Sprintf("%srl:0%s", cDim, cReset)
|
||||
if s.TotalRateLimits > 0 {
|
||||
rl = fmt.Sprintf("%srl:%d%s", cYellow, s.TotalRateLimits, cReset)
|
||||
}
|
||||
|
|
@ -114,7 +177,7 @@ func LogTrafficRequest(verbose bool, model string, messages []TrafficMessage, is
|
|||
if !verbose {
|
||||
return
|
||||
}
|
||||
modeTag := fmt.Sprintf("%sdim%ssync%s", cDim, "", cReset)
|
||||
modeTag := fmt.Sprintf("%ssync%s", cDim, cReset)
|
||||
if isStream {
|
||||
modeTag = fmt.Sprintf("%s⚡ stream%s", cBCyan, cReset)
|
||||
}
|
||||
|
|
@ -142,7 +205,7 @@ func LogTrafficResponse(verbose bool, model, text string, isStream bool) {
|
|||
if !verbose {
|
||||
return
|
||||
}
|
||||
modeTag := fmt.Sprintf("%sdim%ssync%s", cDim, "", cReset)
|
||||
modeTag := fmt.Sprintf("%ssync%s", cDim, cReset)
|
||||
if isStream {
|
||||
modeTag = fmt.Sprintf("%s⚡ stream%s", cBGreen, cReset)
|
||||
}
|
||||
|
|
@ -166,9 +229,14 @@ func AppendSessionLine(logPath, method, pathname, remoteAddress string, statusCo
|
|||
}
|
||||
}
|
||||
|
||||
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] Agent error: %s\n", time.Now().UTC().Format(time.RFC3339), errMsg)
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"cursor-api-proxy/internal/handlers"
|
||||
"cursor-api-proxy/internal/pool"
|
||||
"cursor-api-proxy/internal/process"
|
||||
"cursor-api-proxy/internal/logger"
|
||||
"cursor-api-proxy/internal/router"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
|
@ -98,34 +99,7 @@ func startSingleServer(opts ServerOptions) *http.Server {
|
|||
}
|
||||
}()
|
||||
|
||||
fmt.Printf("cursor-api-proxy listening on %s://%s:%d\n", scheme, cfg.Host, cfg.Port)
|
||||
fmt.Printf("- agent bin: %s\n", cfg.AgentBin)
|
||||
fmt.Printf("- workspace: %s\n", cfg.Workspace)
|
||||
fmt.Printf("- mode: %s\n", cfg.Mode)
|
||||
fmt.Printf("- default model: %s\n", cfg.DefaultModel)
|
||||
fmt.Printf("- force: %v\n", cfg.Force)
|
||||
fmt.Printf("- approve mcps: %v\n", cfg.ApproveMcps)
|
||||
fmt.Printf("- required api key: %v\n", cfg.RequiredKey != "")
|
||||
fmt.Printf("- sessions log: %s\n", cfg.SessionsLogPath)
|
||||
if cfg.ChatOnlyWorkspace {
|
||||
fmt.Println("- chat-only workspace: yes (isolated temp dir)")
|
||||
} else {
|
||||
fmt.Println("- chat-only workspace: no")
|
||||
}
|
||||
if cfg.Verbose {
|
||||
fmt.Println("- verbose traffic: yes (CURSOR_BRIDGE_VERBOSE=true)")
|
||||
} else {
|
||||
fmt.Println("- verbose traffic: no")
|
||||
}
|
||||
if cfg.MaxMode {
|
||||
fmt.Println("- max mode: yes (CURSOR_BRIDGE_MAX_MODE=true)")
|
||||
} else {
|
||||
fmt.Println("- max mode: no")
|
||||
}
|
||||
fmt.Printf("- Windows cmdline budget: %d (prompt tail truncation when over limit; Windows only)\n", cfg.WinCmdlineMax)
|
||||
if len(cfg.ConfigDirs) > 0 {
|
||||
fmt.Printf("- account pool: enabled with %d configuration directories\n", len(cfg.ConfigDirs))
|
||||
}
|
||||
logger.LogServerStart(opts.Version, scheme, cfg.Host, cfg.Port, cfg)
|
||||
|
||||
return srv
|
||||
}
|
||||
|
|
@ -136,8 +110,7 @@ func SetupGracefulShutdown(servers []*http.Server, timeoutMs int) {
|
|||
|
||||
go func() {
|
||||
sig := <-sigCh
|
||||
fmt.Printf("\n[%s] %s received — shutting down gracefully…\n",
|
||||
time.Now().UTC().Format(time.RFC3339), sig)
|
||||
logger.LogShutdown(sig.String())
|
||||
|
||||
process.KillAllChildProcesses()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue