fix(logger): Replace raw console logging calls with structured logger usage across certificate management, connection handling, and route processing for improved observability.

This commit is contained in:
Philipp Kunz 2025-05-19 23:37:11 +00:00
parent e61766959f
commit c9abdea556
6 changed files with 456 additions and 240 deletions

View File

@ -1,5 +1,13 @@
# Changelog
## 2025-05-19 - 19.3.11 - fix(logger)
Replace raw console logging calls with structured logger usage across certificate management, connection handling, and route processing for improved observability.
- Replaced console.log, console.warn, and console.error in SmartCertManager with logger.log for more consistent logging.
- Updated ConnectionManager and RouteConnectionHandler to log detailed connection events using a structured logger.
- Enhanced logging statements with contextual metadata such as connection IDs, remote IPs, target information, and component identifiers.
- Standardized log output across proxy modules to aid in debugging and monitoring.
## 2025-05-19 - 19.3.10 - fix(certificate-manager, smart-proxy)
Fix race condition in ACME certificate provisioning and refactor certificate manager initialization to defer provisioning until after port listeners are active

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '19.3.10',
version: '19.3.11',
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
}

View File

@ -4,6 +4,7 @@ import type { IRouteConfig, IRouteTls } from './models/route-types.js';
import type { IAcmeOptions } from './models/interfaces.js';
import { CertStore } from './cert-store.js';
import type { AcmeStateManager } from './acme-state-manager.js';
import { logger } from '../../core/utils/logger.js';
export interface ICertStatus {
domain: string;
@ -125,16 +126,16 @@ export class SmartCertManager {
// Add challenge route once at initialization if not already active
if (!this.challengeRouteActive) {
console.log('Adding ACME challenge route during initialization');
logger.log('info', 'Adding ACME challenge route during initialization', { component: 'certificate-manager' });
await this.addChallengeRoute();
} else {
console.log('Challenge route already active from previous instance');
logger.log('info', 'Challenge route already active from previous instance', { component: 'certificate-manager' });
}
}
// Skip automatic certificate provisioning during initialization
// This will be called later after ports are listening
console.log('Certificate manager initialized. Deferring certificate provisioning until after ports are listening.');
logger.log('info', 'Certificate manager initialized. Deferring certificate provisioning until after ports are listening.', { component: 'certificate-manager' });
// Start renewal timer
this.startRenewalTimer();
@ -157,7 +158,7 @@ export class SmartCertManager {
try {
await this.provisionCertificate(route, true); // Allow concurrent since we're managing it here
} catch (error) {
console.error(`Failed to provision certificate for route ${route.name}: ${error}`);
logger.log('error', `Failed to provision certificate for route ${route.name}`, { routeName: route.name, error, component: 'certificate-manager' });
}
}
} finally {
@ -176,13 +177,13 @@ export class SmartCertManager {
// Check if provisioning is already in progress (prevent concurrent provisioning)
if (!allowConcurrent && this.isProvisioning) {
console.log(`Certificate provisioning already in progress, skipping ${route.name}`);
logger.log('info', `Certificate provisioning already in progress, skipping ${route.name}`, { routeName: route.name, component: 'certificate-manager' });
return;
}
const domains = this.extractDomainsFromRoute(route);
if (domains.length === 0) {
console.warn(`Route ${route.name} has TLS termination but no domains`);
logger.log('warn', `Route ${route.name} has TLS termination but no domains`, { routeName: route.name, component: 'certificate-manager' });
return;
}
@ -219,7 +220,7 @@ export class SmartCertManager {
// Check if we already have a valid certificate
const existingCert = await this.certStore.getCertificate(routeName);
if (existingCert && this.isCertificateValid(existingCert)) {
console.log(`Using existing valid certificate for ${primaryDomain}`);
logger.log('info', `Using existing valid certificate for ${primaryDomain}`, { domain: primaryDomain, component: 'certificate-manager' });
await this.applyCertificate(primaryDomain, existingCert);
this.updateCertStatus(routeName, 'valid', 'acme', existingCert);
return;
@ -230,7 +231,7 @@ export class SmartCertManager {
this.globalAcmeDefaults?.renewThresholdDays ||
30;
console.log(`Requesting ACME certificate for ${domains.join(', ')} (renew ${renewThreshold} days before expiry)`);
logger.log('info', `Requesting ACME certificate for ${domains.join(', ')} (renew ${renewThreshold} days before expiry)`, { domains: domains.join(', '), renewThreshold, component: 'certificate-manager' });
this.updateCertStatus(routeName, 'pending', 'acme');
try {
@ -252,7 +253,7 @@ export class SmartCertManager {
hasDnsChallenge;
if (shouldIncludeWildcard) {
console.log(`Requesting wildcard certificate for ${primaryDomain} (DNS-01 available)`);
logger.log('info', `Requesting wildcard certificate for ${primaryDomain} (DNS-01 available)`, { domain: primaryDomain, challengeType: 'DNS-01', component: 'certificate-manager' });
}
// Use smartacme to get certificate with optional wildcard
@ -279,9 +280,9 @@ export class SmartCertManager {
await this.applyCertificate(primaryDomain, certData);
this.updateCertStatus(routeName, 'valid', 'acme', certData);
console.log(`Successfully provisioned ACME certificate for ${primaryDomain}`);
logger.log('info', `Successfully provisioned ACME certificate for ${primaryDomain}`, { domain: primaryDomain, component: 'certificate-manager' });
} catch (error) {
console.error(`Failed to provision ACME certificate for ${primaryDomain}: ${error}`);
logger.log('error', `Failed to provision ACME certificate for ${primaryDomain}: ${error.message}`, { domain: primaryDomain, error: error.message, component: 'certificate-manager' });
this.updateCertStatus(routeName, 'error', 'acme', undefined, error.message);
throw error;
}
@ -328,9 +329,9 @@ export class SmartCertManager {
await this.applyCertificate(domain, certData);
this.updateCertStatus(routeName, 'valid', 'static', certData);
console.log(`Successfully loaded static certificate for ${domain}`);
logger.log('info', `Successfully loaded static certificate for ${domain}`, { domain, component: 'certificate-manager' });
} catch (error) {
console.error(`Failed to provision static certificate for ${domain}: ${error}`);
logger.log('error', `Failed to provision static certificate for ${domain}: ${error.message}`, { domain, error: error.message, component: 'certificate-manager' });
this.updateCertStatus(routeName, 'error', 'static', undefined, error.message);
throw error;
}
@ -341,7 +342,7 @@ export class SmartCertManager {
*/
private async applyCertificate(domain: string, certData: ICertificateData): Promise<void> {
if (!this.httpProxy) {
console.warn('HttpProxy not set, cannot apply certificate');
logger.log('warn', `HttpProxy not set, cannot apply certificate for domain ${domain}`, { domain, component: 'certificate-manager' });
return;
}
@ -398,13 +399,13 @@ export class SmartCertManager {
private async addChallengeRoute(): Promise<void> {
// Check with state manager first
if (this.acmeStateManager && this.acmeStateManager.isChallengeRouteActive()) {
console.log('Challenge route already active in global state, skipping');
logger.log('info', 'Challenge route already active in global state, skipping', { component: 'certificate-manager' });
this.challengeRouteActive = true;
return;
}
if (this.challengeRouteActive) {
console.log('Challenge route already active locally, skipping');
logger.log('info', 'Challenge route already active locally, skipping', { component: 'certificate-manager' });
return;
}
@ -427,9 +428,9 @@ export class SmartCertManager {
this.acmeStateManager.addChallengeRoute(challengeRoute);
}
console.log('ACME challenge route successfully added');
logger.log('info', 'ACME challenge route successfully added', { component: 'certificate-manager' });
} catch (error) {
console.error('Failed to add challenge route:', error);
logger.log('error', `Failed to add challenge route: ${error.message}`, { error: error.message, component: 'certificate-manager' });
if ((error as any).code === 'EADDRINUSE') {
throw new Error(`Port ${this.globalAcmeDefaults?.port || 80} is already in use for ACME challenges`);
}
@ -442,7 +443,7 @@ export class SmartCertManager {
*/
private async removeChallengeRoute(): Promise<void> {
if (!this.challengeRouteActive) {
console.log('Challenge route not active, skipping removal');
logger.log('info', 'Challenge route not active, skipping removal', { component: 'certificate-manager' });
return;
}
@ -460,9 +461,9 @@ export class SmartCertManager {
this.acmeStateManager.removeChallengeRoute('acme-challenge');
}
console.log('ACME challenge route successfully removed');
logger.log('info', 'ACME challenge route successfully removed', { component: 'certificate-manager' });
} catch (error) {
console.error('Failed to remove challenge route:', error);
logger.log('error', `Failed to remove challenge route: ${error.message}`, { error: error.message, component: 'certificate-manager' });
// Reset the flag even on error to avoid getting stuck
this.challengeRouteActive = false;
throw error;
@ -492,11 +493,11 @@ export class SmartCertManager {
const cert = await this.certStore.getCertificate(routeName);
if (cert && !this.isCertificateValid(cert)) {
console.log(`Certificate for ${routeName} needs renewal`);
logger.log('info', `Certificate for ${routeName} needs renewal`, { routeName, component: 'certificate-manager' });
try {
await this.provisionCertificate(route);
} catch (error) {
console.error(`Failed to renew certificate for ${routeName}: ${error}`);
logger.log('error', `Failed to renew certificate for ${routeName}: ${error.message}`, { routeName, error: error.message, component: 'certificate-manager' });
}
}
}
@ -621,7 +622,7 @@ export class SmartCertManager {
// Always remove challenge route on shutdown
if (this.challengeRoute) {
console.log('Removing ACME challenge route during shutdown');
logger.log('info', 'Removing ACME challenge route during shutdown', { component: 'certificate-manager' });
await this.removeChallengeRoute();
}

View File

@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
import { SecurityManager } from './security-manager.js';
import { TimeoutManager } from './timeout-manager.js';
import { logger } from '../../core/utils/logger.js';
/**
* Manages connection lifecycle, tracking, and cleanup
@ -97,7 +98,7 @@ export class ConnectionManager {
*/
public initiateCleanupOnce(record: IConnectionRecord, reason: string = 'normal'): void {
if (this.settings.enableDetailedLogging) {
console.log(`[${record.id}] Connection cleanup initiated for ${record.remoteIP} (${reason})`);
logger.log('info', `Connection cleanup initiated`, { connectionId: record.id, remoteIP: record.remoteIP, reason, component: 'connection-manager' });
}
if (
@ -139,7 +140,7 @@ export class ConnectionManager {
// Reset the handler references
record.renegotiationHandler = undefined;
} catch (err) {
console.log(`[${record.id}] Error removing data handlers: ${err}`);
logger.log('error', `Error removing data handlers for connection ${record.id}: ${err}`, { connectionId: record.id, error: err, component: 'connection-manager' });
}
}
@ -160,16 +161,36 @@ export class ConnectionManager {
// Log connection details
if (this.settings.enableDetailedLogging) {
console.log(
`[${record.id}] Connection from ${record.remoteIP} on port ${record.localPort} terminated (${reason}).` +
` Duration: ${plugins.prettyMs(duration)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}, ` +
`TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}` +
`${record.usingNetworkProxy ? ', Using NetworkProxy' : ''}` +
`${record.domainSwitches ? `, Domain switches: ${record.domainSwitches}` : ''}`
logger.log('info',
`Connection from ${record.remoteIP} on port ${record.localPort} terminated (${reason}). ` +
`Duration: ${plugins.prettyMs(duration)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}, ` +
`TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}` +
`${record.usingNetworkProxy ? ', Using NetworkProxy' : ''}` +
`${record.domainSwitches ? `, Domain switches: ${record.domainSwitches}` : ''}`,
{
connectionId: record.id,
remoteIP: record.remoteIP,
localPort: record.localPort,
reason,
duration: plugins.prettyMs(duration),
bytes: { in: bytesReceived, out: bytesSent },
tls: record.isTLS,
keepAlive: record.hasKeepAlive,
usingNetworkProxy: record.usingNetworkProxy,
domainSwitches: record.domainSwitches || 0,
component: 'connection-manager'
}
);
} else {
console.log(
`[${record.id}] Connection from ${record.remoteIP} terminated (${reason}). Active connections: ${this.connectionRecords.size}`
logger.log('info',
`Connection from ${record.remoteIP} terminated (${reason}). Active connections: ${this.connectionRecords.size}`,
{
connectionId: record.id,
remoteIP: record.remoteIP,
reason,
activeConnections: this.connectionRecords.size,
component: 'connection-manager'
}
);
}
}
@ -189,7 +210,7 @@ export class ConnectionManager {
socket.destroy();
}
} catch (err) {
console.log(`[${record.id}] Error destroying ${side} socket: ${err}`);
logger.log('error', `Error destroying ${side} socket for connection ${record.id}: ${err}`, { connectionId: record.id, side, error: err, component: 'connection-manager' });
}
}, 1000);
@ -199,13 +220,13 @@ export class ConnectionManager {
}
}
} catch (err) {
console.log(`[${record.id}] Error closing ${side} socket: ${err}`);
logger.log('error', `Error closing ${side} socket for connection ${record.id}: ${err}`, { connectionId: record.id, side, error: err, component: 'connection-manager' });
try {
if (!socket.destroyed) {
socket.destroy();
}
} catch (destroyErr) {
console.log(`[${record.id}] Error destroying ${side} socket: ${destroyErr}`);
logger.log('error', `Error destroying ${side} socket for connection ${record.id}: ${destroyErr}`, { connectionId: record.id, side, error: destroyErr, component: 'connection-manager' });
}
}
}
@ -224,21 +245,36 @@ export class ConnectionManager {
if (code === 'ECONNRESET') {
reason = 'econnreset';
console.log(
`[${record.id}] ECONNRESET on ${side} side from ${record.remoteIP}: ${err.message}. ` +
`Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)} ago`
);
logger.log('warn', `ECONNRESET on ${side} connection from ${record.remoteIP}. Error: ${err.message}. Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)}`, {
connectionId: record.id,
side,
remoteIP: record.remoteIP,
error: err.message,
duration: plugins.prettyMs(connectionDuration),
lastActivity: plugins.prettyMs(lastActivityAge),
component: 'connection-manager'
});
} else if (code === 'ETIMEDOUT') {
reason = 'etimedout';
console.log(
`[${record.id}] ETIMEDOUT on ${side} side from ${record.remoteIP}: ${err.message}. ` +
`Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)} ago`
);
logger.log('warn', `ETIMEDOUT on ${side} connection from ${record.remoteIP}. Error: ${err.message}. Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)}`, {
connectionId: record.id,
side,
remoteIP: record.remoteIP,
error: err.message,
duration: plugins.prettyMs(connectionDuration),
lastActivity: plugins.prettyMs(lastActivityAge),
component: 'connection-manager'
});
} else {
console.log(
`[${record.id}] Error on ${side} side from ${record.remoteIP}: ${err.message}. ` +
`Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)} ago`
);
logger.log('error', `Error on ${side} connection from ${record.remoteIP}: ${err.message}. Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)}`, {
connectionId: record.id,
side,
remoteIP: record.remoteIP,
error: err.message,
duration: plugins.prettyMs(connectionDuration),
lastActivity: plugins.prettyMs(lastActivityAge),
component: 'connection-manager'
});
}
if (side === 'incoming' && record.incomingTerminationReason === null) {
@ -259,7 +295,12 @@ export class ConnectionManager {
public handleClose(side: 'incoming' | 'outgoing', record: IConnectionRecord) {
return () => {
if (this.settings.enableDetailedLogging) {
console.log(`[${record.id}] Connection closed on ${side} side from ${record.remoteIP}`);
logger.log('info', `Connection closed on ${side} side`, {
connectionId: record.id,
side,
remoteIP: record.remoteIP,
component: 'connection-manager'
});
}
if (side === 'incoming' && record.incomingTerminationReason === null) {
@ -321,11 +362,13 @@ export class ConnectionManager {
if (inactivityTime > effectiveTimeout && !record.connectionClosed) {
// For keep-alive connections, issue a warning first
if (record.hasKeepAlive && !record.inactivityWarningIssued) {
console.log(
`[${id}] Warning: Keep-alive connection from ${record.remoteIP} inactive for ${
plugins.prettyMs(inactivityTime)
}. Will close in 10 minutes if no activity.`
);
logger.log('warn', `Keep-alive connection ${id} from ${record.remoteIP} inactive for ${plugins.prettyMs(inactivityTime)}. Will close in 10 minutes if no activity.`, {
connectionId: id,
remoteIP: record.remoteIP,
inactiveFor: plugins.prettyMs(inactivityTime),
closureWarning: '10 minutes',
component: 'connection-manager'
});
// Set warning flag and add grace period
record.inactivityWarningIssued = true;
@ -337,27 +380,30 @@ export class ConnectionManager {
record.outgoing.write(Buffer.alloc(0));
if (this.settings.enableDetailedLogging) {
console.log(`[${id}] Sent probe packet to test keep-alive connection`);
logger.log('info', `Sent probe packet to test keep-alive connection ${id}`, { connectionId: id, component: 'connection-manager' });
}
} catch (err) {
console.log(`[${id}] Error sending probe packet: ${err}`);
logger.log('error', `Error sending probe packet to connection ${id}: ${err}`, { connectionId: id, error: err, component: 'connection-manager' });
}
}
} else {
// For non-keep-alive or after warning, close the connection
console.log(
`[${id}] Inactivity check: No activity on connection from ${record.remoteIP} ` +
`for ${plugins.prettyMs(inactivityTime)}.` +
(record.hasKeepAlive ? ' Despite keep-alive being enabled.' : '')
);
logger.log('warn', `Closing inactive connection ${id} from ${record.remoteIP} (inactive for ${plugins.prettyMs(inactivityTime)}, keep-alive: ${record.hasKeepAlive ? 'Yes' : 'No'})`, {
connectionId: id,
remoteIP: record.remoteIP,
inactiveFor: plugins.prettyMs(inactivityTime),
hasKeepAlive: record.hasKeepAlive,
component: 'connection-manager'
});
this.cleanupConnection(record, 'inactivity');
}
} else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) {
// If activity detected after warning, clear the warning
if (this.settings.enableDetailedLogging) {
console.log(
`[${id}] Connection activity detected after inactivity warning, resetting warning`
);
logger.log('info', `Connection ${id} activity detected after inactivity warning`, {
connectionId: id,
component: 'connection-manager'
});
}
record.inactivityWarningIssued = false;
}
@ -369,11 +415,12 @@ export class ConnectionManager {
!record.connectionClosed &&
now - record.outgoingClosedTime > 120000
) {
console.log(
`[${id}] Parity check: Incoming socket for ${record.remoteIP} still active ${
plugins.prettyMs(now - record.outgoingClosedTime)
} after outgoing closed.`
);
logger.log('warn', `Parity check: Connection ${id} from ${record.remoteIP} has incoming socket still active ${plugins.prettyMs(now - record.outgoingClosedTime)} after outgoing socket closed`, {
connectionId: id,
remoteIP: record.remoteIP,
timeElapsed: plugins.prettyMs(now - record.outgoingClosedTime),
component: 'connection-manager'
});
this.cleanupConnection(record, 'parity_check');
}
}
@ -406,7 +453,7 @@ export class ConnectionManager {
record.outgoing.end();
}
} catch (err) {
console.log(`Error during graceful connection end for ${id}: ${err}`);
logger.log('error', `Error during graceful end of connection ${id}: ${err}`, { connectionId: id, error: err, component: 'connection-manager' });
}
}
}
@ -433,7 +480,7 @@ export class ConnectionManager {
}
}
} catch (err) {
console.log(`Error during forced connection destruction for ${id}: ${err}`);
logger.log('error', `Error during forced destruction of connection ${id}: ${err}`, { connectionId: id, error: err, component: 'connection-manager' });
}
}
}

View File

@ -1,5 +1,6 @@
import * as plugins from '../../plugins.js';
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
import { logger } from '../../core/utils/logger.js';
// Route checking functions have been removed
import type { IRouteConfig, IRouteAction, IRouteContext } from './models/route-types.js';
import { ConnectionManager } from './connection-manager.js';
@ -83,7 +84,7 @@ export class RouteConnectionHandler {
// Validate IP against rate limits and connection limits
const ipValidation = this.securityManager.validateIP(remoteIP);
if (!ipValidation.allowed) {
console.log(`Connection rejected from ${remoteIP}: ${ipValidation.reason}`);
logger.log('warn', `Connection rejected`, { remoteIP, reason: ipValidation.reason, component: 'route-handler' });
socket.end();
socket.destroy();
return;
@ -114,21 +115,35 @@ export class RouteConnectionHandler {
} catch (err) {
// Ignore errors - these are optional enhancements
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] Enhanced TCP keep-alive settings not supported: ${err}`);
logger.log('warn', `Enhanced TCP keep-alive settings not supported`, { connectionId, error: err, component: 'route-handler' });
}
}
}
}
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] New connection from ${remoteIP} on port ${localPort}. ` +
`Keep-Alive: ${record.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
`Active connections: ${this.connectionManager.getConnectionCount()}`
logger.log('info',
`New connection from ${remoteIP} on port ${localPort}. ` +
`Keep-Alive: ${record.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
`Active connections: ${this.connectionManager.getConnectionCount()}`,
{
connectionId,
remoteIP,
localPort,
keepAlive: record.hasKeepAlive ? 'Enabled' : 'Disabled',
activeConnections: this.connectionManager.getConnectionCount(),
component: 'route-handler'
}
);
} else {
console.log(
`New connection from ${remoteIP} on port ${localPort}. Active connections: ${this.connectionManager.getConnectionCount()}`
logger.log('info',
`New connection from ${remoteIP} on port ${localPort}. Active connections: ${this.connectionManager.getConnectionCount()}`,
{
remoteIP,
localPort,
activeConnections: this.connectionManager.getConnectionCount(),
component: 'route-handler'
}
);
}
@ -147,14 +162,20 @@ export class RouteConnectionHandler {
// Set an initial timeout for handshake data
let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
if (!initialDataReceived) {
console.log(
`[${connectionId}] Initial data warning (${this.settings.initialDataTimeout}ms) for connection from ${record.remoteIP}`
);
logger.log('warn', `No initial data received from ${record.remoteIP} after ${this.settings.initialDataTimeout}ms for connection ${connectionId}`, {
connectionId,
timeout: this.settings.initialDataTimeout,
remoteIP: record.remoteIP,
component: 'route-handler'
});
// Add a grace period
setTimeout(() => {
if (!initialDataReceived) {
console.log(`[${connectionId}] Final initial data timeout after grace period`);
logger.log('warn', `Final initial data timeout after grace period for connection ${connectionId}`, {
connectionId,
component: 'route-handler'
});
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = 'initial_timeout';
this.connectionManager.incrementTerminationStat('incoming', 'initial_timeout');
@ -187,10 +208,11 @@ export class RouteConnectionHandler {
// Block non-TLS connections on port 443
if (!this.tlsManager.isTlsHandshake(chunk) && localPort === 443) {
console.log(
`[${connectionId}] Non-TLS connection detected on port 443. ` +
`Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
);
logger.log('warn', `Non-TLS connection ${connectionId} detected on port 443. Terminating connection - only TLS traffic is allowed on standard HTTPS port.`, {
connectionId,
message: 'Terminating connection - only TLS traffic is allowed on standard HTTPS port.',
component: 'route-handler'
});
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = 'non_tls_blocked';
this.connectionManager.incrementTerminationStat('incoming', 'non_tls_blocked');
@ -223,7 +245,10 @@ export class RouteConnectionHandler {
// Check if we should reject connections without SNI
if (!serverName && this.settings.allowSessionTicket === false) {
console.log(`[${connectionId}] No SNI detected in TLS ClientHello; sending TLS alert.`);
logger.log('warn', `No SNI detected in TLS ClientHello for connection ${connectionId}; sending TLS alert`, {
connectionId,
component: 'route-handler'
});
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
this.connectionManager.incrementTerminationStat(
@ -245,7 +270,11 @@ export class RouteConnectionHandler {
}
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] TLS connection with SNI: ${serverName || '(empty)'}`);
logger.log('info', `TLS connection with SNI`, {
connectionId,
serverName: serverName || '(empty)',
component: 'route-handler'
});
}
}
}
@ -278,12 +307,18 @@ export class RouteConnectionHandler {
});
if (!routeMatch) {
console.log(
`[${connectionId}] No route found for ${serverName || 'connection'} on port ${localPort}`
);
logger.log('warn', `No route found for ${serverName || 'connection'} on port ${localPort} (connection: ${connectionId})`, {
connectionId,
serverName: serverName || 'connection',
localPort,
component: 'route-handler'
});
// No matching route, use default/fallback handling
console.log(`[${connectionId}] Using default route handling for connection`);
logger.log('info', `Using default route handling for connection ${connectionId}`, {
connectionId,
component: 'route-handler'
});
// Check default security settings
const defaultSecuritySettings = this.settings.defaults?.security;
@ -296,7 +331,11 @@ export class RouteConnectionHandler {
);
if (!isAllowed) {
console.log(`[${connectionId}] IP ${remoteIP} not in default allowed list`);
logger.log('warn', `IP ${remoteIP} not in default allowed list for connection ${connectionId}`, {
connectionId,
remoteIP,
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'ip_blocked');
return;
@ -321,7 +360,10 @@ export class RouteConnectionHandler {
);
} else {
// No default target available, terminate the connection
console.log(`[${connectionId}] No default target configured. Closing connection.`);
logger.log('warn', `No default target configured for connection ${connectionId}. Closing connection`, {
connectionId,
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'no_default_target');
return;
@ -332,11 +374,13 @@ export class RouteConnectionHandler {
const route = routeMatch.route;
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Route matched: "${route.name || 'unnamed'}" for ${
serverName || 'connection'
} on port ${localPort}`
);
logger.log('info', `Route matched`, {
connectionId,
routeName: route.name || 'unnamed',
serverName: serverName || 'connection',
localPort,
component: 'route-handler'
});
}
@ -356,7 +400,11 @@ export class RouteConnectionHandler {
return;
default:
console.log(`[${connectionId}] Unknown action type: ${(route.action as any).type}`);
logger.log('error', `Unknown action type '${(route.action as any).type}' for connection ${connectionId}`, {
connectionId,
actionType: (route.action as any).type,
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'unknown_action');
}
@ -381,30 +429,36 @@ export class RouteConnectionHandler {
// Log the connection for monitoring purposes
if (this.settings.enableDetailedLogging) {
console.log(
`[${record.id}] NFTables forwarding (kernel-level): ` +
`${record.remoteIP}:${socket.remotePort} -> ${socket.localAddress}:${record.localPort}` +
` (Route: "${route.name || 'unnamed'}", Domain: ${record.lockedDomain || 'n/a'})`
);
logger.log('info', `NFTables forwarding (kernel-level)`, {
connectionId: record.id,
source: `${record.remoteIP}:${socket.remotePort}`,
destination: `${socket.localAddress}:${record.localPort}`,
routeName: route.name || 'unnamed',
domain: record.lockedDomain || 'n/a',
component: 'route-handler'
});
} else {
console.log(
`[${record.id}] NFTables forwarding: ${record.remoteIP} -> port ${
record.localPort
} (Route: "${route.name || 'unnamed'}")`
);
logger.log('info', `NFTables forwarding`, {
connectionId: record.id,
remoteIP: record.remoteIP,
localPort: record.localPort,
routeName: route.name || 'unnamed',
component: 'route-handler'
});
}
// Additional NFTables-specific logging if configured
if (action.nftables) {
const nftConfig = action.nftables;
if (this.settings.enableDetailedLogging) {
console.log(
`[${record.id}] NFTables config: ` +
`protocol=${nftConfig.protocol || 'tcp'}, ` +
`preserveSourceIP=${nftConfig.preserveSourceIP || false}, ` +
`priority=${nftConfig.priority || 'default'}, ` +
`maxRate=${nftConfig.maxRate || 'unlimited'}`
);
logger.log('info', `NFTables config`, {
connectionId: record.id,
protocol: nftConfig.protocol || 'tcp',
preserveSourceIP: nftConfig.preserveSourceIP || false,
priority: nftConfig.priority || 'default',
maxRate: nftConfig.maxRate || 'unlimited',
component: 'route-handler'
});
}
}
@ -419,7 +473,10 @@ export class RouteConnectionHandler {
// We should have a target configuration for forwarding
if (!action.target) {
console.log(`[${connectionId}] Forward action missing target configuration`);
logger.log('error', `Forward action missing target configuration for connection ${connectionId}`, {
connectionId,
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'missing_target');
return;
@ -447,14 +504,18 @@ export class RouteConnectionHandler {
try {
targetHost = action.target.host(routeContext);
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Dynamic host resolved to: ${
Array.isArray(targetHost) ? targetHost.join(', ') : targetHost
}`
);
logger.log('info', `Dynamic host resolved to ${Array.isArray(targetHost) ? targetHost.join(', ') : targetHost} for connection ${connectionId}`, {
connectionId,
targetHost: Array.isArray(targetHost) ? targetHost.join(', ') : targetHost,
component: 'route-handler'
});
}
} catch (err) {
console.log(`[${connectionId}] Error in host mapping function: ${err}`);
logger.log('error', `Error in host mapping function for connection ${connectionId}: ${err}`, {
connectionId,
error: err,
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'host_mapping_error');
return;
@ -474,14 +535,21 @@ export class RouteConnectionHandler {
try {
targetPort = action.target.port(routeContext);
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Dynamic port mapping: ${record.localPort} -> ${targetPort}`
);
logger.log('info', `Dynamic port mapping from ${record.localPort} to ${targetPort} for connection ${connectionId}`, {
connectionId,
sourcePort: record.localPort,
targetPort,
component: 'route-handler'
});
}
// Store the resolved target port in the context for potential future use
routeContext.targetPort = targetPort;
} catch (err) {
console.log(`[${connectionId}] Error in port mapping function: ${err}`);
logger.log('error', `Error in port mapping function for connection ${connectionId}: ${err}`, {
connectionId,
error: err,
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'port_mapping_error');
return;
@ -503,7 +571,12 @@ export class RouteConnectionHandler {
case 'passthrough':
// For TLS passthrough, just forward directly
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] Using TLS passthrough to ${selectedHost}:${targetPort}`);
logger.log('info', `Using TLS passthrough to ${selectedHost}:${targetPort} for connection ${connectionId}`, {
connectionId,
targetHost: selectedHost,
targetPort,
component: 'route-handler'
});
}
return this.setupDirectConnection(
@ -521,9 +594,11 @@ export class RouteConnectionHandler {
// For TLS termination, use HttpProxy
if (this.httpProxyBridge.getHttpProxy()) {
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Using HttpProxy for TLS termination to ${action.target.host}`
);
logger.log('info', `Using HttpProxy for TLS termination to ${Array.isArray(action.target.host) ? action.target.host.join(', ') : action.target.host} for connection ${connectionId}`, {
connectionId,
targetHost: action.target.host,
component: 'route-handler'
});
}
// If we have an initial chunk with TLS data, start processing it
@ -540,12 +615,18 @@ export class RouteConnectionHandler {
}
// This shouldn't normally happen - we should have TLS data at this point
console.log(`[${connectionId}] TLS termination route without TLS data`);
logger.log('error', `TLS termination route without TLS data for connection ${connectionId}`, {
connectionId,
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'tls_error');
return;
} else {
console.log(`[${connectionId}] HttpProxy not available for TLS termination`);
logger.log('error', `HttpProxy not available for TLS termination for connection ${connectionId}`, {
connectionId,
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'no_http_proxy');
return;
@ -558,9 +639,11 @@ export class RouteConnectionHandler {
if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) {
// Forward non-TLS connections to HttpProxy if configured
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Using HttpProxy for non-TLS connection on port ${record.localPort}`
);
logger.log('info', `Using HttpProxy for non-TLS connection ${connectionId} on port ${record.localPort}`, {
connectionId,
port: record.localPort,
component: 'route-handler'
});
}
this.httpProxyBridge.forwardToHttpProxy(
@ -575,9 +658,12 @@ export class RouteConnectionHandler {
} else {
// Basic forwarding
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Using basic forwarding to ${action.target.host}:${action.target.port}`
);
logger.log('info', `Using basic forwarding to ${Array.isArray(action.target.host) ? action.target.host.join(', ') : action.target.host}:${action.target.port} for connection ${connectionId}`, {
connectionId,
targetHost: action.target.host,
targetPort: action.target.port,
component: 'route-handler'
});
}
// Get the appropriate host value
@ -633,7 +719,10 @@ export class RouteConnectionHandler {
): void {
// For TLS connections, we can't do redirects at the TCP level
if (record.isTLS) {
console.log(`[${record.id}] Cannot redirect TLS connection at TCP level`);
logger.log('warn', `Cannot redirect TLS connection ${record.id} at TCP level`, {
connectionId: record.id,
component: 'route-handler'
});
socket.end();
this.connectionManager.cleanupConnection(record, 'tls_redirect_error');
return;
@ -658,9 +747,11 @@ export class RouteConnectionHandler {
const connectionId = record.id;
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Blocking connection based on route "${route.name || 'unnamed'}"`
);
logger.log('info', `Blocking connection ${connectionId} based on route '${route.name || 'unnamed'}'`, {
connectionId,
routeName: route.name || 'unnamed',
component: 'route-handler'
});
}
// Simply close the connection
@ -699,8 +790,16 @@ export class RouteConnectionHandler {
targetSocket.once('error', (err) => {
// This handler runs only once during the initial connection phase
const code = (err as any).code;
console.log(
`[${connectionId}] Connection setup error to ${finalTargetHost}:${finalTargetPort}: ${err.message} (${code})`
logger.log('error',
`Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${err.message} (${code})`,
{
connectionId,
targetHost: finalTargetHost,
targetPort: finalTargetPort,
errorMessage: err.message,
errorCode: code,
component: 'route-handler'
}
);
// Resume the incoming socket to prevent it from hanging
@ -708,29 +807,57 @@ export class RouteConnectionHandler {
// Log specific error types for easier debugging
if (code === 'ECONNREFUSED') {
console.log(
`[${connectionId}] Target ${finalTargetHost}:${finalTargetPort} refused connection. ` +
`Check if the target service is running and listening on that port.`
logger.log('error',
`Connection ${connectionId}: Target ${finalTargetHost}:${finalTargetPort} refused connection. Check if the target service is running and listening on that port.`,
{
connectionId,
targetHost: finalTargetHost,
targetPort: finalTargetPort,
recommendation: 'Check if the target service is running and listening on that port.',
component: 'route-handler'
}
);
} else if (code === 'ETIMEDOUT') {
console.log(
`[${connectionId}] Connection to ${finalTargetHost}:${finalTargetPort} timed out. ` +
`Check network conditions, firewall rules, or if the target is too far away.`
logger.log('error',
`Connection ${connectionId} to ${finalTargetHost}:${finalTargetPort} timed out. Check network conditions, firewall rules, or if the target is too far away.`,
{
connectionId,
targetHost: finalTargetHost,
targetPort: finalTargetPort,
recommendation: 'Check network conditions, firewall rules, or if the target is too far away.',
component: 'route-handler'
}
);
} else if (code === 'ECONNRESET') {
console.log(
`[${connectionId}] Connection to ${finalTargetHost}:${finalTargetPort} was reset. ` +
`The target might have closed the connection abruptly.`
logger.log('error',
`Connection ${connectionId} to ${finalTargetHost}:${finalTargetPort} was reset. The target might have closed the connection abruptly.`,
{
connectionId,
targetHost: finalTargetHost,
targetPort: finalTargetPort,
recommendation: 'The target might have closed the connection abruptly.',
component: 'route-handler'
}
);
} else if (code === 'EHOSTUNREACH') {
console.log(
`[${connectionId}] Host ${finalTargetHost} is unreachable. ` +
`Check DNS settings, network routing, or firewall rules.`
logger.log('error',
`Connection ${connectionId}: Host ${finalTargetHost} is unreachable. Check DNS settings, network routing, or firewall rules.`,
{
connectionId,
targetHost: finalTargetHost,
recommendation: 'Check DNS settings, network routing, or firewall rules.',
component: 'route-handler'
}
);
} else if (code === 'ENOTFOUND') {
console.log(
`[${connectionId}] DNS lookup failed for ${finalTargetHost}. ` +
`Check your DNS settings or if the hostname is correct.`
logger.log('error',
`Connection ${connectionId}: DNS lookup failed for ${finalTargetHost}. Check your DNS settings or if the hostname is correct.`,
{
connectionId,
targetHost: finalTargetHost,
recommendation: 'Check your DNS settings or if the hostname is correct.',
component: 'route-handler'
}
);
}
@ -779,9 +906,12 @@ export class RouteConnectionHandler {
record.targetPort = finalTargetPort;
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Setting up direct connection to ${finalTargetHost}:${finalTargetPort}`
);
logger.log('info', `Setting up direct connection ${connectionId} to ${finalTargetHost}:${finalTargetPort}`, {
connectionId,
targetHost: finalTargetHost,
targetPort: finalTargetPort,
component: 'route-handler'
});
}
// Setup connection options
@ -826,9 +956,11 @@ export class RouteConnectionHandler {
} catch (err) {
// Ignore errors - these are optional enhancements
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Enhanced TCP keep-alive not supported for outgoing socket: ${err}`
);
logger.log('warn', `Enhanced TCP keep-alive not supported for outgoing socket on connection ${connectionId}: ${err}`, {
connectionId,
error: err,
component: 'route-handler'
});
}
}
}
@ -848,22 +980,23 @@ export class RouteConnectionHandler {
socket.on('timeout', () => {
// For keep-alive connections, just log a warning instead of closing
if (record.hasKeepAlive) {
console.log(
`[${connectionId}] Timeout event on incoming keep-alive connection from ${
record.remoteIP
} after ${plugins.prettyMs(
this.settings.socketTimeout || 3600000
)}. Connection preserved.`
);
logger.log('warn', `Timeout event on incoming keep-alive connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}. Connection preserved.`, {
connectionId,
remoteIP: record.remoteIP,
timeout: plugins.prettyMs(this.settings.socketTimeout || 3600000),
status: 'Connection preserved',
component: 'route-handler'
});
return;
}
// For non-keep-alive connections, proceed with normal cleanup
console.log(
`[${connectionId}] Timeout on incoming side from ${
record.remoteIP
} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`
);
logger.log('warn', `Timeout on incoming side for connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`, {
connectionId,
remoteIP: record.remoteIP,
timeout: plugins.prettyMs(this.settings.socketTimeout || 3600000),
component: 'route-handler'
});
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = 'timeout';
this.connectionManager.incrementTerminationStat('incoming', 'timeout');
@ -874,22 +1007,23 @@ export class RouteConnectionHandler {
targetSocket.on('timeout', () => {
// For keep-alive connections, just log a warning instead of closing
if (record.hasKeepAlive) {
console.log(
`[${connectionId}] Timeout event on outgoing keep-alive connection from ${
record.remoteIP
} after ${plugins.prettyMs(
this.settings.socketTimeout || 3600000
)}. Connection preserved.`
);
logger.log('warn', `Timeout event on outgoing keep-alive connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}. Connection preserved.`, {
connectionId,
remoteIP: record.remoteIP,
timeout: plugins.prettyMs(this.settings.socketTimeout || 3600000),
status: 'Connection preserved',
component: 'route-handler'
});
return;
}
// For non-keep-alive connections, proceed with normal cleanup
console.log(
`[${connectionId}] Timeout on outgoing side from ${
record.remoteIP
} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`
);
logger.log('warn', `Timeout on outgoing side for connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`, {
connectionId,
remoteIP: record.remoteIP,
timeout: plugins.prettyMs(this.settings.socketTimeout || 3600000),
component: 'route-handler'
});
if (record.outgoingTerminationReason === null) {
record.outgoingTerminationReason = 'timeout';
this.connectionManager.incrementTerminationStat('outgoing', 'timeout');
@ -909,9 +1043,12 @@ export class RouteConnectionHandler {
// Wait for the outgoing connection to be ready before setting up piping
targetSocket.once('connect', () => {
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Connection established to target: ${finalTargetHost}:${finalTargetPort}`
);
logger.log('info', `Connection ${connectionId} established to target ${finalTargetHost}:${finalTargetPort}`, {
connectionId,
targetHost: finalTargetHost,
targetPort: finalTargetPort,
component: 'route-handler'
});
}
// Clear the initial connection error handler
@ -933,7 +1070,11 @@ export class RouteConnectionHandler {
// Write pending data immediately
targetSocket.write(combinedData, (err) => {
if (err) {
console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`);
logger.log('error', `Error writing pending data to target for connection ${connectionId}: ${err.message}`, {
connectionId,
error: err.message,
component: 'route-handler'
});
return this.connectionManager.initiateCleanupOnce(record, 'write_error');
}
});
@ -954,15 +1095,17 @@ export class RouteConnectionHandler {
});
// Log successful connection
console.log(
logger.log('info',
`Connection established: ${record.remoteIP} -> ${finalTargetHost}:${finalTargetPort}` +
`${
serverName
? ` (SNI: ${serverName})`
: record.lockedDomain
? ` (Domain: ${record.lockedDomain})`
: ''
}`
`${serverName ? ` (SNI: ${serverName})` : record.lockedDomain ? ` (Domain: ${record.lockedDomain})` : ''}`,
{
remoteIP: record.remoteIP,
targetHost: finalTargetHost,
targetPort: finalTargetPort,
sni: serverName || undefined,
domain: !serverName && record.lockedDomain ? record.lockedDomain : undefined,
component: 'route-handler'
}
);
// Add TLS renegotiation handler if needed
@ -990,17 +1133,21 @@ export class RouteConnectionHandler {
socket.on('data', renegotiationHandler);
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`
);
logger.log('info', `TLS renegotiation handler installed for connection ${connectionId} with SNI ${serverName}`, {
connectionId,
serverName,
component: 'route-handler'
});
}
}
// Set connection timeout
record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(record, (record, reason) => {
console.log(
`[${connectionId}] Connection from ${record.remoteIP} exceeded max lifetime, forcing cleanup.`
);
logger.log('warn', `Connection ${connectionId} from ${record.remoteIP} exceeded max lifetime, forcing cleanup`, {
connectionId,
remoteIP: record.remoteIP,
component: 'route-handler'
});
this.connectionManager.initiateCleanupOnce(record, reason);
});

View File

@ -1,4 +1,5 @@
import * as plugins from '../../plugins.js';
import { logger } from '../../core/utils/logger.js';
// Importing required components
import { ConnectionManager } from './connection-manager.js';
@ -239,7 +240,7 @@ export class SmartProxy extends plugins.EventEmitter {
);
if (autoRoutes.length === 0 && !this.hasStaticCertRoutes()) {
console.log('No routes require certificate management');
logger.log('info', 'No routes require certificate management', { component: 'certificate-manager' });
return;
}
@ -256,7 +257,7 @@ export class SmartProxy extends plugins.EventEmitter {
useProduction: this.settings.acme.useProduction || false,
port: this.settings.acme.port || 80
};
console.log(`Using top-level ACME configuration with email: ${acmeOptions.email}`);
logger.log('info', `Using top-level ACME configuration with email: ${acmeOptions.email}`, { component: 'certificate-manager' });
} else if (autoRoutes.length > 0) {
// Check for route-level ACME config
const routeWithAcme = autoRoutes.find(r => r.action.tls?.acme?.email);
@ -267,7 +268,7 @@ export class SmartProxy extends plugins.EventEmitter {
useProduction: routeAcme.useProduction || false,
port: routeAcme.challengePort || 80
};
console.log(`Using route-level ACME configuration from route '${routeWithAcme.name}' with email: ${acmeOptions.email}`);
logger.log('info', `Using route-level ACME configuration from route '${routeWithAcme.name}' with email: ${acmeOptions.email}`, { component: 'certificate-manager' });
}
}
@ -305,7 +306,7 @@ export class SmartProxy extends plugins.EventEmitter {
public async start() {
// Don't start if already shutting down
if (this.isShuttingDown) {
console.log("Cannot start SmartProxy while it's shutting down");
logger.log('warn', "Cannot start SmartProxy while it's in the shutdown process");
return;
}
@ -332,9 +333,9 @@ export class SmartProxy extends plugins.EventEmitter {
const allWarnings = [...configWarnings, ...acmeWarnings];
if (allWarnings.length > 0) {
console.log("Configuration warnings:");
logger.log('warn', `${allWarnings.length} configuration warnings found`, { count: allWarnings.length });
for (const warning of allWarnings) {
console.log(` - ${warning}`);
logger.log('warn', `${warning}`);
}
}
@ -353,7 +354,7 @@ export class SmartProxy extends plugins.EventEmitter {
// Now that ports are listening, provision any required certificates
if (this.certManager) {
console.log('Starting certificate provisioning now that ports are ready');
logger.log('info', 'Starting certificate provisioning now that ports are ready', { component: 'certificate-manager' });
await this.certManager.provisionAllCertificates();
}
@ -411,16 +412,26 @@ export class SmartProxy extends plugins.EventEmitter {
const terminationStats = this.connectionManager.getTerminationStats();
// Log detailed stats
console.log(
`Active connections: ${connectionRecords.size}. ` +
`Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` +
`Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, HttpProxy=${httpProxyConnections}. ` +
`Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(maxOutgoing)}. ` +
`Termination stats: ${JSON.stringify({
IN: terminationStats.incoming,
OUT: terminationStats.outgoing,
})}`
);
logger.log('info', 'Connection statistics', {
activeConnections: connectionRecords.size,
tls: {
total: tlsConnections,
completed: completedTlsHandshakes,
pending: pendingTlsHandshakes
},
nonTls: nonTlsConnections,
keepAlive: keepAliveConnections,
httpProxy: httpProxyConnections,
longestRunning: {
incoming: plugins.prettyMs(maxIncoming),
outgoing: plugins.prettyMs(maxOutgoing)
},
terminationStats: {
incoming: terminationStats.incoming,
outgoing: terminationStats.outgoing
},
component: 'connection-manager'
});
}, this.settings.inactivityCheckInterval || 60000);
// Make sure the interval doesn't keep the process alive
@ -439,19 +450,19 @@ export class SmartProxy extends plugins.EventEmitter {
* Stop the proxy server
*/
public async stop() {
console.log('SmartProxy shutting down...');
logger.log('info', 'SmartProxy shutting down...');
this.isShuttingDown = true;
this.portManager.setShuttingDown(true);
// Stop certificate manager
if (this.certManager) {
await this.certManager.stop();
console.log('Certificate manager stopped');
logger.log('info', 'Certificate manager stopped');
}
// Stop NFTablesManager
await this.nftablesManager.stop();
console.log('NFTablesManager stopped');
logger.log('info', 'NFTablesManager stopped');
// Stop the connection logger
if (this.connectionLogger) {
@ -461,7 +472,7 @@ export class SmartProxy extends plugins.EventEmitter {
// Stop all port listeners
await this.portManager.closeAll();
console.log('All servers closed. Cleaning up active connections...');
logger.log('info', 'All servers closed. Cleaning up active connections...');
// Clean up all active connections
this.connectionManager.clearConnections();
@ -472,7 +483,7 @@ export class SmartProxy extends plugins.EventEmitter {
// Clear ACME state manager
this.acmeStateManager.clear();
console.log('SmartProxy shutdown complete.');
logger.log('info', 'SmartProxy shutdown complete.');
}
/**
@ -481,7 +492,7 @@ export class SmartProxy extends plugins.EventEmitter {
* Note: This legacy method has been removed. Use updateRoutes instead.
*/
public async updateDomainConfigs(): Promise<void> {
console.warn('Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.');
logger.log('warn', 'Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.');
throw new Error('updateDomainConfigs() is deprecated - use updateRoutes() instead');
}
@ -497,7 +508,7 @@ export class SmartProxy extends plugins.EventEmitter {
const challengeRouteExists = this.settings.routes.some(r => r.name === 'acme-challenge');
if (!challengeRouteExists) {
console.log('Challenge route successfully removed from routes');
logger.log('info', 'Challenge route successfully removed from routes');
return;
}
@ -505,7 +516,9 @@ export class SmartProxy extends plugins.EventEmitter {
await plugins.smartdelay.delayFor(retryDelay);
}
throw new Error('Failed to verify challenge route removal after ' + maxRetries + ' attempts');
const error = `Failed to verify challenge route removal after ${maxRetries} attempts`;
logger.log('error', error);
throw new Error(error);
}
/**
@ -533,7 +546,7 @@ export class SmartProxy extends plugins.EventEmitter {
*/
public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
return this.routeUpdateLock.runExclusive(async () => {
console.log(`Updating routes (${newRoutes.length} routes)`);
logger.log('info', `Updating routes (${newRoutes.length} routes)`, { routeCount: newRoutes.length, component: 'route-manager' });
// Get existing routes that use NFTables
const oldNfTablesRoutes = this.settings.routes.filter(
@ -658,14 +671,14 @@ export class SmartProxy extends plugins.EventEmitter {
// Check for wildcard domains (they can't get ACME certs)
if (domain.includes('*')) {
console.log(`Wildcard domains like "${domain}" are not supported for ACME certificates`);
logger.log('warn', `Wildcard domains like "${domain}" are not supported for automatic ACME certificates`, { domain, component: 'certificate-manager' });
return false;
}
// Check if domain has at least one dot and no invalid characters
const validDomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
if (!validDomainRegex.test(domain)) {
console.log(`Domain "${domain}" has invalid format`);
logger.log('warn', `Domain "${domain}" has invalid format for certificate issuance`, { domain, component: 'certificate-manager' });
return false;
}