@futurewindai/wotchi 0.1.0-beta.2 → 0.1.0-beta.6
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/README.md +178 -47
- package/SECURITY.md +2 -2
- package/dist/cjs/core/admission.js +22 -0
- package/dist/cjs/core/client.js +416 -21
- package/dist/cjs/core/config.js +193 -18
- package/dist/cjs/core/diagnostics.js +16 -1
- package/dist/cjs/core/group-store.js +22 -9
- package/dist/cjs/core/incident-builder.js +18 -0
- package/dist/cjs/core/incident-policy.js +2 -7
- package/dist/cjs/core/limits.js +17 -0
- package/dist/cjs/core/normalize.js +16 -6
- package/dist/cjs/core/notification-queue.js +92 -6
- package/dist/cjs/core/prometheus.js +129 -0
- package/dist/cjs/core/redact.js +57 -8
- package/dist/cjs/core/runtime-monitor.js +109 -0
- package/dist/cjs/index.js +8 -1
- package/dist/cjs/integrations/express/error-handler.js +21 -4
- package/dist/cjs/integrations/express/index.js +2 -1
- package/dist/cjs/integrations/express/status-observer.js +6 -0
- package/dist/cjs/integrations/nest/capture.js +21 -0
- package/dist/cjs/integrations/nest/exception-filter.js +64 -8
- package/dist/cjs/integrations/nest/filter-wrapper.js +17 -0
- package/dist/cjs/integrations/nest/index.js +7 -1
- package/dist/cjs/integrations/nest/module.js +44 -0
- package/dist/cjs/integrations/nest/register.js +15 -1
- package/dist/cjs/integrations/request-context.js +83 -1
- package/dist/cjs/notifiers/alert-payload.js +20 -0
- package/dist/cjs/notifiers/console.js +85 -15
- package/dist/cjs/notifiers/telegram-format.js +75 -5
- package/dist/cjs/notifiers/webhook-http.js +394 -0
- package/dist/cjs/notifiers/webhook.js +28 -0
- package/dist/esm/core/admission.js +19 -0
- package/dist/esm/core/client.js +416 -21
- package/dist/esm/core/config.js +193 -18
- package/dist/esm/core/diagnostics.js +16 -1
- package/dist/esm/core/group-store.js +22 -9
- package/dist/esm/core/incident-builder.js +18 -0
- package/dist/esm/core/incident-policy.js +2 -7
- package/dist/esm/core/limits.js +14 -0
- package/dist/esm/core/normalize.js +16 -6
- package/dist/esm/core/notification-queue.js +92 -6
- package/dist/esm/core/prometheus.js +125 -0
- package/dist/esm/core/redact.js +57 -8
- package/dist/esm/core/runtime-monitor.js +106 -0
- package/dist/esm/index.js +4 -1
- package/dist/esm/integrations/express/error-handler.js +21 -4
- package/dist/esm/integrations/express/index.js +1 -1
- package/dist/esm/integrations/express/status-observer.js +6 -0
- package/dist/esm/integrations/nest/capture.js +17 -0
- package/dist/esm/integrations/nest/exception-filter.js +65 -9
- package/dist/esm/integrations/nest/filter-wrapper.js +14 -0
- package/dist/esm/integrations/nest/index.js +3 -1
- package/dist/esm/integrations/nest/module.js +41 -0
- package/dist/esm/integrations/nest/register.js +15 -1
- package/dist/esm/integrations/request-context.js +83 -1
- package/dist/esm/notifiers/alert-payload.js +16 -0
- package/dist/esm/notifiers/console.js +85 -15
- package/dist/esm/notifiers/telegram-format.js +75 -5
- package/dist/esm/notifiers/webhook-http.js +387 -0
- package/dist/esm/notifiers/webhook.js +24 -0
- package/dist/types/core/admission.d.ts +10 -0
- package/dist/types/core/config.d.ts +17 -1
- package/dist/types/core/diagnostics.d.ts +7 -1
- package/dist/types/core/incident-policy.d.ts +1 -1
- package/dist/types/core/limits.d.ts +14 -0
- package/dist/types/core/notification-queue.d.ts +14 -1
- package/dist/types/core/prometheus.d.ts +7 -0
- package/dist/types/core/runtime-monitor.d.ts +15 -0
- package/dist/types/core/types.d.ts +111 -2
- package/dist/types/index.d.ts +8 -2
- package/dist/types/integrations/express/index.d.ts +2 -2
- package/dist/types/integrations/nest/capture.d.ts +4 -0
- package/dist/types/integrations/nest/exception-filter.d.ts +6 -2
- package/dist/types/integrations/nest/filter-wrapper.d.ts +7 -0
- package/dist/types/integrations/nest/index.d.ts +6 -2
- package/dist/types/integrations/nest/module.d.ts +9 -0
- package/dist/types/integrations/nest/register.d.ts +3 -0
- package/dist/types/integrations/request-context.d.ts +3 -0
- package/dist/types/notifiers/alert-payload.d.ts +5 -0
- package/dist/types/notifiers/webhook-http.d.ts +37 -0
- package/dist/types/notifiers/webhook.d.ts +4 -0
- package/dist/types-cjs/core/admission.d.cts +10 -0
- package/dist/types-cjs/core/config.d.cts +17 -1
- package/dist/types-cjs/core/diagnostics.d.cts +7 -1
- package/dist/types-cjs/core/incident-policy.d.cts +1 -1
- package/dist/types-cjs/core/limits.d.cts +14 -0
- package/dist/types-cjs/core/notification-queue.d.cts +14 -1
- package/dist/types-cjs/core/prometheus.d.cts +7 -0
- package/dist/types-cjs/core/runtime-monitor.d.cts +15 -0
- package/dist/types-cjs/core/types.d.cts +111 -2
- package/dist/types-cjs/index.d.cts +8 -2
- package/dist/types-cjs/integrations/express/index.d.cts +2 -2
- package/dist/types-cjs/integrations/nest/capture.d.cts +4 -0
- package/dist/types-cjs/integrations/nest/exception-filter.d.cts +6 -2
- package/dist/types-cjs/integrations/nest/filter-wrapper.d.cts +7 -0
- package/dist/types-cjs/integrations/nest/index.d.cts +6 -2
- package/dist/types-cjs/integrations/nest/module.d.cts +9 -0
- package/dist/types-cjs/integrations/nest/register.d.cts +3 -0
- package/dist/types-cjs/integrations/request-context.d.cts +3 -0
- package/dist/types-cjs/notifiers/alert-payload.d.cts +5 -0
- package/dist/types-cjs/notifiers/webhook-http.d.cts +37 -0
- package/dist/types-cjs/notifiers/webhook.d.cts +4 -0
- package/package.json +17 -4
|
@@ -5,13 +5,27 @@ exports.registerWotchiNestStatusObserver = registerWotchiNestStatusObserver;
|
|
|
5
5
|
const core_1 = require("@nestjs/core");
|
|
6
6
|
const exception_filter_js_1 = require("./exception-filter.js");
|
|
7
7
|
const status_observer_js_1 = require("../express/status-observer.js");
|
|
8
|
+
const isNestExceptionFilter = (value) => typeof value === "object" &&
|
|
9
|
+
value !== null &&
|
|
10
|
+
"catch" in value &&
|
|
11
|
+
typeof value.catch === "function";
|
|
12
|
+
const getPreviousGlobalFilters = (application) => {
|
|
13
|
+
const configuredFilters = application.config?.getGlobalFilters?.();
|
|
14
|
+
if (!Array.isArray(configuredFilters)) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
return configuredFilters
|
|
18
|
+
.filter(isNestExceptionFilter)
|
|
19
|
+
.filter((filter) => !(filter instanceof exception_filter_js_1.WotchiNestExceptionFilter))
|
|
20
|
+
.reverse();
|
|
21
|
+
};
|
|
8
22
|
function registerWotchiNest(app, client, options) {
|
|
9
23
|
const application = app;
|
|
10
24
|
const directAdapter = application.getHttpAdapter?.();
|
|
11
25
|
const httpAdapter = directAdapter === undefined
|
|
12
26
|
? application.get(core_1.HttpAdapterHost).httpAdapter
|
|
13
27
|
: directAdapter;
|
|
14
|
-
application.useGlobalFilters(new exception_filter_js_1.WotchiNestExceptionFilter(client, httpAdapter, options));
|
|
28
|
+
application.useGlobalFilters(new exception_filter_js_1.WotchiNestExceptionFilter(client, httpAdapter, options, getPreviousGlobalFilters(application)));
|
|
15
29
|
}
|
|
16
30
|
function registerWotchiNestStatusObserver(app, client, options) {
|
|
17
31
|
const application = app;
|
|
@@ -4,6 +4,7 @@ exports.normalizeRoutePath = normalizeRoutePath;
|
|
|
4
4
|
exports.buildRequestContext = buildRequestContext;
|
|
5
5
|
const MAX_ROUTE_LENGTH = 500;
|
|
6
6
|
const MAX_REQUEST_ID_LENGTH = 200;
|
|
7
|
+
const MAX_CORRELATION_ID_LENGTH = 200;
|
|
7
8
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
8
9
|
const HEX_PATTERN = /^[0-9a-f]{8,}$/i;
|
|
9
10
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
@@ -32,6 +33,24 @@ const normalizeRequestIdProperty = (property) => {
|
|
|
32
33
|
}
|
|
33
34
|
return property;
|
|
34
35
|
};
|
|
36
|
+
const normalizeTraceContextProperty = (property) => {
|
|
37
|
+
if (property === undefined) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(property)) {
|
|
41
|
+
throw new TypeError("traceContextProperty must be a simple property name");
|
|
42
|
+
}
|
|
43
|
+
return property;
|
|
44
|
+
};
|
|
45
|
+
const normalizeCorrelationIdProperty = (property) => {
|
|
46
|
+
if (property === undefined) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(property)) {
|
|
50
|
+
throw new TypeError("correlationIdProperty must be a simple property name");
|
|
51
|
+
}
|
|
52
|
+
return property;
|
|
53
|
+
};
|
|
35
54
|
const readRequestId = (request, property) => {
|
|
36
55
|
if (property === undefined || !isRecord(request)) {
|
|
37
56
|
return undefined;
|
|
@@ -47,8 +66,51 @@ const readRequestId = (request, property) => {
|
|
|
47
66
|
return undefined;
|
|
48
67
|
}
|
|
49
68
|
};
|
|
69
|
+
const readTraceContext = (request, property) => {
|
|
70
|
+
if (property === undefined || !isRecord(request)) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
if (!Object.prototype.hasOwnProperty.call(request, property)) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const value = request[property];
|
|
78
|
+
if (!isRecord(value)) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const traceId = typeof value.traceId === "string" ? value.traceId.slice(0, 128) : undefined;
|
|
82
|
+
const spanId = typeof value.spanId === "string" ? value.spanId.slice(0, 128) : undefined;
|
|
83
|
+
if (traceId === undefined && spanId === undefined) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
...(traceId === undefined ? {} : { traceId }),
|
|
88
|
+
...(spanId === undefined ? {} : { spanId }),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const readCorrelationId = (request, property) => {
|
|
96
|
+
if (property === undefined || !isRecord(request)) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
if (!Object.prototype.hasOwnProperty.call(request, property)) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const value = request[property];
|
|
104
|
+
return typeof value === "string" ? value.slice(0, MAX_CORRELATION_ID_LENGTH) : undefined;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
50
110
|
function buildRequestContext(input) {
|
|
51
111
|
const requestIdProperty = normalizeRequestIdProperty(input.options?.requestIdProperty);
|
|
112
|
+
const correlationIdProperty = normalizeCorrelationIdProperty(input.options?.correlationIdProperty);
|
|
113
|
+
const traceContextProperty = normalizeTraceContextProperty(input.options?.traceContextProperty);
|
|
52
114
|
let requestId;
|
|
53
115
|
try {
|
|
54
116
|
requestId = readRequestId(input.request, requestIdProperty);
|
|
@@ -56,6 +118,22 @@ function buildRequestContext(input) {
|
|
|
56
118
|
catch {
|
|
57
119
|
requestId = undefined;
|
|
58
120
|
}
|
|
121
|
+
let correlationId;
|
|
122
|
+
try {
|
|
123
|
+
correlationId = readCorrelationId(input.request, correlationIdProperty);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
correlationId = undefined;
|
|
127
|
+
}
|
|
128
|
+
let trace;
|
|
129
|
+
try {
|
|
130
|
+
const explicitTrace = input.trace;
|
|
131
|
+
trace = readTraceContext({ trace: explicitTrace }, "trace");
|
|
132
|
+
trace ??= readTraceContext(input.request, traceContextProperty);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
trace = undefined;
|
|
136
|
+
}
|
|
59
137
|
const method = typeof input.method === "string" ? input.method.slice(0, 16) : undefined;
|
|
60
138
|
const route = normalizeRoutePath(input.route);
|
|
61
139
|
const statusCode = typeof input.statusCode === "number" && Number.isSafeInteger(input.statusCode)
|
|
@@ -64,7 +142,9 @@ function buildRequestContext(input) {
|
|
|
64
142
|
if (method === undefined &&
|
|
65
143
|
route === undefined &&
|
|
66
144
|
statusCode === undefined &&
|
|
67
|
-
requestId === undefined
|
|
145
|
+
requestId === undefined &&
|
|
146
|
+
correlationId === undefined &&
|
|
147
|
+
trace === undefined) {
|
|
68
148
|
return undefined;
|
|
69
149
|
}
|
|
70
150
|
return {
|
|
@@ -72,5 +152,7 @@ function buildRequestContext(input) {
|
|
|
72
152
|
...(route === undefined ? {} : { route }),
|
|
73
153
|
...(statusCode === undefined ? {} : { statusCode }),
|
|
74
154
|
...(requestId === undefined ? {} : { requestId }),
|
|
155
|
+
...(correlationId === undefined ? {} : { correlationId }),
|
|
156
|
+
...(trace === undefined ? {} : { trace }),
|
|
75
157
|
};
|
|
76
158
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toBoundedPayload = toBoundedPayload;
|
|
4
|
+
exports.toBoundedAlertPayload = toBoundedAlertPayload;
|
|
5
|
+
const redact_js_1 = require("../core/redact.js");
|
|
6
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
function toBoundedPayload(value) {
|
|
8
|
+
const redacted = (0, redact_js_1.redactValue)(value, {
|
|
9
|
+
maxDepth: 6,
|
|
10
|
+
maxKeys: 100,
|
|
11
|
+
maxStringLength: 1_000,
|
|
12
|
+
maxStackLength: 4_000,
|
|
13
|
+
});
|
|
14
|
+
return isRecord(redacted)
|
|
15
|
+
? redacted
|
|
16
|
+
: { title: "Wotchi alert", summary: "Incident details were unavailable." };
|
|
17
|
+
}
|
|
18
|
+
function toBoundedAlertPayload(alert) {
|
|
19
|
+
return toBoundedPayload(alert);
|
|
20
|
+
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.formatConsoleAlert = formatConsoleAlert;
|
|
4
4
|
exports.consoleNotifier = consoleNotifier;
|
|
5
5
|
const redact_js_1 = require("../core/redact.js");
|
|
6
|
+
const alert_payload_js_1 = require("./alert-payload.js");
|
|
6
7
|
const MAX_FIELD_LENGTH = 1_000;
|
|
7
8
|
const MAX_ACTIONS = 5;
|
|
8
9
|
const limit = (value, maxLength = MAX_FIELD_LENGTH) => value.slice(0, maxLength);
|
|
@@ -10,26 +11,94 @@ const safeText = (value, maxLength = MAX_FIELD_LENGTH) => {
|
|
|
10
11
|
const redacted = (0, redact_js_1.redactValue)(value, { maxStringLength: maxLength });
|
|
11
12
|
return typeof redacted === "string" ? limit(redacted, maxLength) : "[unreadable value]";
|
|
12
13
|
};
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
const safeJson = (value) => {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.stringify(value);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return "[unreadable value]";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const boundedAlert = (alert) => {
|
|
23
|
+
const payload = (0, alert_payload_js_1.toBoundedAlertPayload)(alert);
|
|
24
|
+
return {
|
|
25
|
+
...payload,
|
|
26
|
+
title: safeText(typeof payload.title === "string" ? payload.title : alert.title),
|
|
27
|
+
fingerprint: safeText(typeof payload.fingerprint === "string" ? payload.fingerprint : alert.fingerprint),
|
|
28
|
+
severity: alert.severity,
|
|
29
|
+
summary: safeText(typeof payload.summary === "string" ? payload.summary : alert.summary),
|
|
30
|
+
suggestedActions: alert.suggestedActions
|
|
31
|
+
.slice(0, MAX_ACTIONS)
|
|
32
|
+
.map((action) => safeText(action, 300)),
|
|
33
|
+
firstSeenAt: safeText(alert.firstSeenAt, 100),
|
|
34
|
+
lastSeenAt: safeText(alert.lastSeenAt, 100),
|
|
35
|
+
occurrences: Number.isSafeInteger(alert.occurrences) && alert.occurrences >= 0 ? alert.occurrences : 0,
|
|
36
|
+
service: safeText(alert.service, 300),
|
|
37
|
+
environment: safeText(alert.environment, 300),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const textDetails = (alert) => {
|
|
41
|
+
const lines = [];
|
|
42
|
+
if (alert.release !== undefined) {
|
|
43
|
+
lines.push(`Release: ${safeText(alert.release, 200)}`);
|
|
44
|
+
}
|
|
45
|
+
if (alert.instance !== undefined) {
|
|
46
|
+
lines.push(`Instance: ${safeText(alert.instance, 200)}`);
|
|
47
|
+
}
|
|
48
|
+
if (alert.operation !== undefined) {
|
|
49
|
+
lines.push(`Operation: ${safeText(alert.operation, 200)}`);
|
|
50
|
+
}
|
|
51
|
+
if (alert.job !== undefined) {
|
|
52
|
+
lines.push(`Job: ${safeText(alert.job, 200)}`);
|
|
53
|
+
}
|
|
54
|
+
if (alert.correlationId !== undefined) {
|
|
55
|
+
lines.push(`Correlation ID: ${safeText(alert.correlationId, 200)}`);
|
|
56
|
+
}
|
|
57
|
+
if (alert.tags !== undefined) {
|
|
58
|
+
lines.push(`Tags: ${safeText(safeJson(alert.tags), 600)}`);
|
|
59
|
+
}
|
|
60
|
+
if (alert.error !== undefined) {
|
|
61
|
+
lines.push(`Error: ${safeText(alert.error.name, 300)} — ${safeText(alert.error.message, 600)}`);
|
|
62
|
+
if (alert.error.applicationFrame !== undefined) {
|
|
63
|
+
lines.push(`Application frame: ${safeText(alert.error.applicationFrame, 500)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (alert.request !== undefined) {
|
|
67
|
+
const request = [alert.request.method, alert.request.route].filter((value) => typeof value === "string");
|
|
68
|
+
if (alert.request.statusCode !== undefined) {
|
|
69
|
+
request.push(`status ${alert.request.statusCode}`);
|
|
70
|
+
}
|
|
71
|
+
if (request.length > 0) {
|
|
72
|
+
lines.push(`Request: ${request.join(" ")}`);
|
|
73
|
+
}
|
|
74
|
+
if (alert.request.requestId !== undefined) {
|
|
75
|
+
lines.push(`Request ID: ${safeText(alert.request.requestId, 200)}`);
|
|
76
|
+
}
|
|
77
|
+
if (alert.request.correlationId !== undefined) {
|
|
78
|
+
lines.push(`Correlation ID: ${safeText(alert.request.correlationId, 200)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const trace = alert.trace ?? alert.request?.trace;
|
|
82
|
+
if (trace?.traceId !== undefined || trace?.spanId !== undefined) {
|
|
83
|
+
lines.push(`Trace: ${safeText(trace.traceId ?? "", 128)}${trace.spanId === undefined ? "" : ` span ${safeText(trace.spanId, 128)}`}`);
|
|
84
|
+
}
|
|
85
|
+
if (alert.context !== undefined) {
|
|
86
|
+
lines.push(`Context: ${safeText(safeJson(alert.context), 1_000)}`);
|
|
87
|
+
}
|
|
88
|
+
if (alert.links !== undefined) {
|
|
89
|
+
lines.push(`Links: ${safeText(safeJson(alert.links), 600)}`);
|
|
90
|
+
}
|
|
91
|
+
return lines;
|
|
92
|
+
};
|
|
27
93
|
function formatConsoleAlert(alert, format = "text") {
|
|
28
94
|
const bounded = boundedAlert(alert);
|
|
29
95
|
if (format === "json") {
|
|
30
96
|
return JSON.stringify(bounded);
|
|
31
97
|
}
|
|
32
|
-
const actions =
|
|
98
|
+
const actions = alert.suggestedActions
|
|
99
|
+
.slice(0, MAX_ACTIONS)
|
|
100
|
+
.map((action) => `- ${safeText(action, 300)}`)
|
|
101
|
+
.join("\n");
|
|
33
102
|
return [
|
|
34
103
|
bounded.title,
|
|
35
104
|
`Service: ${bounded.service}`,
|
|
@@ -38,6 +107,7 @@ function formatConsoleAlert(alert, format = "text") {
|
|
|
38
107
|
`Occurrences: ${bounded.occurrences}`,
|
|
39
108
|
`First seen: ${bounded.firstSeenAt}`,
|
|
40
109
|
`Last seen: ${bounded.lastSeenAt}`,
|
|
110
|
+
...textDetails(alert),
|
|
41
111
|
"Suggested checks:",
|
|
42
112
|
actions,
|
|
43
113
|
].join("\n");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatTelegramAlert = formatTelegramAlert;
|
|
4
|
+
const redact_js_1 = require("../core/redact.js");
|
|
4
5
|
const MAX_MESSAGE_LENGTH = 4_096;
|
|
5
6
|
const MAX_ACTIONS = 5;
|
|
6
7
|
const MAX_TITLE_LENGTH = 300;
|
|
@@ -8,9 +9,76 @@ const MAX_SERVICE_LENGTH = 300;
|
|
|
8
9
|
const MAX_ENVIRONMENT_LENGTH = 300;
|
|
9
10
|
const MAX_SUMMARY_LENGTH = 2_000;
|
|
10
11
|
const MAX_ACTION_LENGTH = 600;
|
|
12
|
+
const MAX_DETAIL_LENGTH = 900;
|
|
11
13
|
const escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
12
14
|
const limit = (value, maxLength) => value.slice(0, maxLength);
|
|
13
|
-
const
|
|
15
|
+
const safeText = (value, maxLength) => {
|
|
16
|
+
const redacted = (0, redact_js_1.redactValue)(value, { maxStringLength: maxLength });
|
|
17
|
+
return typeof redacted === "string" ? limit(redacted, maxLength) : "[unreadable value]";
|
|
18
|
+
};
|
|
19
|
+
const safeJson = (value) => {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return "[unreadable value]";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const details = (alert) => {
|
|
28
|
+
const lines = [];
|
|
29
|
+
if (alert.release !== undefined) {
|
|
30
|
+
lines.push(`Release: ${safeText(alert.release, 200)}`);
|
|
31
|
+
}
|
|
32
|
+
if (alert.instance !== undefined) {
|
|
33
|
+
lines.push(`Instance: ${safeText(alert.instance, 200)}`);
|
|
34
|
+
}
|
|
35
|
+
if (alert.operation !== undefined) {
|
|
36
|
+
lines.push(`Operation: ${safeText(alert.operation, 200)}`);
|
|
37
|
+
}
|
|
38
|
+
if (alert.job !== undefined) {
|
|
39
|
+
lines.push(`Job: ${safeText(alert.job, 200)}`);
|
|
40
|
+
}
|
|
41
|
+
if (alert.correlationId !== undefined) {
|
|
42
|
+
lines.push(`Correlation ID: ${safeText(alert.correlationId, 200)}`);
|
|
43
|
+
}
|
|
44
|
+
if (alert.tags !== undefined) {
|
|
45
|
+
lines.push(`Tags: ${safeText(safeJson(alert.tags), MAX_DETAIL_LENGTH)}`);
|
|
46
|
+
}
|
|
47
|
+
if (alert.error !== undefined) {
|
|
48
|
+
lines.push(`Error: ${safeText(alert.error.name, 250)} — ${safeText(alert.error.message, 500)}`);
|
|
49
|
+
if (alert.error.applicationFrame !== undefined) {
|
|
50
|
+
lines.push(`Application frame: ${safeText(alert.error.applicationFrame, 500)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (alert.request !== undefined) {
|
|
54
|
+
const request = [alert.request.method, alert.request.route].filter((value) => typeof value === "string");
|
|
55
|
+
if (alert.request.statusCode !== undefined) {
|
|
56
|
+
request.push(`status ${alert.request.statusCode}`);
|
|
57
|
+
}
|
|
58
|
+
if (request.length > 0) {
|
|
59
|
+
lines.push(`Request: ${request.join(" ")}`);
|
|
60
|
+
}
|
|
61
|
+
if (alert.request.requestId !== undefined) {
|
|
62
|
+
lines.push(`Request ID: ${safeText(alert.request.requestId, 200)}`);
|
|
63
|
+
}
|
|
64
|
+
if (alert.request.correlationId !== undefined) {
|
|
65
|
+
lines.push(`Correlation ID: ${safeText(alert.request.correlationId, 200)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const trace = alert.trace ?? alert.request?.trace;
|
|
69
|
+
if (trace?.traceId !== undefined || trace?.spanId !== undefined) {
|
|
70
|
+
lines.push(`Trace: ${safeText(trace.traceId ?? "", 128)}${trace.spanId === undefined ? "" : ` span ${safeText(trace.spanId, 128)}`}`);
|
|
71
|
+
}
|
|
72
|
+
if (alert.context !== undefined) {
|
|
73
|
+
const redacted = (0, redact_js_1.redactValue)(alert.context, { maxDepth: 4, maxKeys: 40, maxStringLength: 300 });
|
|
74
|
+
lines.push(`Context: ${safeText(safeJson(redacted), MAX_DETAIL_LENGTH)}`);
|
|
75
|
+
}
|
|
76
|
+
if (alert.links !== undefined) {
|
|
77
|
+
lines.push(`Links: ${safeText(safeJson(alert.links), MAX_DETAIL_LENGTH)}`);
|
|
78
|
+
}
|
|
79
|
+
return lines.map((line) => limit(line, MAX_DETAIL_LENGTH));
|
|
80
|
+
};
|
|
81
|
+
const render = (title, service, environment, summary, actions, context, firstSeenAt, lastSeenAt, occurrences) => {
|
|
14
82
|
const actionText = actions.length === 0
|
|
15
83
|
? "- No suggested checks."
|
|
16
84
|
: actions.map((action) => `- ${escapeHtml(action)}`).join("\n");
|
|
@@ -22,6 +90,7 @@ const render = (title, service, environment, summary, actions, firstSeenAt, last
|
|
|
22
90
|
`Occurrences: ${occurrences}`,
|
|
23
91
|
`First seen: ${escapeHtml(firstSeenAt)}`,
|
|
24
92
|
`Last seen: ${escapeHtml(lastSeenAt)}`,
|
|
93
|
+
...context.map((line) => escapeHtml(line)),
|
|
25
94
|
"Suggested checks:",
|
|
26
95
|
actionText,
|
|
27
96
|
].join("\n");
|
|
@@ -36,17 +105,18 @@ function formatTelegramAlert(alert) {
|
|
|
36
105
|
let actions = alert.suggestedActions
|
|
37
106
|
.slice(0, MAX_ACTIONS)
|
|
38
107
|
.map((action) => limit(action, MAX_ACTION_LENGTH));
|
|
39
|
-
|
|
108
|
+
const context = details(alert);
|
|
109
|
+
let formatted = render(title, service, environment, summary, actions, context, firstSeenAt, lastSeenAt, alert.occurrences);
|
|
40
110
|
while (formatted.length > MAX_MESSAGE_LENGTH && actions.length > 0) {
|
|
41
111
|
actions = actions.slice(0, -1);
|
|
42
|
-
formatted = render(title, service, environment, summary, actions, firstSeenAt, lastSeenAt, alert.occurrences);
|
|
112
|
+
formatted = render(title, service, environment, summary, actions, context, firstSeenAt, lastSeenAt, alert.occurrences);
|
|
43
113
|
}
|
|
44
114
|
while (formatted.length > MAX_MESSAGE_LENGTH && summary.length > 0) {
|
|
45
115
|
summary = summary.slice(0, Math.max(0, summary.length - 200));
|
|
46
|
-
formatted = render(title, service, environment, summary, actions, firstSeenAt, lastSeenAt, alert.occurrences);
|
|
116
|
+
formatted = render(title, service, environment, summary, actions, context, firstSeenAt, lastSeenAt, alert.occurrences);
|
|
47
117
|
}
|
|
48
118
|
if (formatted.length > MAX_MESSAGE_LENGTH) {
|
|
49
|
-
formatted = render(limit(title, 100), limit(service, 100), limit(environment, 100), "Incident details were truncated.", [], firstSeenAt, lastSeenAt, alert.occurrences);
|
|
119
|
+
formatted = render(limit(title, 100), limit(service, 100), limit(environment, 100), "Incident details were truncated.", [], [], firstSeenAt, lastSeenAt, alert.occurrences);
|
|
50
120
|
}
|
|
51
121
|
return formatted;
|
|
52
122
|
}
|