feat: add structured logging across proxy pool and fetch utils

- fetch-utils.ts: logProxy() — logs every attempt, proxy used, success/failure
  with sessionId prefix for traceability
- proxy-pool.ts: logPool() — logs acquire/release/rotate/markFailed/markSuccess
  with active session count and proxy host info
- Both use consistent [prefix] HH:MM:SS.mmm key=value format

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-17 04:29:31 +07:00
co-authored by Claude
parent 519fe3af83
commit 45f1b33fb6
2 changed files with 95 additions and 9 deletions
+43 -8
View File
@@ -19,6 +19,19 @@ export interface ProxyEntry {
// --- ProxyPool ---------------------------------------------------------------
const POOL_PREFIX = "[proxy-pool]";
function logPool(msg: string, extra?: Record<string, unknown>): void {
const ts = new Date().toISOString().slice(11, 23);
const parts = [`${POOL_PREFIX} ${ts}`, msg];
if (extra) {
for (const [k, v] of Object.entries(extra)) {
parts.push(`${k}=${v ?? "null"}`);
}
}
console.log(parts.join(" "));
}
export class ProxyPool {
private proxies: ProxyEntry[] = [];
private currentIndex = 0;
@@ -71,11 +84,7 @@ export class ProxyPool {
this.currentIndex = 0;
this.failures.clear();
if (this.proxies.length > 0) {
console.log(
`[proxy-pool] Loaded ${this.proxies.length} proxies from ${filePath}`,
);
}
logPool(`loaded ${this.proxies.length} proxies from ${filePath}`);
}
/**
@@ -112,7 +121,9 @@ export class ProxyPool {
getProxyUrl(): string | null {
const entry = this.getCurrent();
if (!entry) return null;
return this.formatProxyUrl(entry);
const url = this.formatProxyUrl(entry);
logPool(`getProxyUrl -> ${entry.host}:${entry.port}`, { index: this.currentIndex });
return url;
}
/** Build a `http://user:pass@host:port` URL from an entry. */
@@ -131,8 +142,11 @@ export class ProxyPool {
*/
rotate(): ProxyEntry | null {
if (this.proxies.length === 0) return null;
const oldIndex = this.currentIndex;
this.currentIndex = (this.currentIndex + 1) % this.proxies.length;
return this.proxies[this.currentIndex] ?? null;
const entry = this.proxies[this.currentIndex] ?? null;
logPool(`rotate ${oldIndex} -> ${this.currentIndex}`, { host: entry?.host });
return entry;
}
/**
@@ -152,6 +166,7 @@ export class ProxyPool {
this.failures.set(key, count);
const th = threshold ?? this.failureThreshold;
logPool(`markFailed ${key} (${count}/${th})`);
if (count >= th) {
console.warn(
`[proxy-pool] Proxy ${key} failed ${count}/${th} times -- skipping`,
@@ -164,7 +179,9 @@ export class ProxyPool {
const entry = this.getCurrent();
if (!entry) return true;
const key = `${entry.host}:${entry.port}`;
return (this.failures.get(key) ?? 0) >= (threshold ?? this.failureThreshold);
const failed = (this.failures.get(key) ?? 0) >= (threshold ?? this.failureThreshold);
if (failed) logPool(`isFailed true for ${key}`);
return failed;
}
/** Reset the failure counter for the current proxy. */
@@ -172,6 +189,7 @@ export class ProxyPool {
const entry = this.getCurrent();
if (!entry) return;
this.failures.delete(`${entry.host}:${entry.port}`);
logPool(`markSuccess ${entry.host}:${entry.port}`);
}
/** Set the failure count that triggers a permanent skip. */
@@ -215,6 +233,7 @@ export class SessionProxyPool {
if (poolOrPath) this.pool.load(poolOrPath);
}
this.failureThreshold = 3;
logPool(`SessionProxyPool created, poolSize=${this.pool.size}`);
}
/** Number of available proxies in the underlying pool. */
@@ -240,6 +259,7 @@ export class SessionProxyPool {
const existing = this.sessions.get(sessionId);
if (existing !== undefined) {
logPool(`acquire existing session=${sessionId.slice(0, 8)} proxyIndex=${existing.proxyIndex}`);
return this.formatProxyUrlAtIndex(existing.proxyIndex);
}
@@ -255,6 +275,8 @@ export class SessionProxyPool {
}
usedBy.add(sessionId);
const entry = this.poolEntryAtIndex(index);
logPool(`acquire session=${sessionId.slice(0, 8)} -> proxyIndex=${index} host=${entry?.host} activeSessions=${this.activeSessions}`);
return this.formatProxyUrlAtIndex(index);
}
@@ -262,6 +284,8 @@ export class SessionProxyPool {
getProxyUrl(sessionId: string): string | null {
const info = this.sessions.get(sessionId);
if (!info) return null;
const entry = this.poolEntryAtIndex(info.proxyIndex);
logPool(`getProxyUrl session=${sessionId.slice(0, 8)} proxyIndex=${info.proxyIndex} host=${entry?.host}`);
return this.formatProxyUrlAtIndex(info.proxyIndex);
}
@@ -277,6 +301,8 @@ export class SessionProxyPool {
}
this.sessions.delete(sessionId);
const entry = this.poolEntryAtIndex(info.proxyIndex);
logPool(`release session=${sessionId.slice(0, 8)} proxyIndex=${info.proxyIndex} host=${entry?.host} activeSessions=${this.activeSessions}`);
}
/**
@@ -292,9 +318,12 @@ export class SessionProxyPool {
if (!info) return false;
info.failures += 1;
logPool(`markFailed session=${sessionId.slice(0, 8)} proxyIndex=${info.proxyIndex} failures=${info.failures}/${this.failureThreshold}`);
if (info.failures < this.failureThreshold) return false;
const oldIndex = info.proxyIndex;
const oldEntry = this.poolEntryAtIndex(oldIndex);
// Mark in underlying pool (public API only works on currentIndex)
const savedIdx = (this.pool as any).currentIndex as number;
@@ -314,6 +343,7 @@ export class SessionProxyPool {
if (newIndex === -1 || newIndex === oldIndex) {
// Single-proxy pool or none available — reset failures, stay put
this.sessions.set(sessionId, { proxyIndex: oldIndex, failures: 0 });
logPool(`markFailed no alternative, staying on oldIndex=${oldIndex} host=${oldEntry?.host}`);
return false;
}
@@ -326,6 +356,8 @@ export class SessionProxyPool {
}
newUsedBy.add(sessionId);
const newEntry = this.poolEntryAtIndex(newIndex);
logPool(`markFailed rotated session=${sessionId.slice(0, 8)} ${oldEntry?.host} -> ${newEntry?.host}`);
return true;
}
@@ -334,6 +366,7 @@ export class SessionProxyPool {
const info = this.sessions.get(sessionId);
if (!info) return;
info.failures = 0;
logPool(`markSuccess session=${sessionId.slice(0, 8)} proxyIndex=${info.proxyIndex}`);
}
// -- Internals ----------------------------------------------------------------
@@ -362,12 +395,14 @@ export class SessionProxyPool {
for (let i = 0; i < this.pool.size; i++) {
const count = this.proxyUsage.get(i)?.size ?? 0;
logPool(`pickLeastUsed proxy[${i}] count=${count}`);
if (count < bestCount) {
bestCount = count;
bestIndex = i;
}
}
logPool(`pickLeastUsed selected index=${bestIndex} count=${bestCount}`);
return bestIndex;
}