fix(dcrouter): replace console logging with structured logger, improve metrics logging, add terminal-ready wait in ops UI, bump dees-catalog patch

This commit is contained in:
2026-02-21 18:13:10 +00:00
parent 79765d6729
commit 582e19e6a6
8 changed files with 92 additions and 126 deletions

View File

@@ -1,5 +1,15 @@
# Changelog # Changelog
## 2026-02-21 - 7.4.1 - fix(dcrouter)
replace console logging with structured logger, improve metrics logging, add terminal-ready wait in ops UI, bump dees-catalog patch
- Replace console.log/console.error calls in classes.dcrouter.ts with structured logger.log (info/debug/error) including contextual data and stringified errors
- MetricsManager: create a dedicated Smartlog instance (metricsLogger) for SmartMetrics and use shared logger for lifecycle events (start/stop)
- SmartProxy/ACME: convert startup/stop/cert events and error logging to structured logs; include generated route and cert metadata where relevant
- Shutdown/startup flows: unify service start/stop/error messages through logger to provide consistent, structured output
- UI change: ops-view-logs now waits for xterm terminalReady before pushing initial logs to avoid race conditions
- Bump dependency @design.estate/dees-catalog from 3.43.0 to 3.43.1
## 2026-02-21 - 7.4.0 - feat(opsserver) ## 2026-02-21 - 7.4.0 - feat(opsserver)
add real-time log push to ops dashboard and recent DNS query tracking add real-time log push to ops dashboard and recent DNS query tracking

View File

@@ -32,7 +32,7 @@
"@api.global/typedserver": "^8.3.0", "@api.global/typedserver": "^8.3.0",
"@api.global/typedsocket": "^4.1.0", "@api.global/typedsocket": "^4.1.0",
"@apiclient.xyz/cloudflare": "^7.1.0", "@apiclient.xyz/cloudflare": "^7.1.0",
"@design.estate/dees-catalog": "^3.43.0", "@design.estate/dees-catalog": "^3.43.1",
"@design.estate/dees-element": "^2.1.6", "@design.estate/dees-element": "^2.1.6",
"@push.rocks/projectinfo": "^5.0.2", "@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/qenv": "^6.1.3", "@push.rocks/qenv": "^6.1.3",

14
pnpm-lock.yaml generated
View File

