Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
319
marketing-agent/services/compliance-guard/src/utils/metrics.js
Normal file
319
marketing-agent/services/compliance-guard/src/utils/metrics.js
Normal file
@@ -0,0 +1,319 @@
|
||||
import promClient from 'prom-client';
|
||||
import { logger } from './logger.js';
|
||||
|
||||
// Create a Registry
|
||||
const register = new promClient.Registry();
|
||||
|
||||
// Add default metrics
|
||||
promClient.collectDefaultMetrics({ register });
|
||||
|
||||
// Define custom metrics
|
||||
const httpRequestDuration = new promClient.Histogram({
|
||||
name: 'compliance_guard_http_request_duration_seconds',
|
||||
help: 'Duration of HTTP requests in seconds',
|
||||
labelNames: ['method', 'route', 'status_code'],
|
||||
buckets: [0.1, 0.5, 1, 2, 5]
|
||||
});
|
||||
|
||||
const apiCallCounter = new promClient.Counter({
|
||||
name: 'compliance_guard_api_calls_total',
|
||||
help: 'Total number of API calls',
|
||||
labelNames: ['endpoint', 'status']
|
||||
});
|
||||
|
||||
const consentCounter = new promClient.Counter({
|
||||
name: 'compliance_guard_consent_total',
|
||||
help: 'Total number of consent actions',
|
||||
labelNames: ['type', 'action', 'status']
|
||||
});
|
||||
|
||||
const dataRequestCounter = new promClient.Counter({
|
||||
name: 'compliance_guard_data_requests_total',
|
||||
help: 'Total number of data requests',
|
||||
labelNames: ['type', 'status']
|
||||
});
|
||||
|
||||
const violationCounter = new promClient.Counter({
|
||||
name: 'compliance_guard_violations_total',
|
||||
help: 'Total number of compliance violations',
|
||||
labelNames: ['type', 'severity']
|
||||
});
|
||||
|
||||
const activeDataRequestsGauge = new promClient.Gauge({
|
||||
name: 'compliance_guard_active_data_requests',
|
||||
help: 'Number of active data requests',
|
||||
labelNames: ['type']
|
||||
});
|
||||
|
||||
const pendingConsentRenewalsGauge = new promClient.Gauge({
|
||||
name: 'compliance_guard_pending_consent_renewals',
|
||||
help: 'Number of consents pending renewal'
|
||||
});
|
||||
|
||||
const complianceScoreGauge = new promClient.Gauge({
|
||||
name: 'compliance_guard_compliance_score',
|
||||
help: 'Overall compliance score',
|
||||
labelNames: ['regulation']
|
||||
});
|
||||
|
||||
const dataProcessingLatency = new promClient.Histogram({
|
||||
name: 'compliance_guard_data_processing_latency_seconds',
|
||||
help: 'Latency of data processing operations',
|
||||
labelNames: ['operation'],
|
||||
buckets: [0.01, 0.05, 0.1, 0.5, 1, 5, 10]
|
||||
});
|
||||
|
||||
// Register metrics
|
||||
register.registerMetric(httpRequestDuration);
|
||||
register.registerMetric(apiCallCounter);
|
||||
register.registerMetric(consentCounter);
|
||||
register.registerMetric(dataRequestCounter);
|
||||
register.registerMetric(violationCounter);
|
||||
register.registerMetric(activeDataRequestsGauge);
|
||||
register.registerMetric(pendingConsentRenewalsGauge);
|
||||
register.registerMetric(complianceScoreGauge);
|
||||
register.registerMetric(dataProcessingLatency);
|
||||
|
||||
// Metrics recording functions
|
||||
export const metrics = {
|
||||
/**
|
||||
* Record HTTP request duration
|
||||
*/
|
||||
recordHttpRequest: (method, route, statusCode, duration) => {
|
||||
httpRequestDuration
|
||||
.labels(method, route, statusCode.toString())
|
||||
.observe(duration);
|
||||
},
|
||||
|
||||
/**
|
||||
* Record API call
|
||||
*/
|
||||
recordApiCall: (endpoint, status) => {
|
||||
apiCallCounter
|
||||
.labels(endpoint, status)
|
||||
.inc();
|
||||
},
|
||||
|
||||
/**
|
||||
* Record consent action
|
||||
*/
|
||||
recordConsentAction: (type, action, status) => {
|
||||
consentCounter
|
||||
.labels(type, action, status)
|
||||
.inc();
|
||||
},
|
||||
|
||||
/**
|
||||
* Record data request
|
||||
*/
|
||||
recordDataRequest: (type, status) => {
|
||||
dataRequestCounter
|
||||
.labels(type, status)
|
||||
.inc();
|
||||
},
|
||||
|
||||
/**
|
||||
* Record violation
|
||||
*/
|
||||
recordViolation: (type, severity) => {
|
||||
violationCounter
|
||||
.labels(type, severity)
|
||||
.inc();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update active data requests gauge
|
||||
*/
|
||||
updateActiveDataRequests: async (getCountsByType) => {
|
||||
try {
|
||||
const counts = await getCountsByType();
|
||||
for (const [type, count] of Object.entries(counts)) {
|
||||
activeDataRequestsGauge
|
||||
.labels(type)
|
||||
.set(count);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to update active data requests metric:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update pending consent renewals
|
||||
*/
|
||||
updatePendingConsentRenewals: async (getCount) => {
|
||||
try {
|
||||
const count = await getCount();
|
||||
pendingConsentRenewalsGauge.set(count);
|
||||
} catch (error) {
|
||||
logger.error('Failed to update pending consent renewals metric:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update compliance score
|
||||
*/
|
||||
updateComplianceScore: (regulation, score) => {
|
||||
complianceScoreGauge
|
||||
.labels(regulation)
|
||||
.set(score);
|
||||
},
|
||||
|
||||
/**
|
||||
* Record data processing latency
|
||||
*/
|
||||
recordDataProcessingLatency: (operation, duration) => {
|
||||
dataProcessingLatency
|
||||
.labels(operation)
|
||||
.observe(duration);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get metrics for Prometheus
|
||||
*/
|
||||
getMetrics: async () => {
|
||||
return await register.metrics();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get metrics in JSON format
|
||||
*/
|
||||
getMetricsJSON: async () => {
|
||||
return await register.getMetricsAsJSON();
|
||||
}
|
||||
};
|
||||
|
||||
// Middleware to record HTTP metrics
|
||||
export const metricsMiddleware = (req, res, next) => {
|
||||
const start = Date.now();
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = (Date.now() - start) / 1000;
|
||||
const route = req.route ? req.route.path : 'unknown';
|
||||
|
||||
metrics.recordHttpRequest(
|
||||
req.method,
|
||||
route,
|
||||
res.statusCode,
|
||||
duration
|
||||
);
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
// Custom metrics for compliance monitoring
|
||||
class ComplianceMetrics {
|
||||
constructor() {
|
||||
this.resetCounters();
|
||||
}
|
||||
|
||||
resetCounters() {
|
||||
this.counters = {
|
||||
consentGrants: 0,
|
||||
consentWithdrawals: 0,
|
||||
dataRequests: 0,
|
||||
dataRequestsCompleted: 0,
|
||||
violations: 0,
|
||||
violationsResolved: 0
|
||||
};
|
||||
}
|
||||
|
||||
incrementConsentGrants() {
|
||||
this.counters.consentGrants++;
|
||||
}
|
||||
|
||||
incrementConsentWithdrawals() {
|
||||
this.counters.consentWithdrawals++;
|
||||
}
|
||||
|
||||
incrementDataRequests() {
|
||||
this.counters.dataRequests++;
|
||||
}
|
||||
|
||||
incrementDataRequestsCompleted() {
|
||||
this.counters.dataRequestsCompleted++;
|
||||
}
|
||||
|
||||
incrementViolations() {
|
||||
this.counters.violations++;
|
||||
}
|
||||
|
||||
incrementViolationsResolved() {
|
||||
this.counters.violationsResolved++;
|
||||
}
|
||||
|
||||
getComplianceHealth() {
|
||||
const health = {
|
||||
status: 'healthy',
|
||||
metrics: {
|
||||
consentCompletionRate: this.counters.consentWithdrawals > 0
|
||||
? (this.counters.consentGrants / (this.counters.consentGrants + this.counters.consentWithdrawals))
|
||||
: 1,
|
||||
dataRequestCompletionRate: this.counters.dataRequests > 0
|
||||
? (this.counters.dataRequestsCompleted / this.counters.dataRequests)
|
||||
: 1,
|
||||
violationResolutionRate: this.counters.violations > 0
|
||||
? (this.counters.violationsResolved / this.counters.violations)
|
||||
: 1
|
||||
}
|
||||
};
|
||||
|
||||
// Determine overall health status
|
||||
const rates = Object.values(health.metrics);
|
||||
const avgRate = rates.reduce((a, b) => a + b, 0) / rates.length;
|
||||
|
||||
if (avgRate >= 0.95) {
|
||||
health.status = 'healthy';
|
||||
} else if (avgRate >= 0.80) {
|
||||
health.status = 'warning';
|
||||
} else {
|
||||
health.status = 'critical';
|
||||
}
|
||||
|
||||
return health;
|
||||
}
|
||||
}
|
||||
|
||||
export const complianceMetrics = new ComplianceMetrics();
|
||||
|
||||
// Periodic metrics update
|
||||
export const startMetricsCollection = (models) => {
|
||||
// Update metrics every minute
|
||||
setInterval(async () => {
|
||||
try {
|
||||
// Update active data requests
|
||||
if (models.DataRequest) {
|
||||
const counts = await models.DataRequest.aggregate([
|
||||
{ $match: { status: { $in: ['pending', 'processing'] } } },
|
||||
{ $group: { _id: '$type', count: { $sum: 1 } } }
|
||||
]);
|
||||
|
||||
const countsByType = counts.reduce((acc, item) => {
|
||||
acc[item._id] = item.count;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
await metrics.updateActiveDataRequests(async () => countsByType);
|
||||
}
|
||||
|
||||
// Update pending consent renewals
|
||||
if (models.ConsentRecord) {
|
||||
await metrics.updatePendingConsentRenewals(async () => {
|
||||
const thirtyDaysFromNow = new Date();
|
||||
thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30);
|
||||
|
||||
return await models.ConsentRecord.countDocuments({
|
||||
status: 'granted',
|
||||
renewable: true,
|
||||
expiryDate: {
|
||||
$gte: new Date(),
|
||||
$lte: thirtyDaysFromNow
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to update metrics:', error);
|
||||
}
|
||||
}, 60000); // Every minute
|
||||
};
|
||||
Reference in New Issue
Block a user