feat(ops/monitoring): add in-memory log buffer, metrics time-series and ops UI integration
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
import { logBuffer } from '../../logger.js';
|
||||
|
||||
export class LogsHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
@@ -64,6 +65,32 @@ export class LogsHandler {
|
||||
);
|
||||
}
|
||||
|
||||
private static mapLogLevel(smartlogLevel: string): 'debug' | 'info' | 'warn' | 'error' {
|
||||
switch (smartlogLevel) {
|
||||
case 'silly':
|
||||
case 'debug':
|
||||
return 'debug';
|
||||
case 'warn':
|
||||
return 'warn';
|
||||
case 'error':
|
||||
return 'error';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
private static deriveCategory(
|
||||
zone?: string,
|
||||
message?: string
|
||||
): 'smtp' | 'dns' | 'security' | 'system' | 'email' {
|
||||
const msg = (message || '').toLowerCase();
|
||||
if (msg.includes('[security:') || msg.includes('security')) return 'security';
|
||||
if (zone === 'email' || msg.includes('email') || msg.includes('smtp') || msg.includes('mta')) return 'email';
|
||||
if (zone === 'dns' || msg.includes('dns')) return 'dns';
|
||||
if (msg.includes('smtp')) return 'smtp';
|
||||
return 'system';
|
||||
}
|
||||
|
||||
private async getRecentLogs(
|
||||
level?: 'error' | 'warn' | 'info' | 'debug',
|
||||
category?: 'smtp' | 'dns' | 'security' | 'system' | 'email',
|
||||
@@ -78,42 +105,64 @@ export class LogsHandler {
|
||||
message: string;
|
||||
metadata?: any;
|
||||
}>> {
|
||||
// TODO: Implement actual log retrieval from storage or logger
|
||||
// For now, return mock data
|
||||
const mockLogs: Array<{
|
||||
// Compute a timestamp cutoff from timeRange
|
||||
let since: number | undefined;
|
||||
if (timeRange) {
|
||||
const rangeMs: Record<string, number> = {
|
||||
'1h': 3600000,
|
||||
'6h': 21600000,
|
||||
'24h': 86400000,
|
||||
'7d': 604800000,
|
||||
'30d': 2592000000,
|
||||
};
|
||||
since = Date.now() - (rangeMs[timeRange] || 86400000);
|
||||
}
|
||||
|
||||
// Map the UI level to smartlog levels for filtering
|
||||
const smartlogLevels: string[] | undefined = level
|
||||
? level === 'debug'
|
||||
? ['debug', 'silly']
|
||||
: level === 'info'
|
||||
? ['info', 'ok', 'success', 'note', 'lifecycle']
|
||||
: [level]
|
||||
: undefined;
|
||||
|
||||
// Fetch a larger batch from buffer, then apply category filter client-side
|
||||
const rawEntries = logBuffer.getEntries({
|
||||
level: smartlogLevels as any,
|
||||
search,
|
||||
since,
|
||||
limit: limit * 3, // over-fetch to compensate for category filtering
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
// Map ILogPackage → UI log format and apply category filter
|
||||
const mapped: Array<{
|
||||
timestamp: number;
|
||||
level: 'debug' | 'info' | 'warn' | 'error';
|
||||
category: 'smtp' | 'dns' | 'security' | 'system' | 'email';
|
||||
message: string;
|
||||
metadata?: any;
|
||||
}> = [];
|
||||
|
||||
const categories: Array<'smtp' | 'dns' | 'security' | 'system' | 'email'> = ['smtp', 'dns', 'security', 'system', 'email'];
|
||||
const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['info', 'warn', 'error', 'debug'];
|
||||
const now = Date.now();
|
||||
|
||||
// Generate some mock log entries
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const mockCategory = categories[Math.floor(Math.random() * categories.length)];
|
||||
const mockLevel = levels[Math.floor(Math.random() * levels.length)];
|
||||
|
||||
// Filter by requested criteria
|
||||
if (level && mockLevel !== level) continue;
|
||||
if (category && mockCategory !== category) continue;
|
||||
|
||||
mockLogs.push({
|
||||
timestamp: now - (i * 60000), // 1 minute apart
|
||||
level: mockLevel,
|
||||
category: mockCategory,
|
||||
message: `Sample log message ${i} from ${mockCategory}`,
|
||||
metadata: {
|
||||
requestId: plugins.uuid.v4(),
|
||||
},
|
||||
|
||||
for (const pkg of rawEntries) {
|
||||
const uiLevel = LogsHandler.mapLogLevel(pkg.level);
|
||||
const uiCategory = LogsHandler.deriveCategory(pkg.context?.zone, pkg.message);
|
||||
|
||||
if (category && uiCategory !== category) continue;
|
||||
|
||||
mapped.push({
|
||||
timestamp: pkg.timestamp,
|
||||
level: uiLevel,
|
||||
category: uiCategory,
|
||||
message: pkg.message,
|
||||
metadata: pkg.data,
|
||||
});
|
||||
|
||||
if (mapped.length >= limit) break;
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
return mockLogs.slice(offset, offset + limit);
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
private setupLogStream(
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
|
||||
import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
import { MetricsManager } from '../../monitoring/index.js';
|
||||
import { SecurityLogger } from '../../security/classes.securitylogger.js';
|
||||
|
||||
export class StatsHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
@@ -203,6 +204,11 @@ export class StatsHandler {
|
||||
if (sections.email) {
|
||||
promises.push(
|
||||
this.collectEmailStats().then(stats => {
|
||||
// Get time-series data from MetricsManager
|
||||
const timeSeries = this.opsServerRef.dcRouterRef.metricsManager
|
||||
? this.opsServerRef.dcRouterRef.metricsManager.getEmailTimeSeries(24)
|
||||
: undefined;
|
||||
|
||||
metrics.email = {
|
||||
sent: stats.sentToday,
|
||||
received: stats.receivedToday,
|
||||
@@ -212,6 +218,7 @@ export class StatsHandler {
|
||||
averageDeliveryTime: 0,
|
||||
deliveryRate: stats.deliveryRate,
|
||||
bounceRate: stats.bounceRate,
|
||||
timeSeries,
|
||||
};
|
||||
})
|
||||
);
|
||||
@@ -220,6 +227,11 @@ export class StatsHandler {
|
||||
if (sections.dns) {
|
||||
promises.push(
|
||||
this.collectDnsStats().then(stats => {
|
||||
// Get time-series data from MetricsManager
|
||||
const timeSeries = this.opsServerRef.dcRouterRef.metricsManager
|
||||
? this.opsServerRef.dcRouterRef.metricsManager.getDnsTimeSeries(24)
|
||||
: undefined;
|
||||
|
||||
metrics.dns = {
|
||||
totalQueries: stats.totalQueries,
|
||||
cacheHits: stats.cacheHits,
|
||||
@@ -228,6 +240,7 @@ export class StatsHandler {
|
||||
activeDomains: stats.topDomains.length,
|
||||
averageResponseTime: 0,
|
||||
queryTypes: stats.queryTypes,
|
||||
timeSeries,
|
||||
};
|
||||
})
|
||||
);
|
||||
@@ -236,6 +249,19 @@ export class StatsHandler {
|
||||
if (sections.security && this.opsServerRef.dcRouterRef.metricsManager) {
|
||||
promises.push(
|
||||
this.opsServerRef.dcRouterRef.metricsManager.getSecurityStats().then(stats => {
|
||||
// Get recent events from the SecurityLogger singleton
|
||||
const securityLogger = SecurityLogger.getInstance();
|
||||
const recentEvents = securityLogger.getRecentEvents(50).map((evt) => ({
|
||||
timestamp: evt.timestamp,
|
||||
level: evt.level,
|
||||
type: evt.type,
|
||||
message: evt.message,
|
||||
details: evt.details,
|
||||
ipAddress: evt.ipAddress,
|
||||
domain: evt.domain,
|
||||
success: evt.success,
|
||||
}));
|
||||
|
||||
metrics.security = {
|
||||
blockedIPs: stats.blockedIPs,
|
||||
reputationScores: {},
|
||||
@@ -244,6 +270,7 @@ export class StatsHandler {
|
||||
phishingDetected: stats.phishingDetected,
|
||||
authenticationFailures: stats.authFailures,
|
||||
suspiciousActivities: stats.totalThreatsBlocked,
|
||||
recentEvents,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user