@poolzin/pool-bot 2026.3.9 → 2026.3.10
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/CHANGELOG.md +24 -0
- package/README.md +147 -69
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +26 -77
- package/dist/agents/skills/security.js +1 -7
- package/dist/build-info.json +3 -3
- package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
- package/dist/cli/cron-cli/register.js +2 -0
- package/dist/cli/errors.js +187 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.maintenance.js +21 -0
- package/dist/cli/program/register.subclis.js +9 -0
- package/dist/cli/swarm-cli/register.js +8 -0
- package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
- package/dist/cli/telemetry-cli/register.js +10 -0
- package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
- package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
- package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
- package/dist/commands/doctor-checks.js +498 -0
- package/dist/context-engine/index.js +1 -1
- package/dist/context-engine/legacy.js +1 -3
- package/dist/context-engine/summarizing.js +5 -8
- package/dist/cron/service/timer.js +18 -0
- package/dist/gateway/protocol/index.js +5 -2
- package/dist/gateway/protocol/schema/error-codes.js +1 -0
- package/dist/gateway/protocol/schema/swarm.js +80 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-close.js +4 -0
- package/dist/gateway/server-constants.js +1 -0
- package/dist/gateway/server-cron.js +29 -0
- package/dist/gateway/server-maintenance.js +35 -2
- package/dist/gateway/server-methods/swarm.js +58 -0
- package/dist/gateway/server-methods/telemetry.js +71 -0
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +9 -2
- package/dist/gateway/server.impl.js +33 -16
- package/dist/infra/abort-pattern.js +4 -4
- package/dist/infra/retry.js +3 -1
- package/dist/skills/commands.js +7 -25
- package/dist/skills/index.js +14 -17
- package/dist/skills/parser.js +12 -27
- package/dist/skills/registry.js +3 -6
- package/dist/skills/security.js +2 -8
- package/dist/swarm/service.js +247 -0
- package/dist/telemetry/alert-engine.js +258 -0
- package/dist/telemetry/cron-instrumentation.js +49 -0
- package/dist/telemetry/gateway-instrumentation.js +80 -0
- package/dist/telemetry/instrumentation.js +66 -0
- package/dist/telemetry/service.js +345 -0
- package/dist/tui/components/assistant-message.js +6 -2
- package/dist/tui/components/hyperlink-markdown.js +32 -0
- package/dist/tui/components/searchable-select-list.js +12 -1
- package/dist/tui/components/user-message.js +6 -2
- package/dist/tui/index.js +22 -6
- package/dist/tui/theme/theme-detection.js +226 -0
- package/dist/tui/tui-command-handlers.js +20 -0
- package/dist/tui/tui-formatters.js +4 -3
- package/dist/tui/utils/ctrl-c-handler.js +67 -0
- package/dist/tui/utils/osc8-hyperlinks.js +208 -0
- package/dist/tui/utils/safe-stop.js +180 -0
- package/dist/tui/utils/session-key-utils.js +81 -0
- package/dist/tui/utils/text-sanitization.js +284 -0
- package/dist/utils/lru-cache.js +116 -0
- package/dist/utils/performance.js +199 -0
- package/dist/utils/retry.js +240 -0
- package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
- package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
- package/docs/PLANO_ACAO_TUI.md +357 -0
- package/docs/PROGRESSO_TUI.md +66 -0
- package/docs/RELATORIO_FINAL.md +217 -0
- package/docs/diagnostico-shell-completion.md +265 -0
- package/docs/features/advanced-memory.md +585 -0
- package/docs/features/discord-components-v2.md +277 -0
- package/docs/features/swarm.md +100 -0
- package/docs/features/telemetry.md +284 -0
- package/docs/integrations/INTEGRATION_PLAN.md +665 -345
- package/docs/models/provider-infrastructure.md +400 -0
- package/docs/security/exec-approvals.md +294 -0
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/hexstrike-bridge/README.md +119 -0
- package/extensions/hexstrike-bridge/index.test.ts +247 -0
- package/extensions/hexstrike-bridge/index.ts +487 -0
- package/extensions/hexstrike-bridge/package.json +17 -0
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mcp-server/index.ts +14 -0
- package/extensions/mcp-server/package.json +11 -0
- package/extensions/mcp-server/src/service.ts +540 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +8 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
2
|
+
const log = createSubsystemLogger("telemetry:alerts");
|
|
3
|
+
export class AlertEngine {
|
|
4
|
+
rules = new Map();
|
|
5
|
+
states = new Map();
|
|
6
|
+
onAlertCallback;
|
|
7
|
+
constructor(rules = []) {
|
|
8
|
+
for (const rule of rules) {
|
|
9
|
+
this.addRule(rule);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
addRule(rule) {
|
|
13
|
+
this.rules.set(rule.id, rule);
|
|
14
|
+
this.states.set(rule.id, { consecutiveCount: 0 });
|
|
15
|
+
log.debug(`Added alert rule: ${rule.id} (${rule.name})`);
|
|
16
|
+
}
|
|
17
|
+
removeRule(ruleId) {
|
|
18
|
+
this.states.delete(ruleId);
|
|
19
|
+
return this.rules.delete(ruleId);
|
|
20
|
+
}
|
|
21
|
+
getRules() {
|
|
22
|
+
return Array.from(this.rules.values());
|
|
23
|
+
}
|
|
24
|
+
onAlert(callback) {
|
|
25
|
+
this.onAlertCallback = callback;
|
|
26
|
+
}
|
|
27
|
+
evaluate(snapshot) {
|
|
28
|
+
const alerts = [];
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
for (const rule of this.rules.values()) {
|
|
31
|
+
if (!rule.enabled)
|
|
32
|
+
continue;
|
|
33
|
+
const alert = this.evaluateRule(rule, snapshot, now);
|
|
34
|
+
if (alert) {
|
|
35
|
+
alerts.push(alert);
|
|
36
|
+
this.onAlertCallback?.(alert);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return alerts;
|
|
40
|
+
}
|
|
41
|
+
evaluateRule(rule, snapshot, now) {
|
|
42
|
+
const state = this.states.get(rule.id);
|
|
43
|
+
if (!state)
|
|
44
|
+
return null;
|
|
45
|
+
// Find matching metrics
|
|
46
|
+
const matchingMetrics = snapshot.metrics.filter((m) => {
|
|
47
|
+
if (m.name !== rule.threshold.metric)
|
|
48
|
+
return false;
|
|
49
|
+
if (rule.attributes) {
|
|
50
|
+
for (const [key, value] of Object.entries(rule.attributes)) {
|
|
51
|
+
if (m.attributes?.[key] !== value)
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
if (matchingMetrics.length === 0) {
|
|
58
|
+
// Reset state if no matching metrics
|
|
59
|
+
state.consecutiveCount = 0;
|
|
60
|
+
state.triggeredAt = undefined;
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// Check if any metric violates threshold
|
|
64
|
+
let violated = false;
|
|
65
|
+
let violatingMetric = null;
|
|
66
|
+
for (const metric of matchingMetrics) {
|
|
67
|
+
if (this.checkThreshold(metric.value, rule.threshold)) {
|
|
68
|
+
violated = true;
|
|
69
|
+
violatingMetric = metric;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!violated) {
|
|
74
|
+
// Reset state if threshold no longer violated
|
|
75
|
+
state.consecutiveCount = 0;
|
|
76
|
+
state.triggeredAt = undefined;
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
// Track consecutive violations
|
|
80
|
+
state.consecutiveCount++;
|
|
81
|
+
if (!state.triggeredAt) {
|
|
82
|
+
state.triggeredAt = now;
|
|
83
|
+
}
|
|
84
|
+
// Check duration requirement
|
|
85
|
+
const durationMs = rule.threshold.durationMs ?? 0;
|
|
86
|
+
if (now - state.triggeredAt < durationMs) {
|
|
87
|
+
return null; // Haven't met duration requirement yet
|
|
88
|
+
}
|
|
89
|
+
// Check cooldown
|
|
90
|
+
const cooldownMs = rule.threshold.cooldownMs ?? 60000;
|
|
91
|
+
if (state.lastAlertAt && now - state.lastAlertAt < cooldownMs) {
|
|
92
|
+
return null; // Still in cooldown
|
|
93
|
+
}
|
|
94
|
+
// Trigger alert
|
|
95
|
+
state.lastAlertAt = now;
|
|
96
|
+
return {
|
|
97
|
+
id: `${rule.id}-${now}`,
|
|
98
|
+
ruleId: rule.id,
|
|
99
|
+
name: rule.name,
|
|
100
|
+
severity: rule.severity,
|
|
101
|
+
metric: rule.threshold.metric,
|
|
102
|
+
value: violatingMetric.value,
|
|
103
|
+
threshold: rule.threshold.value,
|
|
104
|
+
operator: rule.threshold.operator,
|
|
105
|
+
timestamp: now,
|
|
106
|
+
message: this.buildAlertMessage(rule, violatingMetric.value),
|
|
107
|
+
attributes: violatingMetric.attributes,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
checkThreshold(value, threshold) {
|
|
111
|
+
switch (threshold.operator) {
|
|
112
|
+
case "gt":
|
|
113
|
+
return value > threshold.value;
|
|
114
|
+
case "gte":
|
|
115
|
+
return value >= threshold.value;
|
|
116
|
+
case "lt":
|
|
117
|
+
return value < threshold.value;
|
|
118
|
+
case "lte":
|
|
119
|
+
return value <= threshold.value;
|
|
120
|
+
case "eq":
|
|
121
|
+
return value === threshold.value;
|
|
122
|
+
default:
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
buildAlertMessage(rule, value) {
|
|
127
|
+
const opMap = {
|
|
128
|
+
gt: ">",
|
|
129
|
+
gte: ">=",
|
|
130
|
+
lt: "<",
|
|
131
|
+
lte: "<=",
|
|
132
|
+
eq: "=",
|
|
133
|
+
};
|
|
134
|
+
return `${rule.name}: ${rule.threshold.metric} = ${value} ${opMap[rule.threshold.operator]} ${rule.threshold.value}`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Predefined alert rules for common Pool Bot scenarios
|
|
138
|
+
export const defaultAlertRules = [
|
|
139
|
+
// Cron Job Alerts
|
|
140
|
+
{
|
|
141
|
+
id: "cron-high-failure-rate",
|
|
142
|
+
name: "High Cron Job Failure Rate",
|
|
143
|
+
description: "Triggers when cron job failures exceed 3 in 5 minutes",
|
|
144
|
+
enabled: true,
|
|
145
|
+
severity: "warning",
|
|
146
|
+
threshold: {
|
|
147
|
+
metric: "poolbot.cron.job_failures",
|
|
148
|
+
operator: "gt",
|
|
149
|
+
value: 3,
|
|
150
|
+
durationMs: 300000, // 5 minutes
|
|
151
|
+
cooldownMs: 600000, // 10 minutes
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "cron-slow-jobs",
|
|
156
|
+
name: "Slow Cron Job Execution",
|
|
157
|
+
description: "Triggers when cron job duration exceeds 60 seconds",
|
|
158
|
+
enabled: true,
|
|
159
|
+
severity: "info",
|
|
160
|
+
threshold: {
|
|
161
|
+
metric: "poolbot.cron.job_duration_ms",
|
|
162
|
+
operator: "gt",
|
|
163
|
+
value: 60000,
|
|
164
|
+
durationMs: 60000, // 1 minute
|
|
165
|
+
cooldownMs: 300000, // 5 minutes
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "cron-job-stuck",
|
|
170
|
+
name: "Cron Job Possibly Stuck",
|
|
171
|
+
description: "Triggers when cron job duration exceeds 5 minutes",
|
|
172
|
+
enabled: true,
|
|
173
|
+
severity: "critical",
|
|
174
|
+
threshold: {
|
|
175
|
+
metric: "poolbot.cron.job_duration_ms",
|
|
176
|
+
operator: "gt",
|
|
177
|
+
value: 300000,
|
|
178
|
+
durationMs: 60000,
|
|
179
|
+
cooldownMs: 300000,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
// Security Scan Alerts (HexStrike)
|
|
183
|
+
{
|
|
184
|
+
id: "security-scan-failures",
|
|
185
|
+
name: "Security Scan Failures",
|
|
186
|
+
description: "Triggers when security scans fail",
|
|
187
|
+
enabled: true,
|
|
188
|
+
severity: "warning",
|
|
189
|
+
threshold: {
|
|
190
|
+
metric: "poolbot.security.scan_failures",
|
|
191
|
+
operator: "gt",
|
|
192
|
+
value: 2,
|
|
193
|
+
durationMs: 300000,
|
|
194
|
+
cooldownMs: 600000,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "security-high-latency",
|
|
199
|
+
name: "HexStrike API High Latency",
|
|
200
|
+
description: "Triggers when HexStrike API latency exceeds 5 seconds",
|
|
201
|
+
enabled: true,
|
|
202
|
+
severity: "info",
|
|
203
|
+
threshold: {
|
|
204
|
+
metric: "poolbot.security.api_latency_ms",
|
|
205
|
+
operator: "gt",
|
|
206
|
+
value: 5000,
|
|
207
|
+
durationMs: 120000,
|
|
208
|
+
cooldownMs: 300000,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
// Swarm Alerts
|
|
212
|
+
{
|
|
213
|
+
id: "swarm-high-failure-rate",
|
|
214
|
+
name: "Swarm Task Failure Rate",
|
|
215
|
+
description: "Triggers when swarm tasks fail frequently",
|
|
216
|
+
enabled: true,
|
|
217
|
+
severity: "warning",
|
|
218
|
+
threshold: {
|
|
219
|
+
metric: "poolbot.swarm.task_failures",
|
|
220
|
+
operator: "gt",
|
|
221
|
+
value: 5,
|
|
222
|
+
durationMs: 600000,
|
|
223
|
+
cooldownMs: 600000,
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
// Gateway Health Alerts
|
|
227
|
+
{
|
|
228
|
+
id: "gateway-high-memory",
|
|
229
|
+
name: "High Gateway Memory Usage",
|
|
230
|
+
description: "Triggers when gateway memory usage exceeds 512MB",
|
|
231
|
+
enabled: true,
|
|
232
|
+
severity: "critical",
|
|
233
|
+
threshold: {
|
|
234
|
+
metric: "poolbot.gateway.memory_usage_bytes",
|
|
235
|
+
operator: "gt",
|
|
236
|
+
value: 536870912, // 512MB
|
|
237
|
+
durationMs: 120000,
|
|
238
|
+
cooldownMs: 300000,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "gateway-high-cpu",
|
|
243
|
+
name: "High Gateway CPU Usage",
|
|
244
|
+
description: "Triggers when gateway CPU usage exceeds 80%",
|
|
245
|
+
enabled: true,
|
|
246
|
+
severity: "warning",
|
|
247
|
+
threshold: {
|
|
248
|
+
metric: "poolbot.gateway.cpu_usage_percent",
|
|
249
|
+
operator: "gt",
|
|
250
|
+
value: 80,
|
|
251
|
+
durationMs: 180000,
|
|
252
|
+
cooldownMs: 300000,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
export function createAlertEngine(customRules) {
|
|
257
|
+
return new AlertEngine([...defaultAlertRules, ...(customRules ?? [])]);
|
|
258
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { recordCounter, recordGauge, recordHistogram, withInstrumentation, } from "../telemetry/instrumentation.js";
|
|
2
|
+
export function instrumentCronOperation(operation, fn, attributes) {
|
|
3
|
+
return withInstrumentation({
|
|
4
|
+
operation,
|
|
5
|
+
component: "cron",
|
|
6
|
+
attributes,
|
|
7
|
+
}, fn);
|
|
8
|
+
}
|
|
9
|
+
export function recordCronJobExecution(metrics) {
|
|
10
|
+
recordCounter("poolbot.cron.jobs_executed", 1, {
|
|
11
|
+
job_id: metrics.jobId,
|
|
12
|
+
job_name: metrics.jobName,
|
|
13
|
+
schedule: metrics.schedule,
|
|
14
|
+
status: metrics.success ? "success" : "failure",
|
|
15
|
+
});
|
|
16
|
+
recordHistogram("poolbot.cron.job_duration_ms", metrics.durationMs, {
|
|
17
|
+
job_id: metrics.jobId,
|
|
18
|
+
job_name: metrics.jobName,
|
|
19
|
+
});
|
|
20
|
+
if (!metrics.success) {
|
|
21
|
+
recordCounter("poolbot.cron.job_failures", 1, {
|
|
22
|
+
job_id: metrics.jobId,
|
|
23
|
+
job_name: metrics.jobName,
|
|
24
|
+
error_type: metrics.errorType ?? "unknown",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function recordCronJobScheduled(jobId, jobName, schedule) {
|
|
29
|
+
recordCounter("poolbot.cron.jobs_scheduled", 1, {
|
|
30
|
+
job_id: jobId,
|
|
31
|
+
job_name: jobName,
|
|
32
|
+
schedule,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function recordCronJobCancelled(jobId, jobName) {
|
|
36
|
+
recordCounter("poolbot.cron.jobs_cancelled", 1, {
|
|
37
|
+
job_id: jobId,
|
|
38
|
+
job_name: jobName,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function updateActiveCronJobsGauge(count) {
|
|
42
|
+
recordGauge("poolbot.cron.active_jobs", count);
|
|
43
|
+
}
|
|
44
|
+
export function recordCronQueueDepth(depth) {
|
|
45
|
+
recordGauge("poolbot.cron.queue_depth", depth);
|
|
46
|
+
}
|
|
47
|
+
export function recordCronOverdueJobs(count) {
|
|
48
|
+
recordGauge("poolbot.cron.overdue_jobs", count);
|
|
49
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { recordCounter, recordHistogram, withInstrumentation, } from "../telemetry/instrumentation.js";
|
|
2
|
+
export function instrumentGatewayRequest(operation, fn, attributes) {
|
|
3
|
+
return withInstrumentation({
|
|
4
|
+
operation,
|
|
5
|
+
component: "gateway",
|
|
6
|
+
attributes,
|
|
7
|
+
}, fn);
|
|
8
|
+
}
|
|
9
|
+
export function recordGatewayRequest(metrics) {
|
|
10
|
+
recordCounter("poolbot.gateway.requests", 1, {
|
|
11
|
+
method: metrics.method,
|
|
12
|
+
path: metrics.path,
|
|
13
|
+
status_code: String(metrics.statusCode),
|
|
14
|
+
});
|
|
15
|
+
recordHistogram("poolbot.gateway.request_duration_ms", metrics.durationMs, {
|
|
16
|
+
method: metrics.method,
|
|
17
|
+
path: metrics.path,
|
|
18
|
+
});
|
|
19
|
+
if (metrics.statusCode >= 500) {
|
|
20
|
+
recordCounter("poolbot.gateway.server_errors", 1, {
|
|
21
|
+
path: metrics.path,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (metrics.statusCode >= 400) {
|
|
25
|
+
recordCounter("poolbot.gateway.client_errors", 1, {
|
|
26
|
+
path: metrics.path,
|
|
27
|
+
status_code: String(metrics.statusCode),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function createTelemetryMiddleware() {
|
|
32
|
+
return (req, res, next) => {
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
const path = req.url ?? "/unknown";
|
|
35
|
+
const method = req.method ?? "UNKNOWN";
|
|
36
|
+
recordCounter("poolbot.gateway.active_requests", 1, {
|
|
37
|
+
method,
|
|
38
|
+
path,
|
|
39
|
+
});
|
|
40
|
+
const originalEnd = res.end.bind(res);
|
|
41
|
+
res.end = function (...args) {
|
|
42
|
+
const duration = Date.now() - startTime;
|
|
43
|
+
recordCounter("poolbot.gateway.active_requests", -1, {
|
|
44
|
+
method,
|
|
45
|
+
path,
|
|
46
|
+
});
|
|
47
|
+
recordGatewayRequest({
|
|
48
|
+
method,
|
|
49
|
+
path,
|
|
50
|
+
statusCode: res.statusCode,
|
|
51
|
+
durationMs: duration,
|
|
52
|
+
userAgent: req.headers["user-agent"],
|
|
53
|
+
});
|
|
54
|
+
return originalEnd(...args);
|
|
55
|
+
};
|
|
56
|
+
next();
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function instrumentWebSocketConnection(connectionId) {
|
|
60
|
+
recordCounter("poolbot.gateway.ws.connections", 1, {
|
|
61
|
+
connection_id: connectionId,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export function recordWebSocketMessage(connectionId, messageType, sizeBytes) {
|
|
65
|
+
recordCounter("poolbot.gateway.ws.messages", 1, {
|
|
66
|
+
connection_id: connectionId,
|
|
67
|
+
message_type: messageType,
|
|
68
|
+
});
|
|
69
|
+
recordHistogram("poolbot.gateway.ws.message_size_bytes", sizeBytes, {
|
|
70
|
+
message_type: messageType,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function recordWebSocketDisconnection(connectionId, durationMs) {
|
|
74
|
+
recordCounter("poolbot.gateway.ws.disconnections", 1, {
|
|
75
|
+
connection_id: connectionId,
|
|
76
|
+
});
|
|
77
|
+
recordHistogram("poolbot.gateway.ws.connection_duration_ms", durationMs, {
|
|
78
|
+
connection_id: connectionId,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getGlobalTelemetryService } from "../telemetry/service.js";
|
|
2
|
+
export function withInstrumentation(options, fn) {
|
|
3
|
+
const telemetry = getGlobalTelemetryService();
|
|
4
|
+
if (!telemetry?.isEnabled()) {
|
|
5
|
+
return Promise.resolve(fn());
|
|
6
|
+
}
|
|
7
|
+
const spanName = `${options.component}.${options.operation}`;
|
|
8
|
+
const attributes = {
|
|
9
|
+
component: options.component,
|
|
10
|
+
operation: options.operation,
|
|
11
|
+
...options.attributes,
|
|
12
|
+
};
|
|
13
|
+
telemetry.recordCounter(`poolbot.${options.component}.operations`, 1, {
|
|
14
|
+
operation: options.operation,
|
|
15
|
+
});
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
return telemetry.withSpan(spanName, async () => {
|
|
18
|
+
try {
|
|
19
|
+
const result = await fn();
|
|
20
|
+
const duration = Date.now() - startTime;
|
|
21
|
+
telemetry.recordHistogram(`poolbot.${options.component}.duration_ms`, duration, {
|
|
22
|
+
operation: options.operation,
|
|
23
|
+
});
|
|
24
|
+
telemetry.recordCounter(`poolbot.${options.component}.success`, 1, {
|
|
25
|
+
operation: options.operation,
|
|
26
|
+
});
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const duration = Date.now() - startTime;
|
|
31
|
+
telemetry.recordHistogram(`poolbot.${options.component}.duration_ms`, duration, {
|
|
32
|
+
operation: options.operation,
|
|
33
|
+
error: "true",
|
|
34
|
+
});
|
|
35
|
+
telemetry.recordCounter(`poolbot.${options.component}.errors`, 1, {
|
|
36
|
+
operation: options.operation,
|
|
37
|
+
error_type: error instanceof Error ? error.name : "unknown",
|
|
38
|
+
});
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}, attributes);
|
|
42
|
+
}
|
|
43
|
+
export function recordMetric(name, value, attributes) {
|
|
44
|
+
const telemetry = getGlobalTelemetryService();
|
|
45
|
+
if (!telemetry?.isEnabled())
|
|
46
|
+
return;
|
|
47
|
+
telemetry.recordHistogram(name, value, attributes);
|
|
48
|
+
}
|
|
49
|
+
export function recordCounter(name, value = 1, attributes) {
|
|
50
|
+
const telemetry = getGlobalTelemetryService();
|
|
51
|
+
if (!telemetry?.isEnabled())
|
|
52
|
+
return;
|
|
53
|
+
telemetry.recordCounter(name, value, attributes);
|
|
54
|
+
}
|
|
55
|
+
export function recordGauge(name, value, attributes) {
|
|
56
|
+
const telemetry = getGlobalTelemetryService();
|
|
57
|
+
if (!telemetry?.isEnabled())
|
|
58
|
+
return;
|
|
59
|
+
telemetry.recordGauge(name, value, attributes);
|
|
60
|
+
}
|
|
61
|
+
export function recordHistogram(name, value, attributes) {
|
|
62
|
+
const telemetry = getGlobalTelemetryService();
|
|
63
|
+
if (!telemetry?.isEnabled())
|
|
64
|
+
return;
|
|
65
|
+
telemetry.recordHistogram(name, value, attributes);
|
|
66
|
+
}
|