Compare commits

...

4 Commits

Author SHA1 Message Date
4759c4f011 v7.4.3
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-02-21 23:36:10 +00:00
0fbd8d1cdd fix(logging): add adaptive rate-limited DNS query logging, flush pending DNS logs on shutdown, and enhance email delivery logging 2026-02-21 23:36:10 +00:00
447cf44d68 v7.4.2
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-02-21 18:56:44 +00:00
82ce17a941 fix(monitoring,remoteingress,web): Prune old metrics buckets periodically, clear metrics caches on shutdown, simplify edge disconnect handling, and optimize network view data updates 2026-02-21 18:56:44 +00:00
9 changed files with 97 additions and 35 deletions

View File

@@ -1,5 +1,23 @@
# Changelog
## 2026-02-21 - 7.4.3 - fix(logging)
add adaptive rate-limited DNS query logging, flush pending DNS logs on shutdown, and enhance email delivery logging
- Introduce adaptive DNS logging: allow up to 2 individual DNS query logs per second, then aggregate further queries and emit a batched summary (dnsLogWindow, dnsBatchCount, dnsBatchTimer) with a 5s flush.
- Flush pending DNS batch on stop() and log final DNS batch count during shutdown.
- Enhance email observability by logging deliveryStart, deliverySuccess, deliveryFailed and bounceProcessed events alongside existing MetricsManager tracking.
- Dependency bump: @design.estate/dees-catalog updated from ^3.43.1 to ^3.43.2.
- Non-breaking change; intended as a patch release.
## 2026-02-21 - 7.4.2 - fix(monitoring,remoteingress,web)
Prune old metrics buckets periodically, clear metrics caches on shutdown, simplify edge disconnect handling, and optimize network view data updates
- Call pruneOldBuckets() each minute to proactively remove stale time-series buckets in MetricsManager
- Clear metricsCache, emailMinuteBuckets and dnsMinuteBuckets when MetricsManager stops to avoid stale state on shutdown
- On edgeDisconnected remove the edgeStatuses entry instead of mutating an existing record (more explicit cleanup)
- Remove unused traffic-timer variables and move requestsPerSec history updates from render() into updateNetworkData() to avoid unnecessary re-renders
- Optimize traffic data array updates by shifting in-place then reassigning arrays to preserve Lit reactivity and reduce intermediate allocations
## 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

View File

