fix(core): Improve logging and error handling by introducing custom error classes and a global logging interface while refactoring network diagnostics methods.

This commit is contained in:
2025-04-28 15:30:08 +00:00
parent bc19c21949
commit d6c0af35fa
6 changed files with 115 additions and 61 deletions

View File

@@ -1,4 +1,6 @@
import * as plugins from './smartnetwork.plugins.js';
import { getLogger } from './logging.js';
import { NetworkError, TimeoutError } from './errors.js';
import * as stats from './helpers/stats.js';
export class CloudflareSpeed {
@@ -49,7 +51,7 @@ export class CloudflareSpeed {
measurements.push(response[4] - response[0] - response[6]);
},
(error) => {
console.log(`Error: ${error}`);
getLogger().error('Error measuring latency:', error);
},
);
}
@@ -73,7 +75,7 @@ export class CloudflareSpeed {
measurements.push(await this.measureSpeed(bytes, transferTime));
},
(error) => {
console.log(`Error: ${error}`);
getLogger().error('Error measuring download chunk:', error);
},
);
}
@@ -91,7 +93,7 @@ export class CloudflareSpeed {
measurements.push(await this.measureSpeed(bytes, transferTime));
},
(error) => {
console.log(`Error: ${error}`);
getLogger().error('Error measuring upload chunk:', error);
},
);
}
@@ -104,15 +106,16 @@ export class CloudflareSpeed {
}
public async fetchServerLocations(): Promise<{ [key: string]: string }> {
const res = JSON.parse(await this.get('speed.cloudflare.com', '/locations'));
return res.reduce((data: any, optionsArg: { iata: string; city: string }) => {
// Bypass prettier "no-assign-param" rules
const data1 = data;
data1[optionsArg.iata] = optionsArg.city;
return data1;
}, {});
const res = JSON.parse(
await this.get('speed.cloudflare.com', '/locations'),
) as Array<{ iata: string; city: string }>;
return res.reduce(
(data: Record<string, string>, optionsArg) => {
data[optionsArg.iata] = optionsArg.city;
return data;
},
{} as Record<string, string>,
);
}
public async get(hostname: string, path: string): Promise<string> {
@@ -122,6 +125,8 @@ export class CloudflareSpeed {
hostname,
path,
method: 'GET',
// disable connection pooling to avoid listener accumulation
agent: false,
},
(res) => {
const body: Array<Buffer> = [];
@@ -135,9 +140,9 @@ export class CloudflareSpeed {
reject(e);
}
});
req.on('error', (err) => {
reject(err);
});
req.on('error', (err: Error & { code?: string }) => {
reject(new NetworkError(err.message, err.code));
});
},
);
@@ -179,33 +184,36 @@ export class CloudflareSpeed {
return new Promise((resolve, reject) => {
started = plugins.perfHooks.performance.now();
const req = plugins.https.request(options, (res) => {
// disable connection pooling to avoid listener accumulation across requests
const reqOptions = { ...options, agent: false };
const req = plugins.https.request(reqOptions, (res) => {
res.once('readable', () => {
ttfb = plugins.perfHooks.performance.now();
});
res.on('data', () => {});
res.on('end', () => {
ended = plugins.perfHooks.performance.now();
resolve([
started,
dnsLookup,
tcpHandshake,
sslHandshake,
ttfb,
ended,
parseFloat(res.headers['server-timing'].slice(22) as any),
]);
resolve([
started,
dnsLookup,
tcpHandshake,
sslHandshake,
ttfb,
ended,
parseFloat((res.headers['server-timing'] as string).slice(22)),
]);
});
});
req.on('socket', (socket) => {
socket.on('lookup', () => {
// Listen for timing events once per new socket
req.once('socket', (socket) => {
socket.once('lookup', () => {
dnsLookup = plugins.perfHooks.performance.now();
});
socket.on('connect', () => {
socket.once('connect', () => {
tcpHandshake = plugins.perfHooks.performance.now();
});
socket.on('secureConnect', () => {
socket.once('secureConnect', () => {
sslHandshake = plugins.perfHooks.performance.now();
});
});
@@ -238,20 +246,14 @@ export class CloudflareSpeed {
text
.split('\n')
.map((i) => {
const j = i.split('=');
return [j[0], j[1]];
const parts = i.split('=');
return [parts[0], parts[1]];
})
.reduce((data: any, [k, v]) => {
.reduce((data: Record<string, string>, [k, v]) => {
if (v === undefined) return data;
// Bypass prettier "no-assign-param" rules
const data1 = data;
// Object.fromEntries is only supported by Node.js 12 or newer
data1[k] = v;
return data1;
}, {});
data[k] = v;
return data;
}, {} as Record<string, string>);
return this.get('speed.cloudflare.com', '/cdn-cgi/trace').then(parseCfCdnCgiTrace);
}