@@ -24,8 +24,8 @@ importers:
specifier: ^7.1.0 specifier: ^7.1.0
version: 7.1.0 version: 7.1.0
'@design.estate/dees-catalog': '@design.estate/dees-catalog':
specifier: ^3.43.0 specifier: ^3.43.1
version: 3.43.0(@tiptap/pm@2.27.2) version: 3.43.1(@tiptap/pm@2.27.2)
'@design.estate/dees-element': '@design.estate/dees-element':
specifier: ^2.1.6 specifier: ^2.1.6
version: 2.1.6 version: 2.1.6
@@ -351,8 +351,8 @@ packages:
'@configvault.io/interfaces@1.0.17': '@configvault.io/interfaces@1.0.17':
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==} resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
'@design.estate/dees-catalog@3.43.0': '@design.estate/dees-catalog@3.43.1':
resolution: {integrity: sha512-UFW8oThP9Mc4L0wVVgmuGux868Ct/TwZ1WP8hZCe4e/+5gmxDc+4EArnt5hePHENboe1Soobh9mmrMN6kQZ3xQ==} resolution: {integrity: sha512-WAWOV8dIgdKfAbS4Ciek8oDVIWC0OSPODhpQdLlsGBXERcFaBPaYxcpywmrjXB/TFeoAQPxBxhS7jb9/p2Rprg==}
'@design.estate/dees-comms@1.0.30': '@design.estate/dees-comms@1.0.30':
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==} resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
@@ -4218,13 +4218,11 @@ packages:
xterm-addon-fit@0.8.0: xterm-addon-fit@0.8.0:
resolution: {integrity: sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==} resolution: {integrity: sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==}
deprecated: This package is now deprecated. Move to @xterm/addon-fit instead.
peerDependencies: peerDependencies:
xterm: ^5.0.0 xterm: ^5.0.0
xterm@5.3.0: xterm@5.3.0:
resolution: {integrity: sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==} resolution: {integrity: sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==}
deprecated: This package is now deprecated. Move to @xterm/xterm instead.
y18n@5.0.8: y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
@@ -4336,7 +4334,7 @@ snapshots:
'@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@2.0.1) '@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@2.0.1)
'@cloudflare/workers-types': 4.20260210.0 '@cloudflare/workers-types': 4.20260210.0
'@design.estate/dees-catalog': 3.43.0(@tiptap/pm@2.27.2) '@design.estate/dees-catalog': 3.43.1(@tiptap/pm@2.27.2)
'@design.estate/dees-comms': 1.0.30 '@design.estate/dees-comms': 1.0.30
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
@@ -4934,7 +4932,7 @@ snapshots:
dependencies: dependencies:
'@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedrequest-interfaces': 3.0.19
'@design.estate/dees-catalog@3.43.0(@tiptap/pm@2.27.2)': '@design.estate/dees-catalog@3.43.1(@tiptap/pm@2.27.2)':
dependencies: dependencies:
'@design.estate/dees-domtools': 2.3.8 '@design.estate/dees-domtools': 2.3.8
'@design.estate/dees-element': 2.1.6 '@design.estate/dees-element': 2.1.6

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '7.4.0', version: '7.4.1',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -252,9 +252,7 @@ export class DcRouter {
} }
public async start() { public async start() {
console.log('╔═══════════════════════════════════════════════════════════════════╗'); logger.log('info', 'Starting DcRouter Services');
console.log('║ Starting DcRouter Services ║');
console.log('╚═══════════════════════════════════════════════════════════════════╝');
this.opsServer = new OpsServer(this); this.opsServer = new OpsServer(this);
@@ -296,7 +294,7 @@ export class DcRouter {
this.logStartupSummary(); this.logStartupSummary();
} catch (error) { } catch (error) {
console.error('❌ Error starting DcRouter:', error); logger.log('error', 'Error starting DcRouter', { error: String(error) });
// Try to clean up any services that may have started // Try to clean up any services that may have started
await this.stop(); await this.stop();
throw error; throw error;
@@ -307,104 +305,60 @@ export class DcRouter {
* Log comprehensive startup summary * Log comprehensive startup summary
*/ */
private logStartupSummary(): void { private logStartupSummary(): void {
console.log('\n╔═══════════════════════════════════════════════════════════════════╗'); logger.log('info', 'DcRouter Started Successfully');
console.log('║ DcRouter Started Successfully ║');
console.log('╚═══════════════════════════════════════════════════════════════════╝\n');
// Metrics summary // Metrics summary
if (this.metricsManager) { if (this.metricsManager) {
console.log('📊 Metrics Service:'); logger.log('info', 'Metrics Service: SmartMetrics active, SmartProxy stats active, real-time tracking enabled');
console.log(' ├─ SmartMetrics: Active');
console.log(' ├─ SmartProxy Stats: Active');
console.log(' └─ Real-time tracking: Enabled');
} }
// SmartProxy summary // SmartProxy summary
if (this.smartProxy) { if (this.smartProxy) {
console.log('🌐 SmartProxy Service:');
const routeCount = this.options.smartProxyConfig?.routes?.length || 0; const routeCount = this.options.smartProxyConfig?.routes?.length || 0;
console.log(` ├─ Routes configured: ${routeCount}`); const acmeEnabled = this.options.smartProxyConfig?.acme?.enabled || false;
console.log(` ├─ ACME enabled: ${this.options.smartProxyConfig?.acme?.enabled || false}`); const acmeMode = acmeEnabled
if (this.options.smartProxyConfig?.acme?.enabled) { ? `email=${this.options.smartProxyConfig!.acme!.email || 'not set'}, mode=${this.options.smartProxyConfig!.acme!.useProduction ? 'production' : 'staging'}`
console.log(` ├─ ACME email: ${this.options.smartProxyConfig.acme.email || 'not set'}`); : 'disabled';
console.log(` └─ ACME mode: ${this.options.smartProxyConfig.acme.useProduction ? 'production' : 'staging'}`); logger.log('info', `SmartProxy Service: ${routeCount} routes, ACME: ${acmeMode}`);
} else {
console.log(' └─ ACME: disabled');
}
} }
// Email service summary // Email service summary
if (this.emailServer && this.options.emailConfig) { if (this.emailServer && this.options.emailConfig) {
console.log('\n📧 Email Service:');
const ports = this.options.emailConfig.ports || []; const ports = this.options.emailConfig.ports || [];
console.log(` ├─ Ports: ${ports.join(', ')}`); const domainCount = this.options.emailConfig.domains?.length || 0;
console.log(` ├─ Hostname: ${this.options.emailConfig.hostname || 'localhost'}`); const domainNames = this.options.emailConfig.domains?.map(d => `${d.domain} (${d.dnsMode || 'default'})`).join(', ') || 'none';
console.log(` ├─ Domains configured: ${this.options.emailConfig.domains?.length || 0}`); logger.log('info', `Email Service: ports=[${ports.join(', ')}], hostname=${this.options.emailConfig.hostname || 'localhost'}, domains=${domainCount} [${domainNames}], DKIM initialized`);
if (this.options.emailConfig.domains && this.options.emailConfig.domains.length > 0) {
this.options.emailConfig.domains.forEach((domain, index) => {
const isLast = index === this.options.emailConfig!.domains!.length - 1;
console.log(` ${isLast ? '└─' : '├─'} ${domain.domain} (${domain.dnsMode || 'default'})`);
});
}
console.log(` └─ DKIM: Initialized for all domains`);
} }
// DNS service summary // DNS service summary
if (this.dnsServer && this.options.dnsNsDomains && this.options.dnsScopes) { if (this.dnsServer && this.options.dnsNsDomains && this.options.dnsScopes) {
console.log('\n🌍 DNS Service:'); logger.log('info', `DNS Service: nameservers=[${this.options.dnsNsDomains.join(', ')}], authoritative for ${this.options.dnsScopes.length} domains [${this.options.dnsScopes.join(', ')}], UDP:53, DoH enabled`);
console.log(` ├─ Nameservers: ${this.options.dnsNsDomains.join(', ')}`);
console.log(` ├─ Primary NS: ${this.options.dnsNsDomains[0]}`);
console.log(` ├─ Authoritative for: ${this.options.dnsScopes.length} domains`);
console.log(` ├─ UDP Port: 53`);
console.log(` ├─ DNS-over-HTTPS: Enabled via socket handler`);
console.log(` └─ DNSSEC: ${this.options.dnsNsDomains[0] ? 'Enabled' : 'Disabled'}`);
// Show authoritative domains
if (this.options.dnsScopes.length > 0) {
console.log('\n Authoritative Domains:');
this.options.dnsScopes.forEach((domain, index) => {
const isLast = index === this.options.dnsScopes!.length - 1;
console.log(` ${isLast ? '└─' : '├─'} ${domain}`);
});
}
} }
// RADIUS service summary // RADIUS service summary
if (this.radiusServer && this.options.radiusConfig) { if (this.radiusServer && this.options.radiusConfig) {
console.log('\n🔐 RADIUS Service:');
console.log(` ├─ Auth Port: ${this.options.radiusConfig.authPort || 1812}`);
console.log(` ├─ Acct Port: ${this.options.radiusConfig.acctPort || 1813}`);
console.log(` ├─ Clients configured: ${this.options.radiusConfig.clients?.length || 0}`);
const vlanStats = this.radiusServer.getVlanManager().getStats(); const vlanStats = this.radiusServer.getVlanManager().getStats();
console.log(` ├─ VLAN mappings: ${vlanStats.totalMappings}`); logger.log('info', `RADIUS Service: auth=${this.options.radiusConfig.authPort || 1812}, acct=${this.options.radiusConfig.acctPort || 1813}, clients=${this.options.radiusConfig.clients?.length || 0}, VLANs=${vlanStats.totalMappings}, accounting=${this.options.radiusConfig.accounting?.enabled ? 'enabled' : 'disabled'}`);
console.log(` └─ Accounting: ${this.options.radiusConfig.accounting?.enabled ? 'Enabled' : 'Disabled'}`);
} }
// Remote Ingress summary // Remote Ingress summary
if (this.tunnelManager && this.options.remoteIngressConfig?.enabled) { if (this.tunnelManager && this.options.remoteIngressConfig?.enabled) {
console.log('\n🌐 Remote Ingress:');
console.log(` ├─ Tunnel Port: ${this.options.remoteIngressConfig.tunnelPort || 8443}`);
const edgeCount = this.remoteIngressManager?.getAllEdges().length || 0; const edgeCount = this.remoteIngressManager?.getAllEdges().length || 0;
const connectedCount = this.tunnelManager.getConnectedCount(); const connectedCount = this.tunnelManager.getConnectedCount();
console.log(` ├─ Registered Edges: ${edgeCount}`); logger.log('info', `Remote Ingress: tunnel port=${this.options.remoteIngressConfig.tunnelPort || 8443}, edges=${edgeCount} registered/${connectedCount} connected`);
console.log(` └─ Connected Edges: ${connectedCount}`);
} }
// Storage summary // Storage summary
if (this.storageManager && this.options.storage) { if (this.storageManager && this.options.storage) {
console.log('\n💾 Storage:'); logger.log('info', `Storage: path=${this.options.storage.fsPath || 'default'}`);
console.log(` └─ Path: ${this.options.storage.fsPath || 'default'}`);
} }
// Cache database summary // Cache database summary
if (this.cacheDb) { if (this.cacheDb) {
console.log('\n🗄 Cache Database (smartdata + LocalTsmDb):'); logger.log('info', `Cache Database: storage=${this.cacheDb.getStoragePath()}, db=${this.cacheDb.getDbName()}, cleaner=${this.cacheCleaner?.isActive() ? 'active' : 'inactive'} (${(this.options.cacheConfig?.cleanupIntervalHours || 1)}h interval)`);
console.log(` ├─ Storage: ${this.cacheDb.getStoragePath()}`);
console.log(` ├─ Database: ${this.cacheDb.getDbName()}`);
console.log(` └─ Cleaner: ${this.cacheCleaner?.isActive() ? 'Active' : 'Inactive'} (${(this.options.cacheConfig?.cleanupIntervalHours || 1)}h interval)`);
} }
console.log('\n✅ All services are running\n'); logger.log('info', 'All services are running');
} }
/** /**
@@ -439,7 +393,7 @@ export class DcRouter {
* Set up SmartProxy with direct configuration and automatic email routes * Set up SmartProxy with direct configuration and automatic email routes
*/ */
private async setupSmartProxy(): Promise<void> { private async setupSmartProxy(): Promise<void> {
console.log('[DcRouter] Setting up SmartProxy...'); logger.log('info', 'Setting up SmartProxy...');
let routes: plugins.smartproxy.IRouteConfig[] = []; let routes: plugins.smartproxy.IRouteConfig[] = [];
let acmeConfig: plugins.smartproxy.IAcmeOptions | undefined; let acmeConfig: plugins.smartproxy.IAcmeOptions | undefined;
@@ -447,22 +401,20 @@ export class DcRouter {
if (this.options.smartProxyConfig) { if (this.options.smartProxyConfig) {
routes = this.options.smartProxyConfig.routes || []; routes = this.options.smartProxyConfig.routes || [];
acmeConfig = this.options.smartProxyConfig.acme; acmeConfig = this.options.smartProxyConfig.acme;
console.log(`[DcRouter] Found ${routes.length} routes in config`); logger.log('info', `Found ${routes.length} routes in config, ACME config present: ${!!acmeConfig}`);
console.log(`[DcRouter] ACME config present: ${!!acmeConfig}`);
} }
// If email config exists, automatically add email routes // If email config exists, automatically add email routes
if (this.options.emailConfig) { if (this.options.emailConfig) {
const emailRoutes = this.generateEmailRoutes(this.options.emailConfig); const emailRoutes = this.generateEmailRoutes(this.options.emailConfig);
console.log(`Email Routes are:`) logger.log('debug', 'Email routes generated', { routes: JSON.stringify(emailRoutes) });
console.log(emailRoutes)
routes = [...routes, ...emailRoutes]; // Enable email routing through SmartProxy routes = [...routes, ...emailRoutes]; // Enable email routing through SmartProxy
} }
// If DNS is configured, add DNS routes // If DNS is configured, add DNS routes
if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0) { if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0) {
const dnsRoutes = this.generateDnsRoutes(); const dnsRoutes = this.generateDnsRoutes();
console.log(`DNS Routes for nameservers ${this.options.dnsNsDomains.join(', ')}:`, dnsRoutes); logger.log('debug', `DNS routes for nameservers ${this.options.dnsNsDomains.join(', ')}`, { routes: JSON.stringify(dnsRoutes) });
routes = [...routes, ...dnsRoutes]; routes = [...routes, ...dnsRoutes];
} }
@@ -480,7 +432,7 @@ export class DcRouter {
// Configure DNS challenge if available // Configure DNS challenge if available
let challengeHandlers: any[] = []; let challengeHandlers: any[] = [];
if (this.options.dnsChallenge?.cloudflareApiKey) { if (this.options.dnsChallenge?.cloudflareApiKey) {
console.log('Configuring Cloudflare DNS challenge for ACME'); logger.log('info', 'Configuring Cloudflare DNS challenge for ACME');
const cloudflareAccount = new plugins.cloudflare.CloudflareAccount(this.options.dnsChallenge.cloudflareApiKey); const cloudflareAccount = new plugins.cloudflare.CloudflareAccount(this.options.dnsChallenge.cloudflareApiKey);
const dns01Handler = new plugins.smartacme.handlers.Dns01Handler(cloudflareAccount); const dns01Handler = new plugins.smartacme.handlers.Dns01Handler(cloudflareAccount);
challengeHandlers.push(dns01Handler); challengeHandlers.push(dns01Handler);
@@ -488,7 +440,7 @@ export class DcRouter {
// If we have routes or need a basic SmartProxy instance, create it // If we have routes or need a basic SmartProxy instance, create it
if (routes.length > 0 || this.options.smartProxyConfig) { if (routes.length > 0 || this.options.smartProxyConfig) {
console.log('Setting up SmartProxy with combined configuration'); logger.log('info', 'Setting up SmartProxy with combined configuration');
// Track cert entries loaded from cert store so we can populate certificateStatusMap after start // Track cert entries loaded from cert store so we can populate certificateStatusMap after start
const loadedCertEntries: Array<{domain: string; publicKey: string; validUntil?: number; validFrom?: number}> = []; const loadedCertEntries: Array<{domain: string; publicKey: string; validUntil?: number; validFrom?: number}> = [];
@@ -537,7 +489,7 @@ export class DcRouter {
// Stop old SmartAcme if it exists (e.g., during updateSmartProxyConfig) // Stop old SmartAcme if it exists (e.g., during updateSmartProxyConfig)
if (this.smartAcme) { if (this.smartAcme) {
await this.smartAcme.stop().catch(err => await this.smartAcme.stop().catch(err =>
console.error('[DcRouter] Error stopping old SmartAcme:', err) logger.log('error', 'Error stopping old SmartAcme', { error: String(err) })
); );
} }
this.smartAcme = new plugins.smartacme.SmartAcme({ this.smartAcme = new plugins.smartacme.SmartAcme({
@@ -600,25 +552,19 @@ export class DcRouter {
} }
// Create SmartProxy instance // Create SmartProxy instance
console.log('[DcRouter] Creating SmartProxy instance with config:', JSON.stringify({ logger.log('info', `Creating SmartProxy instance: routes=${smartProxyConfig.routes?.length}, acme=${smartProxyConfig.acme?.enabled}, certProvisionFunction=${!!smartProxyConfig.certProvisionFunction}`);
routeCount: smartProxyConfig.routes?.length,
acmeEnabled: smartProxyConfig.acme?.enabled,
acmeEmail: smartProxyConfig.acme?.email,
certProvisionFunction: !!smartProxyConfig.certProvisionFunction
}, null, 2));
this.smartProxy = new plugins.smartproxy.SmartProxy(smartProxyConfig); this.smartProxy = new plugins.smartproxy.SmartProxy(smartProxyConfig);
// Set up event listeners // Set up event listeners
this.smartProxy.on('error', (err) => { this.smartProxy.on('error', (err) => {
console.error('[DcRouter] SmartProxy error:', err); logger.log('error', `SmartProxy error: ${err.message}`, { stack: err.stack });
console.error('[DcRouter] Error stack:', err.stack);
}); });
// Always listen for certificate events — emitted by both ACME and certProvisionFunction paths // Always listen for certificate events — emitted by both ACME and certProvisionFunction paths
// Events are keyed by domain for domain-centric certificate tracking // Events are keyed by domain for domain-centric certificate tracking
this.smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => { this.smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
console.log(`[DcRouter] Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`); logger.log('info', `Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
const routeNames = this.findRouteNamesForDomain(event.domain); const routeNames = this.findRouteNamesForDomain(event.domain);
this.certificateStatusMap.set(event.domain, { this.certificateStatusMap.set(event.domain, {
status: 'valid', routeNames, status: 'valid', routeNames,
@@ -628,7 +574,7 @@ export class DcRouter {
}); });
this.smartProxy.on('certificate-renewed', (event: plugins.smartproxy.ICertificateIssuedEvent) => { this.smartProxy.on('certificate-renewed', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
console.log(`[DcRouter] Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`); logger.log('info', `Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
const routeNames = this.findRouteNamesForDomain(event.domain); const routeNames = this.findRouteNamesForDomain(event.domain);
this.certificateStatusMap.set(event.domain, { this.certificateStatusMap.set(event.domain, {
status: 'valid', routeNames, status: 'valid', routeNames,
@@ -638,7 +584,7 @@ export class DcRouter {
}); });
this.smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => { this.smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => {
console.error(`[DcRouter] Certificate failed for ${event.domain} (${event.source}):`, event.error); logger.log('error', `Certificate failed for ${event.domain} (${event.source}): ${event.error}`);
const routeNames = this.findRouteNamesForDomain(event.domain); const routeNames = this.findRouteNamesForDomain(event.domain);
this.certificateStatusMap.set(event.domain, { this.certificateStatusMap.set(event.domain, {
status: 'failed', routeNames, error: event.error, status: 'failed', routeNames, error: event.error,
@@ -647,9 +593,9 @@ export class DcRouter {
}); });
// Start SmartProxy // Start SmartProxy
console.log('[DcRouter] Starting SmartProxy...'); logger.log('info', 'Starting SmartProxy...');
await this.smartProxy.start(); await this.smartProxy.start();
console.log('[DcRouter] SmartProxy started successfully'); logger.log('info', 'SmartProxy started successfully');
// Populate certificateStatusMap for certs loaded from store at startup // Populate certificateStatusMap for certs loaded from store at startup
for (const entry of loadedCertEntries) { for (const entry of loadedCertEntries) {
@@ -701,10 +647,10 @@ export class DcRouter {
} }
} }
if (loadedCertEntries.length > 0) { if (loadedCertEntries.length > 0) {
console.log(`[DcRouter] Populated certificate status for ${loadedCertEntries.length} store-loaded domain(s)`); logger.log('info', `Populated certificate status for ${loadedCertEntries.length} store-loaded domain(s)`);
} }
console.log(`SmartProxy started with ${routes.length} routes`); logger.log('info', `SmartProxy started with ${routes.length} routes`);
} }
} }
@@ -907,7 +853,7 @@ export class DcRouter {
} }
public async stop() { public async stop() {
console.log('Stopping DcRouter services...'); logger.log('info', 'Stopping DcRouter services...');
await this.opsServer.stop(); await this.opsServer.stop();
@@ -918,36 +864,36 @@ export class DcRouter {
this.cacheCleaner ? Promise.resolve(this.cacheCleaner.stop()) : Promise.resolve(), this.cacheCleaner ? Promise.resolve(this.cacheCleaner.stop()) : Promise.resolve(),
// Stop metrics manager if running // Stop metrics manager if running
this.metricsManager ? this.metricsManager.stop().catch(err => console.error('Error stopping MetricsManager:', err)) : Promise.resolve(), this.metricsManager ? this.metricsManager.stop().catch(err => logger.log('error', 'Error stopping MetricsManager', { error: String(err) })) : Promise.resolve(),
// Stop unified email server if running // Stop unified email server if running
this.emailServer ? this.emailServer.stop().catch(err => console.error('Error stopping email server:', err)) : Promise.resolve(), this.emailServer ? this.emailServer.stop().catch(err => logger.log('error', 'Error stopping email server', { error: String(err) })) : Promise.resolve(),
// Stop SmartAcme if running // Stop SmartAcme if running
this.smartAcme ? this.smartAcme.stop().catch(err => console.error('Error stopping SmartAcme:', err)) : Promise.resolve(), this.smartAcme ? this.smartAcme.stop().catch(err => logger.log('error', 'Error stopping SmartAcme', { error: String(err) })) : Promise.resolve(),
// Stop HTTP SmartProxy if running // Stop HTTP SmartProxy if running
this.smartProxy ? this.smartProxy.stop().catch(err => console.error('Error stopping SmartProxy:', err)) : Promise.resolve(), this.smartProxy ? this.smartProxy.stop().catch(err => logger.log('error', 'Error stopping SmartProxy', { error: String(err) })) : Promise.resolve(),
// Stop DNS server if running // Stop DNS server if running
this.dnsServer ? this.dnsServer ?
this.dnsServer.stop().catch(err => console.error('Error stopping DNS server:', err)) : this.dnsServer.stop().catch(err => logger.log('error', 'Error stopping DNS server', { error: String(err) })) :
Promise.resolve(), Promise.resolve(),
// Stop RADIUS server if running // Stop RADIUS server if running
this.radiusServer ? this.radiusServer ?
this.radiusServer.stop().catch(err => console.error('Error stopping RADIUS server:', err)) : this.radiusServer.stop().catch(err => logger.log('error', 'Error stopping RADIUS server', { error: String(err) })) :
Promise.resolve(), Promise.resolve(),
// Stop Remote Ingress tunnel manager if running // Stop Remote Ingress tunnel manager if running
this.tunnelManager ? this.tunnelManager ?
this.tunnelManager.stop().catch(err => console.error('Error stopping TunnelManager:', err)) : this.tunnelManager.stop().catch(err => logger.log('error', 'Error stopping TunnelManager', { error: String(err) })) :
Promise.resolve() Promise.resolve()
]); ]);
// Stop cache database after other services (they may need it during shutdown) // Stop cache database after other services (they may need it during shutdown)
if (this.cacheDb) { if (this.cacheDb) {
await this.cacheDb.stop().catch(err => console.error('Error stopping CacheDb:', err)); await this.cacheDb.stop().catch(err => logger.log('error', 'Error stopping CacheDb', { error: String(err) }));
} }
// Clear backoff cache in cert scheduler // Clear backoff cache in cert scheduler
@@ -969,9 +915,9 @@ export class DcRouter {
this.remoteIngressManager = undefined; this.remoteIngressManager = undefined;
this.certificateStatusMap.clear(); this.certificateStatusMap.clear();
console.log('All DcRouter services stopped'); logger.log('info', 'All DcRouter services stopped');
} catch (error) { } catch (error) {
console.error('Error during DcRouter shutdown:', error); logger.log('error', 'Error during DcRouter shutdown', { error: String(error) });
throw error; throw error;
} }
} }
@@ -998,7 +944,7 @@ export class DcRouter {
// Start new SmartProxy with updated configuration (will include email routes if configured) // Start new SmartProxy with updated configuration (will include email routes if configured)
await this.setupSmartProxy(); await this.setupSmartProxy();
console.log('SmartProxy configuration updated'); logger.log('info', 'SmartProxy configuration updated');
} }
@@ -1091,7 +1037,7 @@ export class DcRouter {
// Start email handling with new configuration // Start email handling with new configuration
await this.setupUnifiedEmailHandling(); await this.setupUnifiedEmailHandling();
console.log('Unified email configuration updated'); logger.log('info', 'Unified email configuration updated');
} }
/** /**
@@ -1131,7 +1077,7 @@ export class DcRouter {
this.emailServer.updateEmailRoutes(routes); this.emailServer.updateEmailRoutes(routes);
} }
console.log(`Email routes updated with ${routes.length} routes`); logger.log('info', `Email routes updated with ${routes.length} routes`);
} }
/** /**

View File

@@ -2,9 +2,10 @@ import * as plugins from '../plugins.js';
import { DcRouter } from '../classes.dcrouter.js'; import { DcRouter } from '../classes.dcrouter.js';
import { MetricsCache } from './classes.metricscache.js'; import { MetricsCache } from './classes.metricscache.js';
import { SecurityLogger, SecurityEventType } from '../security/classes.securitylogger.js'; import { SecurityLogger, SecurityEventType } from '../security/classes.securitylogger.js';
import { logger } from '../logger.js';
export class MetricsManager { export class MetricsManager {
private logger: plugins.smartlog.Smartlog; private metricsLogger: plugins.smartlog.Smartlog;
private smartMetrics: plugins.smartmetrics.SmartMetrics; private smartMetrics: plugins.smartmetrics.SmartMetrics;
private dcRouter: DcRouter; private dcRouter: DcRouter;
private resetInterval?: NodeJS.Timeout; private resetInterval?: NodeJS.Timeout;
@@ -56,15 +57,15 @@ export class MetricsManager {
constructor(dcRouter: DcRouter) { constructor(dcRouter: DcRouter) {
this.dcRouter = dcRouter; this.dcRouter = dcRouter;
// Create a new Smartlog instance for metrics // Create a Smartlog instance for SmartMetrics (requires its own instance)
this.logger = new plugins.smartlog.Smartlog({ this.metricsLogger = new plugins.smartlog.Smartlog({
logContext: { logContext: {
environment: 'production', environment: 'production',
runtime: 'node', runtime: 'node',
zone: 'dcrouter-metrics', zone: 'dcrouter-metrics',
} }
}); });
this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.logger, 'dcrouter'); this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.metricsLogger, 'dcrouter');
// Initialize metrics cache with 500ms TTL // Initialize metrics cache with 500ms TTL
this.metricsCache = new MetricsCache(500); this.metricsCache = new MetricsCache(500);
} }
@@ -111,7 +112,7 @@ export class MetricsManager {
} }
}, 60000); // Check every minute }, 60000); // Check every minute
this.logger.log('info', 'MetricsManager started'); logger.log('info', 'MetricsManager started');
} }
public async stop(): Promise<void> { public async stop(): Promise<void> {
@@ -122,7 +123,7 @@ export class MetricsManager {
} }
this.smartMetrics.stop(); this.smartMetrics.stop();
this.logger.log('info', 'MetricsManager stopped'); logger.log('info', 'MetricsManager stopped');
} }
// Get server metrics from SmartMetrics and SmartProxy // Get server metrics from SmartMetrics and SmartProxy

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '7.4.0', version: '7.4.1',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -133,6 +133,17 @@ export class OpsViewLogs extends DeesElement {
// Ensure the chart element has finished its own initialization // Ensure the chart element has finished its own initialization
await chartLog.updateComplete; await chartLog.updateComplete;
// Wait for xterm terminal to finish initializing (CDN load)
if (!chartLog.terminalReady) {
await new Promise<void>((resolve) => {
const check = () => {
if (chartLog.terminalReady) { resolve(); return; }
setTimeout(check, 50);
};
check();
});
}
const allEntries = this.getMappedLogEntries(); const allEntries = this.getMappedLogEntries();
if (this.lastPushedCount === 0 && allEntries.length > 0) { if (this.lastPushedCount === 0 && allEntries.length > 0) {
// Initial load: push all entries // Initial load: push all entries