@engjts/nexus 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/BENCHMARK_REPORT.md +0 -343
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1956
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -250
- package/src/advanced/playground/types.ts +0 -49
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1453
- package/src/core/context-pool.ts +0 -79
- package/src/core/context.ts +0 -856
- package/src/core/index.ts +0 -94
- package/src/core/middleware.ts +0 -272
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -623
- package/src/core/router/index.ts +0 -260
- package/src/core/router/radix-tree.ts +0 -242
- package/src/core/serializer.ts +0 -397
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -616
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -281
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- package/tsconfig.json +0 -39
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { AlertChannelAdapterRegistry, AlertChannelAdapter, AlertDefinition as BaseAlertDefinition } from './adapters';
|
|
2
|
-
import { AlertingOptions } from './types';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Alert manager for monitoring alerts with adapter-based channel system
|
|
6
|
-
*/
|
|
7
|
-
export class AlertManager {
|
|
8
|
-
private options: AlertingOptions;
|
|
9
|
-
private alertHistory: Array<{ alert: string; timestamp: number; value: any; }> = [];
|
|
10
|
-
private alertState: Map<string, { lastTriggered: number; count: number; }> = new Map();
|
|
11
|
-
private adapterRegistry: AlertChannelAdapterRegistry;
|
|
12
|
-
private channelConfigs: Record<string, any> = {};
|
|
13
|
-
|
|
14
|
-
constructor(options: AlertingOptions = {}) {
|
|
15
|
-
this.options = options;
|
|
16
|
-
this.adapterRegistry = new AlertChannelAdapterRegistry();
|
|
17
|
-
|
|
18
|
-
// Store channel configurations
|
|
19
|
-
if (options.channels) {
|
|
20
|
-
this.channelConfigs = options.channels as Record<string, any>;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Register a custom alert channel adapter
|
|
26
|
-
*/
|
|
27
|
-
registerAdapter(name: string, adapter: AlertChannelAdapter): void {
|
|
28
|
-
this.adapterRegistry.register(name, adapter);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Get the adapter registry
|
|
33
|
-
*/
|
|
34
|
-
getAdapterRegistry(): AlertChannelAdapterRegistry {
|
|
35
|
-
return this.adapterRegistry;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async checkAndTrigger(alertName: string, currentValue: number) {
|
|
39
|
-
const alert = this.options.alerts?.find(a => a.name === alertName);
|
|
40
|
-
if (!alert) return;
|
|
41
|
-
|
|
42
|
-
const threshold = alert.threshold ?? 0;
|
|
43
|
-
const shouldTrigger = this.evaluateCondition(alert.condition, currentValue, threshold);
|
|
44
|
-
|
|
45
|
-
if (shouldTrigger) {
|
|
46
|
-
const state = this.alertState.get(alertName) || { lastTriggered: 0, count: 0 };
|
|
47
|
-
const now = Date.now();
|
|
48
|
-
|
|
49
|
-
// Prevent alert flooding (minimum 1 minute between same alerts)
|
|
50
|
-
if (now - state.lastTriggered > 60000) {
|
|
51
|
-
state.lastTriggered = now;
|
|
52
|
-
state.count++;
|
|
53
|
-
this.alertState.set(alertName, state);
|
|
54
|
-
|
|
55
|
-
this.alertHistory.push({ alert: alertName, timestamp: now, value: currentValue });
|
|
56
|
-
|
|
57
|
-
await this.sendAlert(alert, currentValue);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private evaluateCondition(condition: string, value: number, threshold: number): boolean {
|
|
63
|
-
// Simple condition parsing: "error_rate > 0.05" or "p95_duration > 1000"
|
|
64
|
-
if (condition.includes('>')) {
|
|
65
|
-
return value > threshold;
|
|
66
|
-
}
|
|
67
|
-
if (condition.includes('<')) {
|
|
68
|
-
return value < threshold;
|
|
69
|
-
}
|
|
70
|
-
if (condition.includes('>=')) {
|
|
71
|
-
return value >= threshold;
|
|
72
|
-
}
|
|
73
|
-
if (condition.includes('<=')) {
|
|
74
|
-
return value <= threshold;
|
|
75
|
-
}
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private async sendAlert(alert: BaseAlertDefinition, value: any) {
|
|
80
|
-
for (const channelName of alert.channels) {
|
|
81
|
-
try {
|
|
82
|
-
const adapter = this.adapterRegistry.get(channelName);
|
|
83
|
-
if (!adapter) {
|
|
84
|
-
console.warn(`No adapter registered for channel: ${channelName}`);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const config = this.channelConfigs[channelName];
|
|
89
|
-
if (!config) {
|
|
90
|
-
console.warn(`No configuration for channel: ${channelName}`);
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (!adapter.validate(config)) {
|
|
95
|
-
console.warn(`Invalid configuration for channel: ${channelName}`);
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
await adapter.send(alert, value, config);
|
|
100
|
-
} catch (error) {
|
|
101
|
-
console.error(`Failed to send alert to ${channelName}:`, error);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
getAlertHistory() {
|
|
107
|
-
return [...this.alertHistory];
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { LabeledMetric, HistogramRecord, GaugeRecord, MetricDefinition } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Enhanced metric registry with labels, gauges, and proper Prometheus formatting
|
|
5
|
-
*/
|
|
6
|
-
export class MetricRegistry {
|
|
7
|
-
private counters: LabeledMetric<number> = new Map();
|
|
8
|
-
private histograms: LabeledMetric<HistogramRecord> = new Map();
|
|
9
|
-
private gauges: LabeledMetric<GaugeRecord> = new Map();
|
|
10
|
-
private definitions: Map<string, MetricDefinition> = new Map();
|
|
11
|
-
private defaultLabels: Record<string, string> = {};
|
|
12
|
-
|
|
13
|
-
setDefaultLabels(labels: Record<string, string>) {
|
|
14
|
-
this.defaultLabels = labels;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
define(definition: MetricDefinition) {
|
|
18
|
-
this.definitions.set(definition.name, definition);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
private formatLabels(labels: Record<string, string> = {}): string {
|
|
22
|
-
const allLabels = { ...this.defaultLabels, ...labels };
|
|
23
|
-
const entries = Object.entries(allLabels);
|
|
24
|
-
if (entries.length === 0) return '';
|
|
25
|
-
return '{' + entries.map(([k, v]) => `${k}="${v}"`).join(',') + '}';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
private getKey(name: string, labels: Record<string, string> = {}): string {
|
|
29
|
-
return name + this.formatLabels(labels);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
increment(name: string, value: number = 1, labels: Record<string, string> = {}) {
|
|
33
|
-
const key = this.getKey(name, labels);
|
|
34
|
-
this.counters.set(key, (this.counters.get(key) || 0) + value);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
decrement(name: string, value: number = 1, labels: Record<string, string> = {}) {
|
|
38
|
-
this.increment(name, -value, labels);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
gauge(name: string, value: number, labels: Record<string, string> = {}) {
|
|
42
|
-
const key = this.getKey(name, labels);
|
|
43
|
-
this.gauges.set(key, { value, timestamp: Date.now() });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
observe(
|
|
47
|
-
name: string,
|
|
48
|
-
value: number,
|
|
49
|
-
labels: Record<string, string> = {},
|
|
50
|
-
buckets: number[] = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
|
|
51
|
-
) {
|
|
52
|
-
const key = this.getKey(name, labels);
|
|
53
|
-
const existing = this.histograms.get(key);
|
|
54
|
-
const record: HistogramRecord = existing || {
|
|
55
|
-
count: 0,
|
|
56
|
-
sum: 0,
|
|
57
|
-
buckets: buckets.map(() => 0),
|
|
58
|
-
bucketBoundaries: buckets
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
record.count += 1;
|
|
62
|
-
record.sum += value;
|
|
63
|
-
record.buckets = record.buckets.map((bucketValue, index) => {
|
|
64
|
-
return bucketValue + (value <= record.bucketBoundaries[index] ? 1 : 0);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
this.histograms.set(key, record);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
toPrometheus(): string {
|
|
71
|
-
const lines: string[] = [];
|
|
72
|
-
const processedMetrics = new Set<string>();
|
|
73
|
-
|
|
74
|
-
// Counters
|
|
75
|
-
for (const [key, value] of this.counters.entries()) {
|
|
76
|
-
const baseName = key.split('{')[0];
|
|
77
|
-
if (!processedMetrics.has(`counter:${baseName}`)) {
|
|
78
|
-
const def = this.definitions.get(baseName);
|
|
79
|
-
if (def?.help) lines.push(`# HELP ${baseName} ${def.help}`);
|
|
80
|
-
lines.push(`# TYPE ${baseName} counter`);
|
|
81
|
-
processedMetrics.add(`counter:${baseName}`);
|
|
82
|
-
}
|
|
83
|
-
lines.push(`${key} ${value}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Gauges
|
|
87
|
-
for (const [key, record] of this.gauges.entries()) {
|
|
88
|
-
const baseName = key.split('{')[0];
|
|
89
|
-
if (!processedMetrics.has(`gauge:${baseName}`)) {
|
|
90
|
-
const def = this.definitions.get(baseName);
|
|
91
|
-
if (def?.help) lines.push(`# HELP ${baseName} ${def.help}`);
|
|
92
|
-
lines.push(`# TYPE ${baseName} gauge`);
|
|
93
|
-
processedMetrics.add(`gauge:${baseName}`);
|
|
94
|
-
}
|
|
95
|
-
lines.push(`${key} ${record.value}`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Histograms
|
|
99
|
-
for (const [key, record] of this.histograms.entries()) {
|
|
100
|
-
const baseName = key.split('{')[0];
|
|
101
|
-
const labelsPart = key.includes('{') ? key.slice(key.indexOf('{')) : '';
|
|
102
|
-
const labelsInner = labelsPart.slice(1, -1);
|
|
103
|
-
|
|
104
|
-
if (!processedMetrics.has(`histogram:${baseName}`)) {
|
|
105
|
-
const def = this.definitions.get(baseName);
|
|
106
|
-
if (def?.help) lines.push(`# HELP ${baseName} ${def.help}`);
|
|
107
|
-
lines.push(`# TYPE ${baseName} histogram`);
|
|
108
|
-
processedMetrics.add(`histogram:${baseName}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
record.bucketBoundaries.forEach((boundary, index) => {
|
|
112
|
-
const bucketLabels = labelsInner ? `${labelsInner},le="${boundary}"` : `le="${boundary}"`;
|
|
113
|
-
lines.push(`${baseName}_bucket{${bucketLabels}} ${record.buckets[index]}`);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const infLabels = labelsInner ? `${labelsInner},le="+Inf"` : `le="+Inf"`;
|
|
117
|
-
lines.push(`${baseName}_bucket{${infLabels}} ${record.count}`);
|
|
118
|
-
|
|
119
|
-
if (labelsPart) {
|
|
120
|
-
lines.push(`${baseName}_sum${labelsPart} ${record.sum}`);
|
|
121
|
-
lines.push(`${baseName}_count${labelsPart} ${record.count}`);
|
|
122
|
-
} else {
|
|
123
|
-
lines.push(`${baseName}_sum ${record.sum}`);
|
|
124
|
-
lines.push(`${baseName}_count ${record.count}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return lines.join('\n');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
snapshot() {
|
|
132
|
-
return {
|
|
133
|
-
counters: Object.fromEntries(this.counters),
|
|
134
|
-
gauges: Object.fromEntries(
|
|
135
|
-
Array.from(this.gauges.entries()).map(([k, v]) => [k, v.value])
|
|
136
|
-
),
|
|
137
|
-
histograms: Object.fromEntries(
|
|
138
|
-
Array.from(this.histograms.entries()).map(([k, v]) => [
|
|
139
|
-
k,
|
|
140
|
-
{ count: v.count, sum: v.sum, mean: v.count > 0 ? v.sum / v.count : 0 }
|
|
141
|
-
])
|
|
142
|
-
)
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
reset() {
|
|
147
|
-
this.counters.clear();
|
|
148
|
-
this.histograms.clear();
|
|
149
|
-
this.gauges.clear();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import { performance } from 'perf_hooks';
|
|
2
|
-
import { Context, Response } from '../../core/types';
|
|
3
|
-
import { AlertManager } from './AlertManager';
|
|
4
|
-
import { APMManager } from './APMManager';
|
|
5
|
-
import { MetricRegistry } from './MetricRegistry';
|
|
6
|
-
import { StructuredLogger } from './StructuredLogger';
|
|
7
|
-
import { TracingManager } from './TracingManager';
|
|
8
|
-
import { ObservabilityOptions, Span, LogEntry } from './types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Enhanced Observability Center with full APM, tracing, logging, and alerting
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export class ObservabilityCenter {
|
|
16
|
-
private metrics: MetricRegistry = new MetricRegistry();
|
|
17
|
-
private tracing?: TracingManager;
|
|
18
|
-
private logger: StructuredLogger;
|
|
19
|
-
private apm?: APMManager;
|
|
20
|
-
private alertManager?: AlertManager;
|
|
21
|
-
private options: ObservabilityOptions;
|
|
22
|
-
|
|
23
|
-
constructor(options: ObservabilityOptions = {}) {
|
|
24
|
-
this.options = options;
|
|
25
|
-
|
|
26
|
-
// Initialize logger
|
|
27
|
-
this.logger = new StructuredLogger(options.logging);
|
|
28
|
-
|
|
29
|
-
// Initialize tracing if enabled
|
|
30
|
-
if (options.tracing?.enabled) {
|
|
31
|
-
this.tracing = new TracingManager(options.tracing);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Initialize APM if enabled
|
|
35
|
-
if (options.apm?.enabled) {
|
|
36
|
-
this.apm = new APMManager(options.apm);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Initialize alerting if enabled
|
|
40
|
-
if (options.alerting?.enabled) {
|
|
41
|
-
this.alertManager = new AlertManager(options.alerting);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Set default labels for metrics
|
|
45
|
-
if (options.metrics?.defaultLabels) {
|
|
46
|
-
this.metrics.setDefaultLabels(options.metrics.defaultLabels);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Register custom metrics
|
|
50
|
-
if (options.metrics?.custom) {
|
|
51
|
-
for (const def of options.metrics.custom) {
|
|
52
|
-
this.metrics.define(def);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Register default metrics
|
|
57
|
-
this.metrics.define({ name: 'http_requests_total', type: 'counter', help: 'Total HTTP requests' });
|
|
58
|
-
this.metrics.define({ name: 'http_request_duration_seconds', type: 'histogram', help: 'HTTP request duration in seconds' });
|
|
59
|
-
this.metrics.define({ name: 'http_request_size_bytes', type: 'histogram', help: 'HTTP request size in bytes' });
|
|
60
|
-
this.metrics.define({ name: 'http_response_size_bytes', type: 'histogram', help: 'HTTP response size in bytes' });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Record an HTTP request
|
|
65
|
-
*/
|
|
66
|
-
recordRequest(ctx: Context, response: Response, durationMs: number, error?: Error) {
|
|
67
|
-
const labels = {
|
|
68
|
-
method: ctx.method,
|
|
69
|
-
path: this.normalizePath(ctx.path),
|
|
70
|
-
status: String(response.statusCode)
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if (this.options.metrics?.enabled) {
|
|
74
|
-
this.metrics.increment('http_requests_total', 1, labels);
|
|
75
|
-
this.metrics.observe('http_request_duration_seconds', durationMs / 1000, labels);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Log request if enabled
|
|
79
|
-
if (this.options.logging?.requests?.enabled) {
|
|
80
|
-
const excludePaths = this.options.logging.requests.excludePaths ?? [];
|
|
81
|
-
if (!excludePaths.some(p => ctx.path.startsWith(p))) {
|
|
82
|
-
this.logger.info('HTTP Request', {
|
|
83
|
-
method: ctx.method,
|
|
84
|
-
path: ctx.path,
|
|
85
|
-
status: response.statusCode,
|
|
86
|
-
duration: `${durationMs.toFixed(2)}ms`,
|
|
87
|
-
...(this.options.logging.requests.includeBody && ctx.body ? { body: ctx.body } : {})
|
|
88
|
-
}, ctx.correlationId);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Check for slow requests and alert
|
|
93
|
-
if (this.alertManager && durationMs > (this.options.apm?.slowQueryThreshold ?? 1000)) {
|
|
94
|
-
this.alertManager.checkAndTrigger('High Response Time', durationMs);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Record error if present
|
|
98
|
-
if (error) {
|
|
99
|
-
this.logger.error('Request error', error, { path: ctx.path, method: ctx.method }, ctx.correlationId);
|
|
100
|
-
this.metrics.increment('http_errors_total', 1, { method: ctx.method, path: this.normalizePath(ctx.path) });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Start a new trace span
|
|
106
|
-
*/
|
|
107
|
-
startSpan(name: string, ctx?: Context): Span | undefined {
|
|
108
|
-
if (!this.tracing) return undefined;
|
|
109
|
-
|
|
110
|
-
let traceContext: { traceId?: string; parentSpanId?: string; } = {};
|
|
111
|
-
if (ctx?.headers) {
|
|
112
|
-
traceContext = this.tracing.extractContext(ctx.headers as Record<string, string | string[] | undefined>);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const shouldSample = ctx ? this.tracing.shouldSample(ctx.path) : true;
|
|
116
|
-
if (!shouldSample) return undefined;
|
|
117
|
-
|
|
118
|
-
return this.tracing.startSpan(name, traceContext.parentSpanId, traceContext.traceId);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* End a trace span
|
|
123
|
-
*/
|
|
124
|
-
endSpan(spanId: string, status: 'ok' | 'error' = 'ok', error?: Error) {
|
|
125
|
-
this.tracing?.endSpan(spanId, status, error);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Add event to a span
|
|
130
|
-
*/
|
|
131
|
-
addSpanEvent(spanId: string, name: string, attributes?: Record<string, any>) {
|
|
132
|
-
this.tracing?.addEvent(spanId, name, attributes);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Set span attributes
|
|
137
|
-
*/
|
|
138
|
-
setSpanAttributes(spanId: string, attributes: Record<string, any>) {
|
|
139
|
-
this.tracing?.setAttributes(spanId, attributes);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Record a database query for APM
|
|
144
|
-
*/
|
|
145
|
-
recordQuery(query: string, durationMs: number) {
|
|
146
|
-
this.apm?.recordQuery(query, durationMs);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Get the structured logger
|
|
151
|
-
*/
|
|
152
|
-
getLogger(): StructuredLogger {
|
|
153
|
-
return this.logger;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Metrics endpoint handler
|
|
158
|
-
*/
|
|
159
|
-
metricsHandler() {
|
|
160
|
-
return (ctx: Context): Response => {
|
|
161
|
-
const format = this.options.metrics?.format ?? 'prometheus';
|
|
162
|
-
if (format === 'json') {
|
|
163
|
-
return ctx.json(this.metrics.snapshot());
|
|
164
|
-
}
|
|
165
|
-
return {
|
|
166
|
-
statusCode: 200,
|
|
167
|
-
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
168
|
-
body: this.metrics.toPrometheus()
|
|
169
|
-
};
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Health check handler
|
|
175
|
-
*/
|
|
176
|
-
healthHandler() {
|
|
177
|
-
return async (_ctx: Context): Promise<Response> => {
|
|
178
|
-
const checks = this.options.health?.checks ?? [];
|
|
179
|
-
const results: Record<string, { status: 'up' | 'down'; details?: Record<string, any>; duration?: number; }> = {};
|
|
180
|
-
|
|
181
|
-
let overallStatus: 'up' | 'down' = 'up';
|
|
182
|
-
|
|
183
|
-
for (const check of checks) {
|
|
184
|
-
const start = performance.now();
|
|
185
|
-
try {
|
|
186
|
-
const timeout = check.timeout ?? 5000;
|
|
187
|
-
const result = await Promise.race([
|
|
188
|
-
check.check(),
|
|
189
|
-
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), timeout)
|
|
190
|
-
)
|
|
191
|
-
]);
|
|
192
|
-
const duration = performance.now() - start;
|
|
193
|
-
|
|
194
|
-
results[check.name] = { ...result, duration };
|
|
195
|
-
if (result.status === 'down' && check.critical !== false) {
|
|
196
|
-
overallStatus = 'down';
|
|
197
|
-
}
|
|
198
|
-
} catch (error: any) {
|
|
199
|
-
const duration = performance.now() - start;
|
|
200
|
-
if (check.critical !== false) {
|
|
201
|
-
overallStatus = 'down';
|
|
202
|
-
}
|
|
203
|
-
results[check.name] = {
|
|
204
|
-
status: 'down',
|
|
205
|
-
duration,
|
|
206
|
-
details: { message: error?.message ?? 'Health check failed' }
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const statusCode = overallStatus === 'up' ? 200 : 503;
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
statusCode,
|
|
215
|
-
headers: { 'Content-Type': 'application/json' },
|
|
216
|
-
body: JSON.stringify({
|
|
217
|
-
status: overallStatus,
|
|
218
|
-
timestamp: new Date().toISOString(),
|
|
219
|
-
checks: results
|
|
220
|
-
})
|
|
221
|
-
};
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Get all traces
|
|
227
|
-
*/
|
|
228
|
-
getTraces(): Span[] {
|
|
229
|
-
return this.tracing?.getSpans() ?? [];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Get slow queries from APM
|
|
234
|
-
*/
|
|
235
|
-
getSlowQueries() {
|
|
236
|
-
return this.apm?.getSlowQueries() ?? [];
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Get memory statistics
|
|
241
|
-
*/
|
|
242
|
-
getMemoryStats() {
|
|
243
|
-
return this.apm?.getMemoryStats();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Get alert history
|
|
248
|
-
*/
|
|
249
|
-
getAlertHistory() {
|
|
250
|
-
return this.alertManager?.getAlertHistory() ?? [];
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Get the alert manager
|
|
255
|
-
*/
|
|
256
|
-
getAlertManager() {
|
|
257
|
-
return this.alertManager;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Get logs with optional filtering
|
|
262
|
-
*/
|
|
263
|
-
getLogs(filter?: { level?: LogEntry['level']; since?: number; limit?: number; }) {
|
|
264
|
-
return this.logger.getLogs(filter);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Increment a custom counter metric
|
|
269
|
-
*/
|
|
270
|
-
incrementCounter(name: string, value: number = 1, labels: Record<string, string> = {}) {
|
|
271
|
-
this.metrics.increment(name, value, labels);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Set a gauge metric value
|
|
276
|
-
*/
|
|
277
|
-
setGauge(name: string, value: number, labels: Record<string, string> = {}) {
|
|
278
|
-
this.metrics.gauge(name, value, labels);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Observe a histogram metric
|
|
283
|
-
*/
|
|
284
|
-
observeHistogram(name: string, value: number, labels: Record<string, string> = {}) {
|
|
285
|
-
this.metrics.observe(name, value, labels);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Cleanup resources
|
|
290
|
-
*/
|
|
291
|
-
shutdown() {
|
|
292
|
-
this.apm?.stop();
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Normalize path for metrics (replace dynamic segments)
|
|
297
|
-
*/
|
|
298
|
-
private normalizePath(path: string): string {
|
|
299
|
-
return path
|
|
300
|
-
.replace(/\/\d+/g, '/:id')
|
|
301
|
-
.replace(/\/[a-f0-9-]{36}/gi, '/:uuid')
|
|
302
|
-
.replace(/\/[a-f0-9]{24}/gi, '/:objectId');
|
|
303
|
-
}
|
|
304
|
-
}
|