Skip to content

Commit 43a9050

Browse files
committed
feat: add debug logs
1 parent 1f04ecf commit 43a9050

File tree

2 files changed

+77
-14
lines changed

2 files changed

+77
-14
lines changed

src/start.ts

+69-14
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { bold, dim, green, log } from '@stacksjs/cli'
1111
import { version } from '../package.json'
1212
import { generateCertficate } from './certificate'
1313
import { config } from './config'
14+
import { debugLog } from './utils'
1415

1516
// Keep track of all running servers for cleanup
1617
const activeServers: Set<http.Server | https.Server> = new Set()
@@ -19,6 +20,7 @@ const activeServers: Set<http.Server | https.Server> = new Set()
1920
* Cleanup function to close all servers and exit gracefully
2021
*/
2122
function cleanup() {
23+
debugLog('cleanup', 'Starting cleanup process')
2224
console.log(`\n`)
2325
log.info('Shutting down proxy servers...')
2426

@@ -29,9 +31,11 @@ function cleanup() {
2931
)
3032

3133
Promise.all(closePromises).then(() => {
34+
debugLog('cleanup', 'All servers closed successfully')
3235
log.success('All proxy servers shut down successfully')
3336
process.exit(0)
3437
}).catch((err) => {
38+
debugLog('cleanup', `Error during cleanup: ${err.message}`)
3539
log.error('Error during shutdown:', err)
3640
process.exit(1)
3741
})
@@ -41,6 +45,7 @@ function cleanup() {
4145
process.on('SIGINT', cleanup)
4246
process.on('SIGTERM', cleanup)
4347
process.on('uncaughtException', (err) => {
48+
debugLog('process', `Uncaught exception: ${err.message}`)
4449
log.error('Uncaught exception:', err)
4550
cleanup()
4651
})
@@ -49,14 +54,20 @@ process.on('uncaughtException', (err) => {
4954
* Load SSL certificates from files or use provided strings
5055
*/
5156
async function loadSSLConfig(options: ReverseProxyOption): Promise<SSLConfig | null> {
57+
debugLog('ssl', 'Loading SSL configuration')
58+
5259
// If HTTPS is explicitly disabled, return null
53-
if (options.https === false)
60+
if (options.https === false) {
61+
debugLog('ssl', 'HTTPS is disabled, skipping SSL configuration')
5462
return null
63+
}
5564

5665
// If HTTPS is true and paths are specified, they must exist
5766
if (options.https === true && (options.keyPath || options.certPath)) {
67+
debugLog('ssl', 'Loading SSL certificates from files')
5868
if (!options.keyPath || !options.certPath) {
5969
const missing = !options.keyPath ? 'keyPath' : 'certPath'
70+
debugLog('ssl', `Missing required SSL path: ${missing}`)
6071
throw new Error(`SSL Configuration requires both keyPath and certPath. Missing: ${missing}`)
6172
}
6273

@@ -76,14 +87,17 @@ async function loadSSLConfig(options: ReverseProxyOption): Promise<SSLConfig | n
7687
// If caCertPath is specified, it must exist too
7788
let ca: string | undefined
7889
if (options.caCertPath) {
90+
debugLog('ssl', 'Loading CA certificate')
7991
try {
8092
ca = await fs.promises.readFile(options.caCertPath, 'utf8')
8193
}
8294
catch (err) {
95+
debugLog('ssl', `Failed to read CA certificate: ${(err as Error).message}`)
8396
throw new Error(`Failed to read CA certificate: ${(err as Error).message}`)
8497
}
8598
}
8699

100+
debugLog('ssl', 'SSL certificates loaded successfully')
87101
return { key, cert, ...(ca ? { ca } : {}) }
88102
}
89103
catch (err) {
@@ -92,40 +106,48 @@ async function loadSSLConfig(options: ReverseProxyOption): Promise<SSLConfig | n
92106
? `File not found: ${error.path}`
93107
: error.message
94108

109+
debugLog('ssl', `SSL Configuration Error: ${detail}`)
95110
throw new Error(`SSL Configuration Error:\n${detail}.\nHTTPS was explicitly enabled but certificate files are missing.`)
96111
}
97112
}
98113

99114
// If HTTPS is true but no paths specified, check for direct cert content
100115
if (options.https === true && options.key && options.cert) {
116+
debugLog('ssl', 'Using provided SSL certificate strings')
101117
return {
102118
key: options.key,
103119
cert: options.cert,
104120
}
105121
}
106122

107-
// If HTTPS is true but no certificates provided at all, throw error
108-
if (options.https === true)
123+
// If HTTPS is true but no certificates provided at all, generate certificate
124+
if (options.https === true) {
125+
debugLog('ssl', 'Generating self-signed certificate')
109126
return await generateCertficate(options)
127+
}
110128

111129
// Default to no SSL
130+
debugLog('ssl', 'No SSL configuration provided')
112131
return null
113132
}
114133

115134
/**
116135
* Check if a port is in use
117136
*/
118137
function isPortInUse(port: number, hostname: string): Promise<boolean> {
138+
debugLog('port', `Checking if port ${port} is in use on ${hostname}`)
119139
return new Promise((resolve) => {
120140
const server = net.createServer()
121141

122142
server.once('error', (err: NodeJS.ErrnoException) => {
123143
if (err.code === 'EADDRINUSE') {
144+
debugLog('port', `Port ${port} is in use`)
124145
resolve(true)
125146
}
126147
})
127148

128149
server.once('listening', () => {
150+
debugLog('port', `Port ${port} is available`)
129151
server.close()
130152
resolve(false)
131153
})
@@ -138,46 +160,50 @@ function isPortInUse(port: number, hostname: string): Promise<boolean> {
138160
* Find next available port
139161
*/
140162
async function findAvailablePort(startPort: number, hostname: string): Promise<number> {
163+
debugLog('port', `Finding available port starting from ${startPort}`)
141164
let port = startPort
142165
while (await isPortInUse(port, hostname)) {
143-
log.debug(`Port ${port} is in use, trying ${port + 1}`)
166+
debugLog('port', `Port ${port} is in use, trying ${port + 1}`)
144167
port++
145168
}
169+
debugLog('port', `Found available port: ${port}`)
146170
return port
147171
}
148172

149173
/**
150174
* Test connection to a server
151175
*/
152176
async function testConnection(hostname: string, port: number): Promise<void> {
177+
debugLog('connection', `Testing connection to ${hostname}:${port}`)
153178
return new Promise<void>((resolve, reject) => {
154-
log.debug(`Testing connection to ${hostname}:${port}...`)
155-
156179
const socket = net.connect({
157180
host: hostname,
158181
port,
159182
timeout: 5000, // 5 second timeout
160183
})
161184

162185
socket.once('connect', () => {
163-
log.debug(`Successfully connected to ${hostname}:${port}`)
186+
debugLog('connection', `Successfully connected to ${hostname}:${port}`)
164187
socket.end()
165188
resolve()
166189
})
167190

168191
socket.once('timeout', () => {
192+
debugLog('connection', `Connection to ${hostname}:${port} timed out`)
169193
socket.destroy()
170194
reject(new Error(`Connection to ${hostname}:${port} timed out`))
171195
})
172196

173197
socket.once('error', (err) => {
198+
debugLog('connection', `Failed to connect to ${hostname}:${port}: ${err.message}`)
174199
socket.destroy()
175200
reject(new Error(`Failed to connect to ${hostname}:${port}: ${err.message}`))
176201
})
177202
})
178203
}
179204

180205
export async function startServer(options?: ReverseProxyOption): Promise<void> {
206+
debugLog('server', `Starting server with options: ${JSON.stringify(options)}`)
181207
// Merge with default config
182208
const mergedOptions: ReverseProxyOption = {
183209
...config,
@@ -187,6 +213,8 @@ export async function startServer(options?: ReverseProxyOption): Promise<void> {
187213
const { fromUrl, toUrl, protocol } = normalizeUrls(mergedOptions)
188214
const fromPort = Number.parseInt(fromUrl.port) || (protocol.includes('https:') ? 443 : 80)
189215

216+
debugLog('server', `Normalized URLs - From: ${fromUrl}, To: ${toUrl}, Protocol: ${protocol}`)
217+
190218
// Load SSL config first to fail fast if certificates are missing
191219
const sslConfig = await loadSSLConfig(mergedOptions)
192220

@@ -195,6 +223,7 @@ export async function startServer(options?: ReverseProxyOption): Promise<void> {
195223
await testConnection(fromUrl.hostname, fromPort)
196224
}
197225
catch (err) {
226+
debugLog('server', `Connection test failed: ${(err as Error).message}`)
198227
log.error((err as Error).message)
199228
process.exit(1)
200229
}
@@ -222,9 +251,11 @@ async function createProxyServer(
222251
ssl: SSLConfig | null,
223252
options: ReverseProxyOption,
224253
): Promise<void> {
254+
debugLog('proxy', `Creating proxy server ${from} -> ${to}`)
225255
const useHttps = options.https === true
226256

227257
const requestHandler = (req: http.IncomingMessage, res: http.ServerResponse) => {
258+
debugLog('request', `${req.method} ${req.url}`)
228259
const proxyOptions = {
229260
hostname: sourceUrl.hostname,
230261
port: fromPort,
@@ -237,6 +268,7 @@ async function createProxyServer(
237268
}
238269

239270
const proxyReq = http.request(proxyOptions, (proxyRes) => {
271+
debugLog('response', `${proxyRes.statusCode} ${req.url}`)
240272
// Add security headers
241273
const headers = {
242274
...proxyRes.headers,
@@ -249,6 +281,7 @@ async function createProxyServer(
249281
})
250282

251283
proxyReq.on('error', (err) => {
284+
debugLog('proxy', `Proxy request failed: ${err.message}`)
252285
log.error('Proxy request failed:', err)
253286
res.writeHead(502)
254287
res.end(`Proxy Error: ${err.message}`)
@@ -286,19 +319,15 @@ async function createProxyServer(
286319

287320
if (ssl) {
288321
server.on('secureConnection', (tlsSocket) => {
289-
log.debug('TLS Connection:', {
290-
protocol: tlsSocket.getProtocol?.(),
291-
cipher: tlsSocket.getCipher?.(),
292-
authorized: tlsSocket.authorized,
293-
authError: tlsSocket.authorizationError,
294-
})
322+
debugLog('tls', `TLS Connection - Protocol: ${tlsSocket.getProtocol?.()}, Cipher: ${tlsSocket.getCipher?.()}, Authorized: ${tlsSocket.authorized}`)
295323
})
296324
}
297325

298326
activeServers.add(server)
299327

300328
return new Promise((resolve, reject) => {
301329
server.listen(listenPort, hostname, () => {
330+
debugLog('server', `Server listening on port ${listenPort}`)
302331
console.log('')
303332
console.log(` ${green(bold('reverse-proxy'))} ${green(`v${version}`)}`)
304333
console.log('')
@@ -316,11 +345,15 @@ async function createProxyServer(
316345
resolve()
317346
})
318347

319-
server.on('error', reject)
348+
server.on('error', (err) => {
349+
debugLog('server', `Server error: ${(err as Error).message}`)
350+
reject(err)
351+
})
320352
})
321353
}
322354

323355
export async function setupReverseProxy(options: ProxySetupOptions): Promise<void> {
356+
debugLog('setup', `Setting up reverse proxy with options: ${JSON.stringify(options)}`)
324357
const { from, to, fromPort, sourceUrl, ssl } = options
325358
const httpPort = 80
326359
const httpsPort = 443
@@ -329,39 +362,48 @@ export async function setupReverseProxy(options: ProxySetupOptions): Promise<voi
329362

330363
try {
331364
if (useHttps && ssl) {
365+
debugLog('setup', 'Checking HTTP port for redirect server')
332366
const isHttpPortBusy = await isPortInUse(httpPort, hostname)
333367
if (!isHttpPortBusy) {
368+
debugLog('setup', 'Starting HTTP redirect server')
334369
startHttpRedirectServer()
335370
}
336371
else {
372+
debugLog('setup', 'Port 80 is in use, skipping HTTP redirect server')
337373
log.warn('Port 80 is in use, HTTP to HTTPS redirect will not be available')
338374
}
339375
}
340376

341377
const targetPort = useHttps ? httpsPort : httpPort
378+
debugLog('setup', `Checking if target port ${targetPort} is available`)
342379
const isTargetPortBusy = await isPortInUse(targetPort, hostname)
343380

344381
if (isTargetPortBusy) {
382+
debugLog('setup', `Target port ${targetPort} is busy, finding alternative`)
345383
const availablePort = await findAvailablePort(useHttps ? 8443 : 8080, hostname)
346384
log.warn(`Port ${targetPort} is in use. Using port ${availablePort} instead.`)
347385
log.info(`You can use 'sudo lsof -i :${targetPort}' (Unix) or 'netstat -ano | findstr :${targetPort}' (Windows) to check what's using the port.`)
348386

349387
await createProxyServer(from, to, fromPort, availablePort, hostname, sourceUrl, ssl, options)
350388
}
351389
else {
390+
debugLog('setup', `Creating proxy server on port ${targetPort}`)
352391
await createProxyServer(from, to, fromPort, targetPort, hostname, sourceUrl, ssl, options)
353392
}
354393
}
355394
catch (err) {
395+
debugLog('setup', `Failed to setup reverse proxy: ${(err as Error).message}`)
356396
log.error(`Failed to setup reverse proxy: ${(err as Error).message}`)
357397
cleanup()
358398
}
359399
}
360400

361401
export function startHttpRedirectServer(): void {
402+
debugLog('redirect', 'Starting HTTP redirect server')
362403
const server = http
363404
.createServer((req, res) => {
364405
const host = req.headers.host || ''
406+
debugLog('redirect', `Redirecting ${req.url} to https://${host}${req.url}`)
365407
res.writeHead(301, {
366408
Location: `https://${host}${req.url}`,
367409
})
@@ -373,13 +415,15 @@ export function startHttpRedirectServer(): void {
373415
}
374416

375417
export function startProxy(options?: ReverseProxyOption): void {
418+
debugLog('proxy', `Starting proxy with options: ${JSON.stringify(options)}`)
376419
const finalOptions = {
377420
...config,
378421
...options,
379422
}
380423

381424
// Remove SSL-related properties if HTTPS is disabled
382425
if (finalOptions.https === false) {
426+
debugLog('proxy', 'HTTPS disabled, removing SSL options')
383427
delete finalOptions.keyPath
384428
delete finalOptions.certPath
385429
delete finalOptions.caCertPath
@@ -399,21 +443,27 @@ export function startProxy(options?: ReverseProxyOption): void {
399443
})
400444

401445
startServer(finalOptions).catch((err) => {
446+
debugLog('proxy', `Failed to start proxy: ${err.message}`)
402447
log.error(`Failed to start proxy: ${err.message}`)
403448
cleanup()
404449
})
405450
}
406451

407452
export function startProxies(options?: ReverseProxyOptions): void {
453+
debugLog('proxies', 'Starting multiple proxies')
408454
if (Array.isArray(options)) {
455+
debugLog('proxies', `Starting ${options.length} proxies`)
409456
Promise.all(options.map(option => startServer(option)))
410457
.catch((err) => {
458+
debugLog('proxies', `Failed to start proxies: ${err.message}`)
411459
log.error('Failed to start proxies:', err)
412460
cleanup()
413461
})
414462
}
415463
else if (options) {
464+
debugLog('proxies', 'Starting single proxy')
416465
startServer(options).catch((err) => {
466+
debugLog('proxies', `Failed to start proxy: ${err.message}`)
417467
log.error('Failed to start proxy:', err)
418468
cleanup()
419469
})
@@ -424,13 +474,16 @@ export function startProxies(options?: ReverseProxyOptions): void {
424474
* Create normalized URLs with correct protocol
425475
*/
426476
function normalizeUrls(options: ReverseProxyOption) {
477+
debugLog('urls', 'Normalizing URLs')
427478
const useHttps = options.https === true
428479
const protocol = useHttps ? 'https://' : 'http://'
429480

430481
// Use default values if from/to are undefined
431482
const fromString = options.from || 'localhost:5173'
432483
const toString = options.to || 'stacks.localhost'
433484

485+
debugLog('urls', `Original URLs - From: ${fromString}, To: ${toString}`)
486+
434487
const fromUrl = new URL(fromString.startsWith('http')
435488
? fromString
436489
: `${protocol}${fromString}`)
@@ -439,6 +492,8 @@ function normalizeUrls(options: ReverseProxyOption) {
439492
? toString
440493
: `${protocol}${toString}`)
441494

495+
debugLog('urls', `Normalized URLs - From: ${fromUrl}, To: ${toUrl}, Protocol: ${protocol}`)
496+
442497
return {
443498
fromUrl,
444499
toUrl,

src/utils.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { config } from './config'
2+
3+
export function debugLog(category: string, message: string): void {
4+
if (config.verbose) {
5+
// eslint-disable-next-line no-console
6+
console.debug(`[rpx:${category}] ${message}`)
7+
}
8+
}

0 commit comments

Comments
 (0)