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:
10
changelog.md
10
changelog.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
14
pnpm-lock.yaml
generated
@@ -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
|
||||||
|
|||||||
@@ -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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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,18 +112,18 @@ 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> {
|
||||||
// Clear the reset interval
|
// Clear the reset interval
|
||||||
if (this.resetInterval) {
|
if (this.resetInterval) {
|
||||||
clearInterval(this.resetInterval);
|
clearInterval(this.resetInterval);
|
||||||
this.resetInterval = undefined;
|
this.resetInterval = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user