genesis-ai-cli 13.9.0 → 13.10.0
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/dist/src/observability/alerting.d.ts +143 -0
- package/dist/src/observability/alerting.js +424 -0
- package/dist/src/observability/index.d.ts +32 -0
- package/dist/src/observability/index.js +93 -0
- package/dist/src/observability/logger.d.ts +116 -0
- package/dist/src/observability/logger.js +289 -0
- package/dist/src/observability/metrics.d.ts +217 -0
- package/dist/src/observability/metrics.js +511 -0
- package/dist/src/observability/tracer.d.ts +211 -0
- package/dist/src/observability/tracer.js +450 -0
- package/dist/test/observability.test.d.ts +10 -0
- package/dist/test/observability.test.js +588 -0
- package/package.json +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alerting System
|
|
3
|
+
*
|
|
4
|
+
* Send alerts to various channels (Slack, PagerDuty, etc.)
|
|
5
|
+
* Features:
|
|
6
|
+
* - Multiple channel support
|
|
7
|
+
* - Alert severity levels
|
|
8
|
+
* - Rate limiting to prevent alert storms
|
|
9
|
+
* - Alert aggregation
|
|
10
|
+
* - Retry with backoff
|
|
11
|
+
*/
|
|
12
|
+
export type AlertSeverity = 'info' | 'warning' | 'error' | 'critical';
|
|
13
|
+
export type AlertChannel = 'slack' | 'pagerduty' | 'webhook' | 'console';
|
|
14
|
+
export interface Alert {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
message: string;
|
|
18
|
+
severity: AlertSeverity;
|
|
19
|
+
source: string;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
labels?: Record<string, string>;
|
|
22
|
+
annotations?: Record<string, string>;
|
|
23
|
+
error?: {
|
|
24
|
+
name: string;
|
|
25
|
+
message: string;
|
|
26
|
+
stack?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface ChannelConfig {
|
|
30
|
+
type: AlertChannel;
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
minSeverity: AlertSeverity;
|
|
33
|
+
config: SlackConfig | PagerDutyConfig | WebhookConfig | Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
export interface SlackConfig {
|
|
36
|
+
webhookUrl: string;
|
|
37
|
+
channel?: string;
|
|
38
|
+
username?: string;
|
|
39
|
+
iconEmoji?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface PagerDutyConfig {
|
|
42
|
+
routingKey: string;
|
|
43
|
+
apiUrl?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface WebhookConfig {
|
|
46
|
+
url: string;
|
|
47
|
+
method?: 'POST' | 'PUT';
|
|
48
|
+
headers?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
export interface AlerterConfig {
|
|
51
|
+
channels: ChannelConfig[];
|
|
52
|
+
rateLimitPerMinute: number;
|
|
53
|
+
aggregationWindowMs: number;
|
|
54
|
+
defaultSource: string;
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Alerter class for sending alerts
|
|
59
|
+
*/
|
|
60
|
+
export declare class Alerter {
|
|
61
|
+
private config;
|
|
62
|
+
private alertCounts;
|
|
63
|
+
private pendingAlerts;
|
|
64
|
+
private aggregationTimer;
|
|
65
|
+
constructor(config?: Partial<AlerterConfig>);
|
|
66
|
+
/**
|
|
67
|
+
* Send an alert
|
|
68
|
+
*/
|
|
69
|
+
alert(alert: Omit<Alert, 'id' | 'timestamp'>): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Send info alert
|
|
72
|
+
*/
|
|
73
|
+
info(title: string, message: string, options?: Partial<Alert>): Promise<boolean>;
|
|
74
|
+
/**
|
|
75
|
+
* Send warning alert
|
|
76
|
+
*/
|
|
77
|
+
warning(title: string, message: string, options?: Partial<Alert>): Promise<boolean>;
|
|
78
|
+
/**
|
|
79
|
+
* Send error alert
|
|
80
|
+
*/
|
|
81
|
+
error(title: string, error: Error, options?: Partial<Alert>): Promise<boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* Send critical alert
|
|
84
|
+
*/
|
|
85
|
+
critical(title: string, message: string, options?: Partial<Alert>): Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* Flush pending alerts
|
|
88
|
+
*/
|
|
89
|
+
flush(): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Stop alerter
|
|
92
|
+
*/
|
|
93
|
+
stop(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Add a channel
|
|
96
|
+
*/
|
|
97
|
+
addChannel(channel: ChannelConfig): void;
|
|
98
|
+
/**
|
|
99
|
+
* Get stats
|
|
100
|
+
*/
|
|
101
|
+
getStats(): {
|
|
102
|
+
pendingAlerts: number;
|
|
103
|
+
channelCount: number;
|
|
104
|
+
rateLimitInfo: Map<string, {
|
|
105
|
+
count: number;
|
|
106
|
+
windowStart: number;
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
109
|
+
private sendAlert;
|
|
110
|
+
private sendToSlack;
|
|
111
|
+
private sendToPagerDuty;
|
|
112
|
+
private sendToWebhook;
|
|
113
|
+
private sendToConsole;
|
|
114
|
+
private checkRateLimit;
|
|
115
|
+
private getAggregationKey;
|
|
116
|
+
private aggregateAlerts;
|
|
117
|
+
private startAggregationTimer;
|
|
118
|
+
private generateAlertId;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get global alerter
|
|
122
|
+
*/
|
|
123
|
+
export declare function getAlerter(config?: Partial<AlerterConfig>): Alerter;
|
|
124
|
+
/**
|
|
125
|
+
* Reset global alerter
|
|
126
|
+
*/
|
|
127
|
+
export declare function resetAlerter(): void;
|
|
128
|
+
/**
|
|
129
|
+
* Send info alert via global alerter
|
|
130
|
+
*/
|
|
131
|
+
export declare function alertInfo(title: string, message: string, options?: Partial<Alert>): Promise<boolean>;
|
|
132
|
+
/**
|
|
133
|
+
* Send warning alert via global alerter
|
|
134
|
+
*/
|
|
135
|
+
export declare function alertWarning(title: string, message: string, options?: Partial<Alert>): Promise<boolean>;
|
|
136
|
+
/**
|
|
137
|
+
* Send error alert via global alerter
|
|
138
|
+
*/
|
|
139
|
+
export declare function alertError(title: string, error: Error, options?: Partial<Alert>): Promise<boolean>;
|
|
140
|
+
/**
|
|
141
|
+
* Send critical alert via global alerter
|
|
142
|
+
*/
|
|
143
|
+
export declare function alertCritical(title: string, message: string, options?: Partial<Alert>): Promise<boolean>;
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Alerting System
|
|
4
|
+
*
|
|
5
|
+
* Send alerts to various channels (Slack, PagerDuty, etc.)
|
|
6
|
+
* Features:
|
|
7
|
+
* - Multiple channel support
|
|
8
|
+
* - Alert severity levels
|
|
9
|
+
* - Rate limiting to prevent alert storms
|
|
10
|
+
* - Alert aggregation
|
|
11
|
+
* - Retry with backoff
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.Alerter = void 0;
|
|
15
|
+
exports.getAlerter = getAlerter;
|
|
16
|
+
exports.resetAlerter = resetAlerter;
|
|
17
|
+
exports.alertInfo = alertInfo;
|
|
18
|
+
exports.alertWarning = alertWarning;
|
|
19
|
+
exports.alertError = alertError;
|
|
20
|
+
exports.alertCritical = alertCritical;
|
|
21
|
+
const SEVERITY_LEVELS = {
|
|
22
|
+
info: 0,
|
|
23
|
+
warning: 1,
|
|
24
|
+
error: 2,
|
|
25
|
+
critical: 3,
|
|
26
|
+
};
|
|
27
|
+
const SEVERITY_COLORS = {
|
|
28
|
+
info: '#36a64f', // green
|
|
29
|
+
warning: '#ffcc00', // yellow
|
|
30
|
+
error: '#ff6600', // orange
|
|
31
|
+
critical: '#ff0000', // red
|
|
32
|
+
};
|
|
33
|
+
const SEVERITY_EMOJIS = {
|
|
34
|
+
info: 'information_source',
|
|
35
|
+
warning: 'warning',
|
|
36
|
+
error: 'x',
|
|
37
|
+
critical: 'rotating_light',
|
|
38
|
+
};
|
|
39
|
+
const DEFAULT_CONFIG = {
|
|
40
|
+
channels: [],
|
|
41
|
+
rateLimitPerMinute: 60,
|
|
42
|
+
aggregationWindowMs: 60000,
|
|
43
|
+
defaultSource: 'genesis',
|
|
44
|
+
enabled: true,
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Alerter class for sending alerts
|
|
48
|
+
*/
|
|
49
|
+
class Alerter {
|
|
50
|
+
config;
|
|
51
|
+
alertCounts = new Map();
|
|
52
|
+
pendingAlerts = new Map();
|
|
53
|
+
aggregationTimer = null;
|
|
54
|
+
constructor(config = {}) {
|
|
55
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
56
|
+
// Start aggregation timer if needed
|
|
57
|
+
if (this.config.aggregationWindowMs > 0) {
|
|
58
|
+
this.startAggregationTimer();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Send an alert
|
|
63
|
+
*/
|
|
64
|
+
async alert(alert) {
|
|
65
|
+
if (!this.config.enabled) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const fullAlert = {
|
|
69
|
+
...alert,
|
|
70
|
+
id: this.generateAlertId(),
|
|
71
|
+
timestamp: Date.now(),
|
|
72
|
+
source: alert.source || this.config.defaultSource,
|
|
73
|
+
};
|
|
74
|
+
// Check rate limit
|
|
75
|
+
if (!this.checkRateLimit(fullAlert)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
// Aggregation
|
|
79
|
+
if (this.config.aggregationWindowMs > 0) {
|
|
80
|
+
const key = this.getAggregationKey(fullAlert);
|
|
81
|
+
let pending = this.pendingAlerts.get(key);
|
|
82
|
+
if (!pending) {
|
|
83
|
+
pending = [];
|
|
84
|
+
this.pendingAlerts.set(key, pending);
|
|
85
|
+
}
|
|
86
|
+
pending.push(fullAlert);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// Send immediately
|
|
90
|
+
return this.sendAlert(fullAlert);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Send info alert
|
|
94
|
+
*/
|
|
95
|
+
async info(title, message, options) {
|
|
96
|
+
return this.alert({ title, message, severity: 'info', source: this.config.defaultSource, ...options });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Send warning alert
|
|
100
|
+
*/
|
|
101
|
+
async warning(title, message, options) {
|
|
102
|
+
return this.alert({ title, message, severity: 'warning', source: this.config.defaultSource, ...options });
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Send error alert
|
|
106
|
+
*/
|
|
107
|
+
async error(title, error, options) {
|
|
108
|
+
return this.alert({
|
|
109
|
+
title,
|
|
110
|
+
message: error.message,
|
|
111
|
+
severity: 'error',
|
|
112
|
+
source: this.config.defaultSource,
|
|
113
|
+
error: {
|
|
114
|
+
name: error.name,
|
|
115
|
+
message: error.message,
|
|
116
|
+
stack: error.stack,
|
|
117
|
+
},
|
|
118
|
+
...options,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Send critical alert
|
|
123
|
+
*/
|
|
124
|
+
async critical(title, message, options) {
|
|
125
|
+
return this.alert({ title, message, severity: 'critical', source: this.config.defaultSource, ...options });
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Flush pending alerts
|
|
129
|
+
*/
|
|
130
|
+
async flush() {
|
|
131
|
+
for (const [key, alerts] of this.pendingAlerts) {
|
|
132
|
+
if (alerts.length === 0)
|
|
133
|
+
continue;
|
|
134
|
+
if (alerts.length === 1) {
|
|
135
|
+
await this.sendAlert(alerts[0]);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Aggregate
|
|
139
|
+
const aggregated = this.aggregateAlerts(alerts);
|
|
140
|
+
await this.sendAlert(aggregated);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
this.pendingAlerts.clear();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Stop alerter
|
|
147
|
+
*/
|
|
148
|
+
stop() {
|
|
149
|
+
if (this.aggregationTimer) {
|
|
150
|
+
clearInterval(this.aggregationTimer);
|
|
151
|
+
this.aggregationTimer = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Add a channel
|
|
156
|
+
*/
|
|
157
|
+
addChannel(channel) {
|
|
158
|
+
this.config.channels.push(channel);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get stats
|
|
162
|
+
*/
|
|
163
|
+
getStats() {
|
|
164
|
+
let pendingCount = 0;
|
|
165
|
+
for (const alerts of this.pendingAlerts.values()) {
|
|
166
|
+
pendingCount += alerts.length;
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
pendingAlerts: pendingCount,
|
|
170
|
+
channelCount: this.config.channels.length,
|
|
171
|
+
rateLimitInfo: new Map(this.alertCounts),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
async sendAlert(alert) {
|
|
175
|
+
const promises = [];
|
|
176
|
+
for (const channel of this.config.channels) {
|
|
177
|
+
if (!channel.enabled)
|
|
178
|
+
continue;
|
|
179
|
+
if (SEVERITY_LEVELS[alert.severity] < SEVERITY_LEVELS[channel.minSeverity])
|
|
180
|
+
continue;
|
|
181
|
+
switch (channel.type) {
|
|
182
|
+
case 'slack':
|
|
183
|
+
promises.push(this.sendToSlack(alert, channel.config));
|
|
184
|
+
break;
|
|
185
|
+
case 'pagerduty':
|
|
186
|
+
promises.push(this.sendToPagerDuty(alert, channel.config));
|
|
187
|
+
break;
|
|
188
|
+
case 'webhook':
|
|
189
|
+
promises.push(this.sendToWebhook(alert, channel.config));
|
|
190
|
+
break;
|
|
191
|
+
case 'console':
|
|
192
|
+
promises.push(this.sendToConsole(alert));
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const results = await Promise.allSettled(promises);
|
|
197
|
+
return results.some(r => r.status === 'fulfilled' && r.value);
|
|
198
|
+
}
|
|
199
|
+
async sendToSlack(alert, config) {
|
|
200
|
+
const payload = {
|
|
201
|
+
channel: config.channel,
|
|
202
|
+
username: config.username || 'Genesis Alerts',
|
|
203
|
+
icon_emoji: config.iconEmoji || `:${SEVERITY_EMOJIS[alert.severity]}:`,
|
|
204
|
+
attachments: [{
|
|
205
|
+
color: SEVERITY_COLORS[alert.severity],
|
|
206
|
+
title: alert.title,
|
|
207
|
+
text: alert.message,
|
|
208
|
+
fields: [
|
|
209
|
+
{ title: 'Severity', value: alert.severity.toUpperCase(), short: true },
|
|
210
|
+
{ title: 'Source', value: alert.source, short: true },
|
|
211
|
+
...(alert.labels ? Object.entries(alert.labels).map(([k, v]) => ({
|
|
212
|
+
title: k,
|
|
213
|
+
value: v,
|
|
214
|
+
short: true,
|
|
215
|
+
})) : []),
|
|
216
|
+
],
|
|
217
|
+
footer: 'Genesis Alerting',
|
|
218
|
+
ts: Math.floor(alert.timestamp / 1000),
|
|
219
|
+
}],
|
|
220
|
+
};
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch(config.webhookUrl, {
|
|
223
|
+
method: 'POST',
|
|
224
|
+
headers: { 'Content-Type': 'application/json' },
|
|
225
|
+
body: JSON.stringify(payload),
|
|
226
|
+
});
|
|
227
|
+
return response.ok;
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error('Slack alert failed:', error);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async sendToPagerDuty(alert, config) {
|
|
235
|
+
const severityMap = {
|
|
236
|
+
info: 'info',
|
|
237
|
+
warning: 'warning',
|
|
238
|
+
error: 'error',
|
|
239
|
+
critical: 'critical',
|
|
240
|
+
};
|
|
241
|
+
const payload = {
|
|
242
|
+
routing_key: config.routingKey,
|
|
243
|
+
event_action: 'trigger',
|
|
244
|
+
dedup_key: alert.id,
|
|
245
|
+
payload: {
|
|
246
|
+
summary: `${alert.title}: ${alert.message}`,
|
|
247
|
+
source: alert.source,
|
|
248
|
+
severity: severityMap[alert.severity],
|
|
249
|
+
timestamp: new Date(alert.timestamp).toISOString(),
|
|
250
|
+
custom_details: {
|
|
251
|
+
...alert.labels,
|
|
252
|
+
...alert.annotations,
|
|
253
|
+
...(alert.error ? { error: alert.error } : {}),
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
try {
|
|
258
|
+
const response = await fetch(config.apiUrl || 'https://events.pagerduty.com/v2/enqueue', {
|
|
259
|
+
method: 'POST',
|
|
260
|
+
headers: { 'Content-Type': 'application/json' },
|
|
261
|
+
body: JSON.stringify(payload),
|
|
262
|
+
});
|
|
263
|
+
return response.ok;
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
console.error('PagerDuty alert failed:', error);
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async sendToWebhook(alert, config) {
|
|
271
|
+
try {
|
|
272
|
+
const response = await fetch(config.url, {
|
|
273
|
+
method: config.method || 'POST',
|
|
274
|
+
headers: {
|
|
275
|
+
'Content-Type': 'application/json',
|
|
276
|
+
...config.headers,
|
|
277
|
+
},
|
|
278
|
+
body: JSON.stringify(alert),
|
|
279
|
+
});
|
|
280
|
+
return response.ok;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
console.error('Webhook alert failed:', error);
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async sendToConsole(alert) {
|
|
288
|
+
const prefix = `[ALERT:${alert.severity.toUpperCase()}]`;
|
|
289
|
+
const message = `${prefix} ${alert.title}: ${alert.message}`;
|
|
290
|
+
switch (alert.severity) {
|
|
291
|
+
case 'critical':
|
|
292
|
+
case 'error':
|
|
293
|
+
console.error(message, alert.error || '');
|
|
294
|
+
break;
|
|
295
|
+
case 'warning':
|
|
296
|
+
console.warn(message);
|
|
297
|
+
break;
|
|
298
|
+
default:
|
|
299
|
+
console.log(message);
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
checkRateLimit(alert) {
|
|
304
|
+
const key = `${alert.source}:${alert.severity}`;
|
|
305
|
+
const now = Date.now();
|
|
306
|
+
const windowMs = 60000; // 1 minute
|
|
307
|
+
let entry = this.alertCounts.get(key);
|
|
308
|
+
if (!entry || now - entry.windowStart > windowMs) {
|
|
309
|
+
entry = { count: 0, windowStart: now };
|
|
310
|
+
this.alertCounts.set(key, entry);
|
|
311
|
+
}
|
|
312
|
+
if (entry.count >= this.config.rateLimitPerMinute) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
entry.count++;
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
getAggregationKey(alert) {
|
|
319
|
+
return `${alert.source}:${alert.severity}:${alert.title}`;
|
|
320
|
+
}
|
|
321
|
+
aggregateAlerts(alerts) {
|
|
322
|
+
const first = alerts[0];
|
|
323
|
+
return {
|
|
324
|
+
...first,
|
|
325
|
+
message: `${alerts.length} occurrences:\n${alerts.slice(0, 5).map(a => `- ${a.message}`).join('\n')}${alerts.length > 5 ? `\n... and ${alerts.length - 5} more` : ''}`,
|
|
326
|
+
annotations: {
|
|
327
|
+
...first.annotations,
|
|
328
|
+
aggregated_count: String(alerts.length),
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
startAggregationTimer() {
|
|
333
|
+
this.aggregationTimer = setInterval(() => {
|
|
334
|
+
this.flush().catch(console.error);
|
|
335
|
+
}, this.config.aggregationWindowMs);
|
|
336
|
+
if (this.aggregationTimer.unref) {
|
|
337
|
+
this.aggregationTimer.unref();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
generateAlertId() {
|
|
341
|
+
return `alert-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
exports.Alerter = Alerter;
|
|
345
|
+
// Global alerter
|
|
346
|
+
let globalAlerter = null;
|
|
347
|
+
/**
|
|
348
|
+
* Get global alerter
|
|
349
|
+
*/
|
|
350
|
+
function getAlerter(config) {
|
|
351
|
+
if (!globalAlerter) {
|
|
352
|
+
const channels = [];
|
|
353
|
+
// Add Slack if configured
|
|
354
|
+
if (process.env.SLACK_WEBHOOK_URL) {
|
|
355
|
+
channels.push({
|
|
356
|
+
type: 'slack',
|
|
357
|
+
enabled: true,
|
|
358
|
+
minSeverity: process.env.SLACK_MIN_SEVERITY || 'warning',
|
|
359
|
+
config: {
|
|
360
|
+
webhookUrl: process.env.SLACK_WEBHOOK_URL,
|
|
361
|
+
channel: process.env.SLACK_CHANNEL,
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
// Add PagerDuty if configured
|
|
366
|
+
if (process.env.PAGERDUTY_ROUTING_KEY) {
|
|
367
|
+
channels.push({
|
|
368
|
+
type: 'pagerduty',
|
|
369
|
+
enabled: true,
|
|
370
|
+
minSeverity: 'error',
|
|
371
|
+
config: {
|
|
372
|
+
routingKey: process.env.PAGERDUTY_ROUTING_KEY,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
// Add console in development
|
|
377
|
+
if (process.env.NODE_ENV === 'development' || process.env.ALERT_CONSOLE === 'true') {
|
|
378
|
+
channels.push({
|
|
379
|
+
type: 'console',
|
|
380
|
+
enabled: true,
|
|
381
|
+
minSeverity: 'info',
|
|
382
|
+
config: {},
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
globalAlerter = new Alerter({
|
|
386
|
+
...config,
|
|
387
|
+
channels: [...channels, ...(config?.channels || [])],
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
return globalAlerter;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Reset global alerter
|
|
394
|
+
*/
|
|
395
|
+
function resetAlerter() {
|
|
396
|
+
if (globalAlerter) {
|
|
397
|
+
globalAlerter.stop();
|
|
398
|
+
}
|
|
399
|
+
globalAlerter = null;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Send info alert via global alerter
|
|
403
|
+
*/
|
|
404
|
+
async function alertInfo(title, message, options) {
|
|
405
|
+
return getAlerter().info(title, message, options);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Send warning alert via global alerter
|
|
409
|
+
*/
|
|
410
|
+
async function alertWarning(title, message, options) {
|
|
411
|
+
return getAlerter().warning(title, message, options);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Send error alert via global alerter
|
|
415
|
+
*/
|
|
416
|
+
async function alertError(title, error, options) {
|
|
417
|
+
return getAlerter().error(title, error, options);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Send critical alert via global alerter
|
|
421
|
+
*/
|
|
422
|
+
async function alertCritical(title, message, options) {
|
|
423
|
+
return getAlerter().critical(title, message, options);
|
|
424
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability Module
|
|
3
|
+
*
|
|
4
|
+
* Production-grade observability stack:
|
|
5
|
+
* - Structured logging with pino-compatible API
|
|
6
|
+
* - Prometheus metrics collection
|
|
7
|
+
* - OpenTelemetry-compatible distributed tracing
|
|
8
|
+
* - Alerting via Slack, PagerDuty, webhooks
|
|
9
|
+
*/
|
|
10
|
+
export { Logger, Timer, getLogger, resetLogger, createLogger, startTimer, type LogLevel, type LogEntry, type LoggerConfig, } from './logger.js';
|
|
11
|
+
export { Counter, Gauge, Histogram, Summary, MetricsRegistry, getMetricsRegistry, resetMetricsRegistry, createGenesisMetrics, type MetricType, type MetricConfig, type GenesisMetrics, } from './metrics.js';
|
|
12
|
+
export { Span, Tracer, ConsoleExporter, InMemoryExporter, OTLPHttpExporter, getTracer, resetTracer, startSpan, withTrace, type SpanContext, type SpanData, type SpanKind, type SpanStatus, type SpanExporter, type TracerConfig, } from './tracer.js';
|
|
13
|
+
export { Alerter, getAlerter, resetAlerter, alertInfo, alertWarning, alertError, alertCritical, type Alert, type AlertSeverity, type AlertChannel, type AlerterConfig, type SlackConfig, type PagerDutyConfig, type WebhookConfig, } from './alerting.js';
|
|
14
|
+
/**
|
|
15
|
+
* Initialize full observability stack
|
|
16
|
+
*/
|
|
17
|
+
export interface ObservabilityConfig {
|
|
18
|
+
serviceName: string;
|
|
19
|
+
serviceVersion?: string;
|
|
20
|
+
environment?: string;
|
|
21
|
+
logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
22
|
+
logFormat?: 'json' | 'pretty';
|
|
23
|
+
metricsPrefix?: string;
|
|
24
|
+
traceSampleRate?: number;
|
|
25
|
+
slackWebhookUrl?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function initObservability(config: ObservabilityConfig): {
|
|
28
|
+
logger: import('./logger.js').Logger;
|
|
29
|
+
metrics: import('./metrics.js').GenesisMetrics;
|
|
30
|
+
tracer: import('./tracer.js').Tracer;
|
|
31
|
+
alerter: import('./alerting.js').Alerter;
|
|
32
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Observability Module
|
|
4
|
+
*
|
|
5
|
+
* Production-grade observability stack:
|
|
6
|
+
* - Structured logging with pino-compatible API
|
|
7
|
+
* - Prometheus metrics collection
|
|
8
|
+
* - OpenTelemetry-compatible distributed tracing
|
|
9
|
+
* - Alerting via Slack, PagerDuty, webhooks
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.alertCritical = exports.alertError = exports.alertWarning = exports.alertInfo = exports.resetAlerter = exports.getAlerter = exports.Alerter = exports.withTrace = exports.startSpan = exports.resetTracer = exports.getTracer = exports.OTLPHttpExporter = exports.InMemoryExporter = exports.ConsoleExporter = exports.Tracer = exports.Span = exports.createGenesisMetrics = exports.resetMetricsRegistry = exports.getMetricsRegistry = exports.MetricsRegistry = exports.Summary = exports.Histogram = exports.Gauge = exports.Counter = exports.startTimer = exports.createLogger = exports.resetLogger = exports.getLogger = exports.Timer = exports.Logger = void 0;
|
|
13
|
+
exports.initObservability = initObservability;
|
|
14
|
+
// Logger
|
|
15
|
+
var logger_js_1 = require("./logger.js");
|
|
16
|
+
Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return logger_js_1.Logger; } });
|
|
17
|
+
Object.defineProperty(exports, "Timer", { enumerable: true, get: function () { return logger_js_1.Timer; } });
|
|
18
|
+
Object.defineProperty(exports, "getLogger", { enumerable: true, get: function () { return logger_js_1.getLogger; } });
|
|
19
|
+
Object.defineProperty(exports, "resetLogger", { enumerable: true, get: function () { return logger_js_1.resetLogger; } });
|
|
20
|
+
Object.defineProperty(exports, "createLogger", { enumerable: true, get: function () { return logger_js_1.createLogger; } });
|
|
21
|
+
Object.defineProperty(exports, "startTimer", { enumerable: true, get: function () { return logger_js_1.startTimer; } });
|
|
22
|
+
// Metrics
|
|
23
|
+
var metrics_js_1 = require("./metrics.js");
|
|
24
|
+
Object.defineProperty(exports, "Counter", { enumerable: true, get: function () { return metrics_js_1.Counter; } });
|
|
25
|
+
Object.defineProperty(exports, "Gauge", { enumerable: true, get: function () { return metrics_js_1.Gauge; } });
|
|
26
|
+
Object.defineProperty(exports, "Histogram", { enumerable: true, get: function () { return metrics_js_1.Histogram; } });
|
|
27
|
+
Object.defineProperty(exports, "Summary", { enumerable: true, get: function () { return metrics_js_1.Summary; } });
|
|
28
|
+
Object.defineProperty(exports, "MetricsRegistry", { enumerable: true, get: function () { return metrics_js_1.MetricsRegistry; } });
|
|
29
|
+
Object.defineProperty(exports, "getMetricsRegistry", { enumerable: true, get: function () { return metrics_js_1.getMetricsRegistry; } });
|
|
30
|
+
Object.defineProperty(exports, "resetMetricsRegistry", { enumerable: true, get: function () { return metrics_js_1.resetMetricsRegistry; } });
|
|
31
|
+
Object.defineProperty(exports, "createGenesisMetrics", { enumerable: true, get: function () { return metrics_js_1.createGenesisMetrics; } });
|
|
32
|
+
// Tracing
|
|
33
|
+
var tracer_js_1 = require("./tracer.js");
|
|
34
|
+
Object.defineProperty(exports, "Span", { enumerable: true, get: function () { return tracer_js_1.Span; } });
|
|
35
|
+
Object.defineProperty(exports, "Tracer", { enumerable: true, get: function () { return tracer_js_1.Tracer; } });
|
|
36
|
+
Object.defineProperty(exports, "ConsoleExporter", { enumerable: true, get: function () { return tracer_js_1.ConsoleExporter; } });
|
|
37
|
+
Object.defineProperty(exports, "InMemoryExporter", { enumerable: true, get: function () { return tracer_js_1.InMemoryExporter; } });
|
|
38
|
+
Object.defineProperty(exports, "OTLPHttpExporter", { enumerable: true, get: function () { return tracer_js_1.OTLPHttpExporter; } });
|
|
39
|
+
Object.defineProperty(exports, "getTracer", { enumerable: true, get: function () { return tracer_js_1.getTracer; } });
|
|
40
|
+
Object.defineProperty(exports, "resetTracer", { enumerable: true, get: function () { return tracer_js_1.resetTracer; } });
|
|
41
|
+
Object.defineProperty(exports, "startSpan", { enumerable: true, get: function () { return tracer_js_1.startSpan; } });
|
|
42
|
+
Object.defineProperty(exports, "withTrace", { enumerable: true, get: function () { return tracer_js_1.withTrace; } });
|
|
43
|
+
// Alerting
|
|
44
|
+
var alerting_js_1 = require("./alerting.js");
|
|
45
|
+
Object.defineProperty(exports, "Alerter", { enumerable: true, get: function () { return alerting_js_1.Alerter; } });
|
|
46
|
+
Object.defineProperty(exports, "getAlerter", { enumerable: true, get: function () { return alerting_js_1.getAlerter; } });
|
|
47
|
+
Object.defineProperty(exports, "resetAlerter", { enumerable: true, get: function () { return alerting_js_1.resetAlerter; } });
|
|
48
|
+
Object.defineProperty(exports, "alertInfo", { enumerable: true, get: function () { return alerting_js_1.alertInfo; } });
|
|
49
|
+
Object.defineProperty(exports, "alertWarning", { enumerable: true, get: function () { return alerting_js_1.alertWarning; } });
|
|
50
|
+
Object.defineProperty(exports, "alertError", { enumerable: true, get: function () { return alerting_js_1.alertError; } });
|
|
51
|
+
Object.defineProperty(exports, "alertCritical", { enumerable: true, get: function () { return alerting_js_1.alertCritical; } });
|
|
52
|
+
function initObservability(config) {
|
|
53
|
+
const { Logger } = require('./logger.js');
|
|
54
|
+
const { getMetricsRegistry, createGenesisMetrics } = require('./metrics.js');
|
|
55
|
+
const { Tracer } = require('./tracer.js');
|
|
56
|
+
const { Alerter } = require('./alerting.js');
|
|
57
|
+
// Logger
|
|
58
|
+
const logger = new Logger({
|
|
59
|
+
name: config.serviceName,
|
|
60
|
+
level: config.logLevel || 'info',
|
|
61
|
+
format: config.logFormat || 'json',
|
|
62
|
+
});
|
|
63
|
+
// Metrics
|
|
64
|
+
const registry = getMetricsRegistry(config.metricsPrefix || config.serviceName);
|
|
65
|
+
const metrics = createGenesisMetrics(registry);
|
|
66
|
+
// Set build info
|
|
67
|
+
metrics.buildInfo.set(1, {
|
|
68
|
+
version: config.serviceVersion || 'unknown',
|
|
69
|
+
node_version: process.version,
|
|
70
|
+
});
|
|
71
|
+
// Tracer
|
|
72
|
+
const tracer = new Tracer({
|
|
73
|
+
serviceName: config.serviceName,
|
|
74
|
+
serviceVersion: config.serviceVersion,
|
|
75
|
+
environment: config.environment,
|
|
76
|
+
sampleRate: config.traceSampleRate ?? 1.0,
|
|
77
|
+
});
|
|
78
|
+
// Alerter
|
|
79
|
+
const channels = [];
|
|
80
|
+
if (config.slackWebhookUrl) {
|
|
81
|
+
channels.push({
|
|
82
|
+
type: 'slack',
|
|
83
|
+
enabled: true,
|
|
84
|
+
minSeverity: 'warning',
|
|
85
|
+
config: { webhookUrl: config.slackWebhookUrl },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const alerter = new Alerter({
|
|
89
|
+
defaultSource: config.serviceName,
|
|
90
|
+
channels,
|
|
91
|
+
});
|
|
92
|
+
return { logger, metrics, tracer, alerter };
|
|
93
|
+
}
|