@@ -1,7 +1,7 @@
{
"name": "@serve.zone/dcrouter",
"private": false,
"version": "7.4.1",
"version": "7.4.3",
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
"type": "module",
"exports": {
@@ -32,7 +32,7 @@
"@api.global/typedserver": "^8.3.0",
"@api.global/typedsocket": "^4.1.0",
"@apiclient.xyz/cloudflare": "^7.1.0",
"@design.estate/dees-catalog": "^3.43.1",
"@design.estate/dees-catalog": "^3.43.2",
"@design.estate/dees-element": "^2.1.6",
"@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/qenv": "^6.1.3",

12
pnpm-lock.yaml generated
View File

@@ -24,8 +24,8 @@ importers:
specifier: ^7.1.0
version: 7.1.0
'@design.estate/dees-catalog':
specifier: ^3.43.1
version: 3.43.1(@tiptap/pm@2.27.2)
specifier: ^3.43.2
version: 3.43.2(@tiptap/pm@2.27.2)
'@design.estate/dees-element':
specifier: ^2.1.6
version: 2.1.6
@@ -351,8 +351,8 @@ packages:
'@configvault.io/interfaces@1.0.17':
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
'@design.estate/dees-catalog@3.43.1':
resolution: {integrity: sha512-WAWOV8dIgdKfAbS4Ciek8oDVIWC0OSPODhpQdLlsGBXERcFaBPaYxcpywmrjXB/TFeoAQPxBxhS7jb9/p2Rprg==}
'@design.estate/dees-catalog@3.43.2':
resolution: {integrity: sha512-7pU+K+B70SxqR4DrBkpc/xvQGLDkxAV2jH7Qyh0TYgkCoxxjsxCuTMKM9JguA38wm6bEgBJVTvyg5S3wCwxm4Q==}
'@design.estate/dees-comms@1.0.30':
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
@@ -4334,7 +4334,7 @@ snapshots:
'@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@2.0.1)
'@cloudflare/workers-types': 4.20260210.0
'@design.estate/dees-catalog': 3.43.1(@tiptap/pm@2.27.2)
'@design.estate/dees-catalog': 3.43.2(@tiptap/pm@2.27.2)
'@design.estate/dees-comms': 1.0.30
'@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5
@@ -4932,7 +4932,7 @@ snapshots:
dependencies:
'@api.global/typedrequest-interfaces': 3.0.19
'@design.estate/dees-catalog@3.43.1(@tiptap/pm@2.27.2)':
'@design.estate/dees-catalog@3.43.2(@tiptap/pm@2.27.2)':
dependencies:
'@design.estate/dees-domtools': 2.3.8
'@design.estate/dees-element': 2.1.6

View File

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

View File

@@ -212,6 +212,11 @@ export class DcRouter {
public remoteIngressManager?: RemoteIngressManager;
public tunnelManager?: TunnelManager;
// DNS query logging rate limiter state
private dnsLogWindow: number[] = [];
private dnsBatchCount: number = 0;
private dnsBatchTimer: ReturnType<typeof setTimeout> | null = null;
// Certificate status tracking from SmartProxy events (keyed by domain)
public certificateStatusMap = new Map<string, {
status: 'valid' | 'failed';
@@ -855,6 +860,17 @@ export class DcRouter {
public async stop() {
logger.log('info', 'Stopping DcRouter services...');
// Flush pending DNS batch log
if (this.dnsBatchTimer) {
clearTimeout(this.dnsBatchTimer);
if (this.dnsBatchCount > 0) {
logger.log('info', `DNS: ${this.dnsBatchCount} queries processed (rate limited, final flush)`, { zone: 'dns' });
}
this.dnsBatchTimer = null;
this.dnsBatchCount = 0;
this.dnsLogWindow = [];
}
await this.opsServer.stop();
try {
@@ -1002,21 +1018,25 @@ export class DcRouter {
// Start the server
await this.emailServer.start();
// Wire delivery events to MetricsManager for time-series tracking
// Wire delivery events to MetricsManager and logger
if (this.metricsManager && this.emailServer.deliverySystem) {
this.emailServer.deliverySystem.on('deliveryStart', (item: any) => {
this.metricsManager.trackEmailReceived(item?.from);
logger.log('info', `Email delivery started: ${item?.from}${item?.to}`, { zone: 'email' });
});
this.emailServer.deliverySystem.on('deliverySuccess', (item: any) => {
this.metricsManager.trackEmailSent(item?.to);
logger.log('info', `Email delivered to ${item?.to}`, { zone: 'email' });
});
this.emailServer.deliverySystem.on('deliveryFailed', (item: any, error: any) => {
this.metricsManager.trackEmailFailed(item?.to, error?.message);
logger.log('warn', `Email delivery failed to ${item?.to}: ${error?.message}`, { zone: 'email' });
});
}
if (this.metricsManager && this.emailServer) {
this.emailServer.on('bounceProcessed', () => {
this.metricsManager.trackEmailBounced();
logger.log('warn', 'Email bounce processed', { zone: 'email' });
});
}
@@ -1203,9 +1223,18 @@ export class DcRouter {
await this.dnsServer.start();
logger.log('info', `DNS server started on UDP ${vmIpAddress}:53`);
// Wire DNS query events to MetricsManager for time-series tracking
// Wire DNS query events to MetricsManager and logger with adaptive rate limiting
if (this.metricsManager && this.dnsServer) {
const flushDnsBatch = () => {
if (this.dnsBatchCount > 0) {
logger.log('info', `DNS: ${this.dnsBatchCount} queries processed (rate limited)`, { zone: 'dns' });
this.dnsBatchCount = 0;
}
this.dnsBatchTimer = null;
};
this.dnsServer.on('query', (event: plugins.smartdns.dnsServerMod.IDnsQueryCompletedEvent) => {
// Metrics tracking
for (const question of event.questions) {
this.metricsManager.trackDnsQuery(
question.type,
@@ -1215,6 +1244,21 @@ export class DcRouter {
event.answered,
);
}
// Adaptive logging: individual logs up to 2/sec, then batch
const now = Date.now();
this.dnsLogWindow = this.dnsLogWindow.filter(t => now - t < 1000);
if (this.dnsLogWindow.length < 2) {
this.dnsLogWindow.push(now);
const summary = event.questions.map(q => `${q.type} ${q.name}`).join(', ');
logger.log('info', `DNS query: ${summary} (${event.responseTimeMs}ms, ${event.answered ? 'answered' : 'unanswered'})`, { zone: 'dns' });
} else {
this.dnsBatchCount++;
if (!this.dnsBatchTimer) {
this.dnsBatchTimer = setTimeout(flushDnsBatch, 5000);
}
}
});
}

View File

@@ -110,6 +110,9 @@ export class MetricsManager {
this.securityMetrics.incidents = [];
this.securityMetrics.lastResetDate = currentDate;
}
// Prune old time-series buckets every minute (don't wait for lazy query)
this.pruneOldBuckets();
}, 60000); // Check every minute
logger.log('info', 'MetricsManager started');
@@ -123,6 +126,12 @@ export class MetricsManager {
}
this.smartMetrics.stop();
// Clear caches and time-series buckets on shutdown
this.metricsCache.clear();
this.emailMinuteBuckets.clear();
this.dnsMinuteBuckets.clear();
logger.log('info', 'MetricsManager stopped');
}

View File

@@ -35,11 +35,7 @@ export class TunnelManager {
});
this.hub.on('edgeDisconnected', (data: { edgeId: string }) => {
const existing = this.edgeStatuses.get(data.edgeId);
if (existing) {
existing.connected = false;
existing.activeTunnels = 0;
}
this.edgeStatuses.delete(data.edgeId);
});
this.hub.on('streamOpened', (data: { edgeId: string; streamId: number }) => {

View File

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

View File

@@ -47,9 +47,6 @@ export class OpsViewNetwork extends DeesElement {
private lastChartUpdate = 0;
private chartUpdateThreshold = 1000; // Minimum ms between chart updates
private lastTrafficUpdateTime = 0;
private trafficUpdateInterval = 1000; // Update every 1 second
private requestCountHistory = new Map<number, number>(); // Track requests per time bucket
private trafficUpdateTimer: any = null;
private requestsPerSecHistory: number[] = []; // Track requests/sec over time for trend
private historyLoaded = false; // Whether server-side throughput history has been loaded
@@ -106,8 +103,6 @@ export class OpsViewNetwork extends DeesElement {
this.trafficDataIn = [...emptyData];
this.trafficDataOut = emptyData.map(point => ({ ...point }));
this.lastTrafficUpdateTime = now;
}
/**
@@ -413,11 +408,7 @@ export class OpsViewNetwork extends DeesElement {
const throughput = this.calculateThroughput();
const activeConnections = this.statsState.serverStats?.activeConnections || 0;
// Track requests/sec history for the trend sparkline
this.requestsPerSecHistory.push(reqPerSec);
if (this.requestsPerSecHistory.length > 20) {
this.requestsPerSecHistory.shift();
}
// Build trend data from pre-computed history (mutated in updateNetworkData, not here)
const trendData = [...this.requestsPerSecHistory];
while (trendData.length < 20) {
trendData.unshift(0);
@@ -529,6 +520,13 @@ export class OpsViewNetwork extends DeesElement {
}
private async updateNetworkData() {
// Track requests/sec history for the trend sparkline (moved out of render)
const reqPerSec = this.networkState.requestsPerSecond || 0;
this.requestsPerSecHistory.push(reqPerSec);
if (this.requestsPerSecHistory.length > 20) {
this.requestsPerSecHistory.shift();
}
// Only update if connections changed significantly
const newConnectionCount = this.networkState.connections.length;
const oldConnectionCount = this.networkRequests.length;
@@ -602,16 +600,13 @@ export class OpsViewNetwork extends DeesElement {
y: Math.round(throughputOutMbps * 10) / 10
};
// Efficient array updates - modify in place when possible
// In-place mutation then reassign for Lit reactivity (avoids 4 intermediate arrays)
if (this.trafficDataIn.length >= 60) {
// Remove oldest and add newest
this.trafficDataIn = [...this.trafficDataIn.slice(1), newDataPointIn];
this.trafficDataOut = [...this.trafficDataOut.slice(1), newDataPointOut];
} else {
// Still filling up the initial data
this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];
this.trafficDataIn.shift();
this.trafficDataOut.shift();
}
this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];
this.lastChartUpdate = now;
}