refactor: strengthen client boundaries and quality gates

This commit is contained in:
hectorzhao
2026-08-28 14:26:58 +08:00
parent 3af145abe5
commit ad27acad7e
51 changed files with 7703 additions and 697 deletions
+47 -7
View File
@@ -54,7 +54,9 @@ function escapeLabel(value: string) {
function metricLine(name: string, value: number, labels?: Record<string, string>) {
const suffix = labels
? `{${Object.entries(labels).map(([key, item]) => `${key}="${escapeLabel(item)}"`).join(',')}}`
? `{${Object.entries(labels)
.map(([key, item]) => `${key}="${escapeLabel(item)}"`)
.join(',')}}`
: '';
return `${name}${suffix} ${Number.isFinite(value) ? value : 0}`;
}
@@ -78,6 +80,7 @@ export class MetricsService implements OnModuleDestroy {
private inboundWorkflowConfiguredSlots = 0;
private inboundWorkflowInFlightSlots = 0;
private readonly inboundWorkflowResults = new Map<string, number>();
private readonly authProtectionResults = new Map<string, number>();
constructor() {
this.eventLoopDelay.enable();
@@ -176,6 +179,14 @@ export class MetricsService implements OnModuleDestroy {
this.inboundWorkflowResults.set(result, (this.inboundWorkflowResults.get(result) ?? 0) + 1);
}
recordAuthProtectionResult(
event: 'captcha_allowed' | 'captcha_rejected' | 'login_locked',
scope: 'none' | 'account' | 'ip' | 'pair' = 'none',
) {
const key = `${event}\u0000${scope}`;
this.authProtectionResults.set(key, (this.authProtectionResults.get(key) ?? 0) + 1);
}
render() {
const memory = process.memoryUsage();
const uptime = Number(process.hrtime.bigint() - this.startedAt) / 1_000_000_000;
@@ -194,7 +205,10 @@ export class MetricsService implements OnModuleDestroy {
metricLine('cmpp_api_nodejs_heap_total_bytes', memory.heapTotal),
'# HELP cmpp_api_nodejs_event_loop_lag_p99_seconds Event loop delay p99 since the previous scrape.',
'# TYPE cmpp_api_nodejs_event_loop_lag_p99_seconds gauge',
metricLine('cmpp_api_nodejs_event_loop_lag_p99_seconds', this.eventLoopDelay.count ? this.eventLoopDelay.percentile(99) / 1_000_000_000 : 0),
metricLine(
'cmpp_api_nodejs_event_loop_lag_p99_seconds',
this.eventLoopDelay.count ? this.eventLoopDelay.percentile(99) / 1_000_000_000 : 0,
),
'# HELP cmpp_api_http_requests_in_flight Current API requests in flight.',
'# TYPE cmpp_api_http_requests_in_flight gauge',
metricLine('cmpp_api_http_requests_in_flight', this.inFlight),
@@ -226,16 +240,26 @@ export class MetricsService implements OnModuleDestroy {
metricLine('cmpp_worker_inbound_workflow_slots', this.inboundWorkflowInFlightSlots, { state: 'in_flight' }),
'# HELP cmpp_worker_inbound_workflow_oldest_pending_age_seconds Age of the oldest pending durable workflow.',
'# TYPE cmpp_worker_inbound_workflow_oldest_pending_age_seconds gauge',
metricLine('cmpp_worker_inbound_workflow_oldest_pending_age_seconds', this.inboundWorkflowOldestPendingAgeSeconds),
metricLine(
'cmpp_worker_inbound_workflow_oldest_pending_age_seconds',
this.inboundWorkflowOldestPendingAgeSeconds,
),
'# HELP cmpp_worker_inbound_workflow_results_total Durable workflow processing outcomes.',
'# TYPE cmpp_worker_inbound_workflow_results_total counter',
'# HELP cmpp_api_auth_protection_events_total Authentication protection outcomes by bounded event and scope.',
'# TYPE cmpp_api_auth_protection_events_total counter',
];
for (const [key, metric] of this.http) {
const [method, route, status] = key.split('\u0000');
const labels = { method, route, status };
lines.push(metricLine('cmpp_api_http_requests_total', metric.count, labels));
HTTP_DURATION_BUCKETS.forEach((bucket, index) => {
lines.push(metricLine('cmpp_api_http_request_duration_seconds_bucket', metric.buckets[index], { ...labels, le: String(bucket) }));
lines.push(
metricLine('cmpp_api_http_request_duration_seconds_bucket', metric.buckets[index], {
...labels,
le: String(bucket),
}),
);
});
lines.push(metricLine('cmpp_api_http_request_duration_seconds_bucket', metric.count, { ...labels, le: '+Inf' }));
lines.push(metricLine('cmpp_api_http_request_duration_seconds_sum', metric.durationSum, labels));
@@ -245,9 +269,16 @@ export class MetricsService implements OnModuleDestroy {
const [stage, result] = key.split('\u0000');
const labels = { stage, result };
CMPP_INBOUND_DURATION_BUCKETS.forEach((bucket, index) => {
lines.push(metricLine('cmpp_api_cmpp_inbound_stage_duration_seconds_bucket', metric.buckets[index], { ...labels, le: String(bucket) }));
lines.push(
metricLine('cmpp_api_cmpp_inbound_stage_duration_seconds_bucket', metric.buckets[index], {
...labels,
le: String(bucket),
}),
);
});
lines.push(metricLine('cmpp_api_cmpp_inbound_stage_duration_seconds_bucket', metric.count, { ...labels, le: '+Inf' }));
lines.push(
metricLine('cmpp_api_cmpp_inbound_stage_duration_seconds_bucket', metric.count, { ...labels, le: '+Inf' }),
);
lines.push(metricLine('cmpp_api_cmpp_inbound_stage_duration_seconds_sum', metric.durationSum, labels));
lines.push(metricLine('cmpp_api_cmpp_inbound_stage_duration_seconds_count', metric.count, labels));
}
@@ -255,7 +286,12 @@ export class MetricsService implements OnModuleDestroy {
const [stage, result] = key.split('\u0000');
const labels = { stage, result };
SEND_WORKER_DURATION_BUCKETS.forEach((bucket, index) => {
lines.push(metricLine('cmpp_worker_send_stage_duration_seconds_bucket', metric.buckets[index], { ...labels, le: String(bucket) }));
lines.push(
metricLine('cmpp_worker_send_stage_duration_seconds_bucket', metric.buckets[index], {
...labels,
le: String(bucket),
}),
);
});
lines.push(metricLine('cmpp_worker_send_stage_duration_seconds_bucket', metric.count, { ...labels, le: '+Inf' }));
lines.push(metricLine('cmpp_worker_send_stage_duration_seconds_sum', metric.durationSum, labels));
@@ -273,6 +309,10 @@ export class MetricsService implements OnModuleDestroy {
for (const [result, count] of this.inboundWorkflowResults) {
lines.push(metricLine('cmpp_worker_inbound_workflow_results_total', count, { result }));
}
for (const [key, count] of this.authProtectionResults) {
const [event, scope] = key.split('\u0000');
lines.push(metricLine('cmpp_api_auth_protection_events_total', count, { event, scope }));
}
this.eventLoopDelay.reset();
return `${lines.join('\n')}\n`;
}