@futurewindai/wotchi 0.1.0-beta.1
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/LICENSE +201 -0
- package/README.md +128 -0
- package/SECURITY.md +17 -0
- package/dist/cjs/core/client.js +154 -0
- package/dist/cjs/core/config.js +114 -0
- package/dist/cjs/core/diagnostics.js +14 -0
- package/dist/cjs/core/errors.js +11 -0
- package/dist/cjs/core/fingerprint.js +37 -0
- package/dist/cjs/core/group-store.js +95 -0
- package/dist/cjs/core/incident-builder.js +46 -0
- package/dist/cjs/core/incident-policy.js +31 -0
- package/dist/cjs/core/normalize.js +138 -0
- package/dist/cjs/core/notification-queue.js +103 -0
- package/dist/cjs/core/process-monitor.js +30 -0
- package/dist/cjs/core/redact.js +141 -0
- package/dist/cjs/core/rolling-window.js +60 -0
- package/dist/cjs/core/stack-frame.js +33 -0
- package/dist/cjs/core/types.js +2 -0
- package/dist/cjs/index.js +13 -0
- package/dist/cjs/integrations/express/error-handler.js +15 -0
- package/dist/cjs/integrations/express/index.js +12 -0
- package/dist/cjs/integrations/express/request-context.js +19 -0
- package/dist/cjs/integrations/nest/exception-filter.js +36 -0
- package/dist/cjs/integrations/nest/index.js +9 -0
- package/dist/cjs/integrations/nest/register.js +13 -0
- package/dist/cjs/integrations/nest/request-context.js +41 -0
- package/dist/cjs/integrations/request-context.js +76 -0
- package/dist/cjs/notifiers/console.js +33 -0
- package/dist/cjs/notifiers/telegram-format.js +52 -0
- package/dist/cjs/notifiers/telegram-http.js +150 -0
- package/dist/cjs/notifiers/telegram.js +38 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/core/client.js +151 -0
- package/dist/esm/core/config.js +110 -0
- package/dist/esm/core/diagnostics.js +10 -0
- package/dist/esm/core/errors.js +7 -0
- package/dist/esm/core/fingerprint.js +33 -0
- package/dist/esm/core/group-store.js +92 -0
- package/dist/esm/core/incident-builder.js +43 -0
- package/dist/esm/core/incident-policy.js +28 -0
- package/dist/esm/core/normalize.js +134 -0
- package/dist/esm/core/notification-queue.js +100 -0
- package/dist/esm/core/process-monitor.js +27 -0
- package/dist/esm/core/redact.js +136 -0
- package/dist/esm/core/rolling-window.js +57 -0
- package/dist/esm/core/stack-frame.js +29 -0
- package/dist/esm/core/types.js +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/integrations/express/error-handler.js +12 -0
- package/dist/esm/integrations/express/index.js +5 -0
- package/dist/esm/integrations/express/request-context.js +16 -0
- package/dist/esm/integrations/nest/exception-filter.js +33 -0
- package/dist/esm/integrations/nest/index.js +2 -0
- package/dist/esm/integrations/nest/register.js +10 -0
- package/dist/esm/integrations/nest/request-context.js +38 -0
- package/dist/esm/integrations/request-context.js +72 -0
- package/dist/esm/notifiers/console.js +29 -0
- package/dist/esm/notifiers/telegram-format.js +49 -0
- package/dist/esm/notifiers/telegram-http.js +144 -0
- package/dist/esm/notifiers/telegram.js +34 -0
- package/dist/types/core/client.d.ts +2 -0
- package/dist/types/core/config.d.ts +29 -0
- package/dist/types/core/diagnostics.d.ts +7 -0
- package/dist/types/core/errors.d.ts +3 -0
- package/dist/types/core/fingerprint.d.ts +3 -0
- package/dist/types/core/group-store.d.ts +15 -0
- package/dist/types/core/incident-builder.d.ts +3 -0
- package/dist/types/core/incident-policy.d.ts +15 -0
- package/dist/types/core/normalize.d.ts +17 -0
- package/dist/types/core/notification-queue.d.ts +16 -0
- package/dist/types/core/process-monitor.d.ts +5 -0
- package/dist/types/core/redact.d.ts +11 -0
- package/dist/types/core/rolling-window.d.ts +12 -0
- package/dist/types/core/stack-frame.d.ts +2 -0
- package/dist/types/core/types.d.ts +109 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/integrations/express/error-handler.d.ts +4 -0
- package/dist/types/integrations/express/index.d.ts +7 -0
- package/dist/types/integrations/express/request-context.d.ts +5 -0
- package/dist/types/integrations/nest/exception-filter.d.ts +10 -0
- package/dist/types/integrations/nest/index.d.ts +4 -0
- package/dist/types/integrations/nest/register.d.ts +10 -0
- package/dist/types/integrations/nest/request-context.d.ts +5 -0
- package/dist/types/integrations/request-context.d.ts +13 -0
- package/dist/types/notifiers/console.d.ts +3 -0
- package/dist/types/notifiers/telegram-format.d.ts +2 -0
- package/dist/types/notifiers/telegram-http.d.ts +21 -0
- package/dist/types/notifiers/telegram.d.ts +4 -0
- package/package.json +111 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import https from "node:https";
|
|
2
|
+
const DEFAULT_TIMEOUT_MS = 3_000;
|
|
3
|
+
const MAX_RESPONSE_BYTES = 8_192;
|
|
4
|
+
const MAX_RETRY_DELAY_MS = 1_000;
|
|
5
|
+
const wait = (delayMs) => new Promise((resolve) => {
|
|
6
|
+
setTimeout(resolve, delayMs);
|
|
7
|
+
});
|
|
8
|
+
const failRequest = () => {
|
|
9
|
+
throw new Error("Telegram request failed");
|
|
10
|
+
};
|
|
11
|
+
const parseRetryAfter = (body) => {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(body);
|
|
14
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
const parameters = parsed.parameters;
|
|
18
|
+
if (typeof parameters !== "object" || parameters === null) {
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
const retryAfter = parameters.retry_after;
|
|
22
|
+
return typeof retryAfter === "number" && Number.isSafeInteger(retryAfter) && retryAfter > 0
|
|
23
|
+
? Math.min(retryAfter * 1_000, MAX_RETRY_DELAY_MS)
|
|
24
|
+
: 0;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const isSuccessResponse = (response) => {
|
|
31
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(response.body);
|
|
36
|
+
return (typeof parsed === "object" && parsed !== null && parsed.ok === true);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const createRequestOptions = (botToken) => ({
|
|
43
|
+
protocol: "https:",
|
|
44
|
+
hostname: "api.telegram.org",
|
|
45
|
+
method: "POST",
|
|
46
|
+
path: `/bot${botToken}/sendMessage`,
|
|
47
|
+
headers: {},
|
|
48
|
+
});
|
|
49
|
+
const httpsRequest = (options, body, timeoutMs) => new Promise((resolve, reject) => {
|
|
50
|
+
let settled = false;
|
|
51
|
+
const finish = (callback) => {
|
|
52
|
+
if (settled) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
settled = true;
|
|
56
|
+
callback();
|
|
57
|
+
};
|
|
58
|
+
const request = https.request(options, (response) => {
|
|
59
|
+
const chunks = [];
|
|
60
|
+
let size = 0;
|
|
61
|
+
response.on("data", (chunk) => {
|
|
62
|
+
size += Buffer.byteLength(chunk);
|
|
63
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
64
|
+
request.destroy();
|
|
65
|
+
finish(() => reject(new Error("Telegram response body exceeded limit")));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
69
|
+
});
|
|
70
|
+
response.once("error", (error) => finish(() => reject(error)));
|
|
71
|
+
response.once("end", () => finish(() => resolve({
|
|
72
|
+
statusCode: response.statusCode ?? 0,
|
|
73
|
+
headers: response.headers,
|
|
74
|
+
body: Buffer.concat(chunks).toString("utf8"),
|
|
75
|
+
})));
|
|
76
|
+
});
|
|
77
|
+
request.once("error", (error) => finish(() => reject(error)));
|
|
78
|
+
request.setTimeout(timeoutMs, () => {
|
|
79
|
+
request.destroy();
|
|
80
|
+
finish(() => reject(new Error("Telegram request timed out")));
|
|
81
|
+
});
|
|
82
|
+
request.end(body);
|
|
83
|
+
});
|
|
84
|
+
const requestWithTimeout = async (request, options, body, timeoutMs) => {
|
|
85
|
+
let timer;
|
|
86
|
+
try {
|
|
87
|
+
const timeout = new Promise((_, reject) => {
|
|
88
|
+
timer = setTimeout(() => reject(new Error("Telegram request timed out")), timeoutMs);
|
|
89
|
+
});
|
|
90
|
+
return await Promise.race([request(options, body, timeoutMs), timeout]);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error instanceof Error && error.message === "Telegram request timed out") {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
if (error instanceof Error && error.message === "Telegram response body exceeded limit") {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
return failRequest();
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
if (timer !== undefined) {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
export async function sendTelegramMessage(options, request = httpsRequest) {
|
|
108
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
109
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 30_000) {
|
|
110
|
+
throw new TypeError("timeoutMs is invalid");
|
|
111
|
+
}
|
|
112
|
+
const body = JSON.stringify({
|
|
113
|
+
chat_id: options.chatId,
|
|
114
|
+
text: options.text,
|
|
115
|
+
parse_mode: "HTML",
|
|
116
|
+
disable_web_page_preview: true,
|
|
117
|
+
});
|
|
118
|
+
const requestOptions = createRequestOptions(options.botToken);
|
|
119
|
+
requestOptions.headers = {
|
|
120
|
+
"content-type": "application/json",
|
|
121
|
+
"content-length": Buffer.byteLength(body, "utf8"),
|
|
122
|
+
};
|
|
123
|
+
let rateLimitRetries = 0;
|
|
124
|
+
let serverRetries = 0;
|
|
125
|
+
while (true) {
|
|
126
|
+
const response = await requestWithTimeout(request, requestOptions, body, timeoutMs);
|
|
127
|
+
if (Buffer.byteLength(response.body, "utf8") > MAX_RESPONSE_BYTES) {
|
|
128
|
+
throw new Error("Telegram response body exceeded limit");
|
|
129
|
+
}
|
|
130
|
+
if (isSuccessResponse(response)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (response.statusCode === 429 && rateLimitRetries === 0) {
|
|
134
|
+
rateLimitRetries += 1;
|
|
135
|
+
await wait(parseRetryAfter(response.body));
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (response.statusCode >= 500 && response.statusCode <= 599 && serverRetries === 0) {
|
|
139
|
+
serverRetries += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`Telegram API request failed with status ${response.statusCode}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { formatTelegramAlert } from "./telegram-format.js";
|
|
2
|
+
import { sendTelegramMessage, } from "./telegram-http.js";
|
|
3
|
+
const validateValue = (value, field) => {
|
|
4
|
+
if (typeof value !== "string" ||
|
|
5
|
+
value.trim().length === 0 ||
|
|
6
|
+
value.length > 200 ||
|
|
7
|
+
/[\s/?#]/.test(value)) {
|
|
8
|
+
throw new TypeError(`${field} is invalid`);
|
|
9
|
+
}
|
|
10
|
+
return value.trim();
|
|
11
|
+
};
|
|
12
|
+
export function createTelegramNotifier(options, request) {
|
|
13
|
+
const botToken = validateValue(options.botToken, "botToken");
|
|
14
|
+
const chatId = validateValue(options.chatId, "chatId");
|
|
15
|
+
const sendOptions = {
|
|
16
|
+
botToken,
|
|
17
|
+
chatId,
|
|
18
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
name: "telegram",
|
|
22
|
+
async send(alert) {
|
|
23
|
+
const message = { ...sendOptions, text: formatTelegramAlert(alert) };
|
|
24
|
+
if (request === undefined) {
|
|
25
|
+
await sendTelegramMessage(message);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
await sendTelegramMessage(message, request);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function telegramNotifier(options) {
|
|
33
|
+
return createTelegramNotifier(options);
|
|
34
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { WotchiConfig, WotchiNotifier } from "./types.js";
|
|
2
|
+
import { WotchiConfigurationError } from "./errors.js";
|
|
3
|
+
export { WotchiConfigurationError };
|
|
4
|
+
export interface NormalizedWotchiConfig {
|
|
5
|
+
readonly service: string;
|
|
6
|
+
readonly environment: string;
|
|
7
|
+
readonly release?: string;
|
|
8
|
+
readonly enabled: boolean;
|
|
9
|
+
readonly grouping: {
|
|
10
|
+
readonly windowMs: number;
|
|
11
|
+
readonly alertThreshold: number;
|
|
12
|
+
readonly cooldownMs: number;
|
|
13
|
+
readonly maxGroups: number;
|
|
14
|
+
readonly maxEventsPerWindow: number;
|
|
15
|
+
};
|
|
16
|
+
readonly queue: {
|
|
17
|
+
readonly maxPendingAlerts: number;
|
|
18
|
+
readonly concurrency: 1;
|
|
19
|
+
};
|
|
20
|
+
readonly privacy: {
|
|
21
|
+
readonly redactKeys: readonly string[];
|
|
22
|
+
readonly maxDepth: number;
|
|
23
|
+
readonly maxKeys: number;
|
|
24
|
+
readonly maxStringLength: number;
|
|
25
|
+
readonly maxStackLength: number;
|
|
26
|
+
};
|
|
27
|
+
readonly notifiers: readonly WotchiNotifier[];
|
|
28
|
+
}
|
|
29
|
+
export declare function validateConfig(config: WotchiConfig): Readonly<NormalizedWotchiConfig>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WotchiDiagnostics } from "./types.js";
|
|
2
|
+
export interface DiagnosticsState {
|
|
3
|
+
capturedEvents: number;
|
|
4
|
+
captureFailures: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function createDiagnosticsState(): DiagnosticsState;
|
|
7
|
+
export declare function snapshotDiagnostics(state: DiagnosticsState, values: Omit<WotchiDiagnostics, "capturedEvents" | "captureFailures">): Readonly<WotchiDiagnostics>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IncidentGroup, IncidentSeverity, SafeErrorEvent } from "./types.js";
|
|
2
|
+
export interface GroupStoreOptions {
|
|
3
|
+
maxGroups: number;
|
|
4
|
+
maxEventsPerWindow: number;
|
|
5
|
+
windowMs: number;
|
|
6
|
+
now?: () => number;
|
|
7
|
+
}
|
|
8
|
+
export interface GroupStore {
|
|
9
|
+
record(fingerprint: string, event: SafeErrorEvent, severity?: IncidentSeverity): IncidentGroup;
|
|
10
|
+
get(fingerprint: string): IncidentGroup | undefined;
|
|
11
|
+
markAlerted(fingerprint: string, timestamp?: number): IncidentGroup | undefined;
|
|
12
|
+
size(): number;
|
|
13
|
+
groupsEvicted(): number;
|
|
14
|
+
}
|
|
15
|
+
export declare function createGroupStore(options: GroupStoreOptions): GroupStore;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IncidentGroup, IncidentSeverity } from "./types.js";
|
|
2
|
+
export type IncidentEventKind = "error" | "manual" | "process-monitor";
|
|
3
|
+
export interface IncidentPolicyInput {
|
|
4
|
+
group: IncidentGroup;
|
|
5
|
+
now: number;
|
|
6
|
+
alertThreshold: number;
|
|
7
|
+
cooldownMs: number;
|
|
8
|
+
eventKind?: IncidentEventKind;
|
|
9
|
+
manualSeverity?: IncidentSeverity;
|
|
10
|
+
}
|
|
11
|
+
export interface IncidentPolicyDecision {
|
|
12
|
+
shouldAlert: boolean;
|
|
13
|
+
severity: IncidentSeverity;
|
|
14
|
+
}
|
|
15
|
+
export declare function evaluateIncidentPolicy(input: IncidentPolicyInput): IncidentPolicyDecision;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface NormalizationLimits {
|
|
2
|
+
maxDepth: number;
|
|
3
|
+
maxKeys: number;
|
|
4
|
+
maxStringLength: number;
|
|
5
|
+
maxStackLength: number;
|
|
6
|
+
}
|
|
7
|
+
export type SafeNormalizedValue = null | boolean | number | string | SafeNormalizedValue[] | {
|
|
8
|
+
[key: string]: SafeNormalizedValue;
|
|
9
|
+
};
|
|
10
|
+
export interface NormalizedError {
|
|
11
|
+
name: string;
|
|
12
|
+
message: string;
|
|
13
|
+
stack?: string;
|
|
14
|
+
code?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function normalizeUnknown(value: unknown, limits?: Partial<NormalizationLimits>): SafeNormalizedValue;
|
|
17
|
+
export declare function normalizeError(error: unknown, limits?: Partial<NormalizationLimits>): NormalizedError;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IncidentAlert, WotchiNotifier } from "./types.js";
|
|
2
|
+
export interface NotificationQueueOptions {
|
|
3
|
+
maxPendingAlerts: number;
|
|
4
|
+
concurrency: 1;
|
|
5
|
+
onNotifierError?: (error: unknown, notifier: WotchiNotifier) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface NotificationQueue {
|
|
8
|
+
enqueue(alert: IncidentAlert, notifiers: readonly WotchiNotifier[]): boolean;
|
|
9
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
10
|
+
pending(): number;
|
|
11
|
+
alertsQueued(): number;
|
|
12
|
+
alertsDropped(): number;
|
|
13
|
+
alertsSent(): number;
|
|
14
|
+
notifierFailures(): number;
|
|
15
|
+
}
|
|
16
|
+
export declare function createNotificationQueue(options: NotificationQueueOptions): NotificationQueue;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NormalizedError, SafeNormalizedValue } from "./normalize.js";
|
|
2
|
+
export declare const REDACTED = "[REDACTED]";
|
|
3
|
+
export interface RedactionOptions {
|
|
4
|
+
redactKeys?: readonly string[];
|
|
5
|
+
maxDepth?: number;
|
|
6
|
+
maxKeys?: number;
|
|
7
|
+
maxStringLength?: number;
|
|
8
|
+
maxStackLength?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function redactValue(value: unknown, options?: RedactionOptions): SafeNormalizedValue;
|
|
11
|
+
export declare function redactError(error: NormalizedError, options?: RedactionOptions): NormalizedError;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RollingWindowOptions {
|
|
2
|
+
maxEvents: number;
|
|
3
|
+
windowMs: number;
|
|
4
|
+
now?: () => number;
|
|
5
|
+
}
|
|
6
|
+
export interface RollingWindow {
|
|
7
|
+
add(timestamp?: number): void;
|
|
8
|
+
count(timestamp?: number): number;
|
|
9
|
+
size(timestamp?: number): number;
|
|
10
|
+
timestamps(timestamp?: number): readonly number[];
|
|
11
|
+
}
|
|
12
|
+
export declare function createRollingWindow(options: RollingWindowOptions): RollingWindow;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export type IncidentSeverity = "low" | "medium" | "high" | "critical";
|
|
2
|
+
export type WotchiEventKind = "error" | "manual" | "process-monitor";
|
|
3
|
+
export interface WotchiRequestContext extends Record<string, unknown> {
|
|
4
|
+
method?: string;
|
|
5
|
+
route?: string;
|
|
6
|
+
statusCode?: number;
|
|
7
|
+
requestId?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface WotchiEventInput {
|
|
10
|
+
level: "error";
|
|
11
|
+
kind?: WotchiEventKind;
|
|
12
|
+
message: string;
|
|
13
|
+
error?: unknown;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
context?: Record<string, unknown>;
|
|
16
|
+
request?: WotchiRequestContext;
|
|
17
|
+
}
|
|
18
|
+
export interface SafeErrorEvent {
|
|
19
|
+
id: string;
|
|
20
|
+
timestamp: string;
|
|
21
|
+
service: string;
|
|
22
|
+
environment: string;
|
|
23
|
+
release?: string;
|
|
24
|
+
error: {
|
|
25
|
+
name: string;
|
|
26
|
+
message: string;
|
|
27
|
+
stack?: string;
|
|
28
|
+
code?: string;
|
|
29
|
+
};
|
|
30
|
+
request?: WotchiRequestContext;
|
|
31
|
+
context?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
export interface IncidentGroup {
|
|
34
|
+
fingerprint: string;
|
|
35
|
+
firstSeenAt: string;
|
|
36
|
+
lastSeenAt: string;
|
|
37
|
+
totalCount: number;
|
|
38
|
+
windowCount: number;
|
|
39
|
+
sample: SafeErrorEvent;
|
|
40
|
+
lastAlertedAt?: string;
|
|
41
|
+
severity: IncidentSeverity;
|
|
42
|
+
}
|
|
43
|
+
export interface IncidentAlert {
|
|
44
|
+
id: string;
|
|
45
|
+
fingerprint: string;
|
|
46
|
+
title: string;
|
|
47
|
+
severity: IncidentSeverity;
|
|
48
|
+
summary: string;
|
|
49
|
+
suggestedActions: string[];
|
|
50
|
+
firstSeenAt: string;
|
|
51
|
+
lastSeenAt: string;
|
|
52
|
+
occurrences: number;
|
|
53
|
+
service: string;
|
|
54
|
+
environment: string;
|
|
55
|
+
}
|
|
56
|
+
export interface WotchiNotifier {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
send(alert: IncidentAlert): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
export interface ConsoleNotifierOptions {
|
|
61
|
+
write?: (line: string) => void;
|
|
62
|
+
}
|
|
63
|
+
export interface TelegramNotifierOptions {
|
|
64
|
+
botToken: string;
|
|
65
|
+
chatId: string;
|
|
66
|
+
timeoutMs?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface WotchiConfig {
|
|
69
|
+
service: string;
|
|
70
|
+
environment: string;
|
|
71
|
+
release?: string;
|
|
72
|
+
enabled?: boolean;
|
|
73
|
+
grouping?: {
|
|
74
|
+
windowMs?: number;
|
|
75
|
+
alertThreshold?: number;
|
|
76
|
+
cooldownMs?: number;
|
|
77
|
+
maxGroups?: number;
|
|
78
|
+
maxEventsPerWindow?: number;
|
|
79
|
+
};
|
|
80
|
+
queue?: {
|
|
81
|
+
maxPendingAlerts?: number;
|
|
82
|
+
concurrency?: 1;
|
|
83
|
+
};
|
|
84
|
+
privacy?: {
|
|
85
|
+
redactKeys?: string[];
|
|
86
|
+
maxDepth?: number;
|
|
87
|
+
maxKeys?: number;
|
|
88
|
+
maxStringLength?: number;
|
|
89
|
+
maxStackLength?: number;
|
|
90
|
+
};
|
|
91
|
+
notifiers: WotchiNotifier[];
|
|
92
|
+
}
|
|
93
|
+
export interface WotchiDiagnostics {
|
|
94
|
+
capturedEvents: number;
|
|
95
|
+
captureFailures: number;
|
|
96
|
+
groupsEvicted: number;
|
|
97
|
+
alertsQueued: number;
|
|
98
|
+
alertsDropped: number;
|
|
99
|
+
alertsSent: number;
|
|
100
|
+
notifierFailures: number;
|
|
101
|
+
activeGroups: number;
|
|
102
|
+
pendingAlerts: number;
|
|
103
|
+
}
|
|
104
|
+
export interface WotchiClient {
|
|
105
|
+
captureException(error: unknown, context?: Record<string, unknown>): void;
|
|
106
|
+
captureEvent(event: WotchiEventInput): void;
|
|
107
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
108
|
+
getDiagnostics(): Readonly<WotchiDiagnostics>;
|
|
109
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createWotchi } from "./core/client.js";
|
|
2
|
+
import { registerWotchiProcessMonitor } from "./core/process-monitor.js";
|
|
3
|
+
import { consoleNotifier } from "./notifiers/console.js";
|
|
4
|
+
import { telegramNotifier } from "./notifiers/telegram.js";
|
|
5
|
+
export { WotchiConfigurationError } from "./core/errors.js";
|
|
6
|
+
export type { ProcessMonitorHandle } from "./core/process-monitor.js";
|
|
7
|
+
export type { NormalizedWotchiConfig } from "./core/config.js";
|
|
8
|
+
export type { ConsoleNotifierOptions, IncidentAlert, IncidentGroup, IncidentSeverity, SafeErrorEvent, TelegramNotifierOptions, WotchiClient, WotchiConfig, WotchiDiagnostics, WotchiEventInput, WotchiEventKind, WotchiNotifier, WotchiRequestContext, } from "./core/types.js";
|
|
9
|
+
export { consoleNotifier, createWotchi, registerWotchiProcessMonitor, telegramNotifier };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ErrorRequestHandler } from "express";
|
|
2
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
3
|
+
import { type ExpressWotchiOptions } from "./request-context.js";
|
|
4
|
+
export declare function createExpressErrorHandler(client: WotchiClient, options?: ExpressWotchiOptions): ErrorRequestHandler;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
2
|
+
import { createExpressErrorHandler } from "./error-handler.js";
|
|
3
|
+
import type { ExpressWotchiOptions } from "./request-context.js";
|
|
4
|
+
export { consoleNotifier, createWotchi, telegramNotifier } from "../../index.js";
|
|
5
|
+
export type { ConsoleNotifierOptions, IncidentAlert, IncidentGroup, IncidentSeverity, SafeErrorEvent, TelegramNotifierOptions, WotchiClient, WotchiConfig, WotchiDiagnostics, WotchiEventInput, WotchiNotifier, WotchiRequestContext, } from "../../index.js";
|
|
6
|
+
export type { ExpressWotchiOptions } from "./request-context.js";
|
|
7
|
+
export declare function wotchiErrorHandler(client: WotchiClient, options?: ExpressWotchiOptions): ReturnType<typeof createExpressErrorHandler>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Request, Response } from "express";
|
|
2
|
+
import { type RequestContextOptions } from "../request-context.js";
|
|
3
|
+
import type { WotchiRequestContext } from "../../core/types.js";
|
|
4
|
+
export type ExpressWotchiOptions = RequestContextOptions;
|
|
5
|
+
export declare function getExpressRequestContext(request: Request, response: Response, options?: ExpressWotchiOptions): WotchiRequestContext | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ArgumentsHost, type HttpServer } from "@nestjs/common";
|
|
2
|
+
import { BaseExceptionFilter } from "@nestjs/core";
|
|
3
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
4
|
+
import { type NestWotchiOptions } from "./request-context.js";
|
|
5
|
+
export declare class WotchiNestExceptionFilter extends BaseExceptionFilter<unknown> {
|
|
6
|
+
private readonly client;
|
|
7
|
+
private readonly options?;
|
|
8
|
+
constructor(client: WotchiClient, applicationRef: HttpServer, options?: NestWotchiOptions | undefined);
|
|
9
|
+
catch(exception: unknown, host: ArgumentsHost): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { consoleNotifier, createWotchi, telegramNotifier } from "../../index.js";
|
|
2
|
+
export { registerWotchiNest } from "./register.js";
|
|
3
|
+
export type { ConsoleNotifierOptions, IncidentAlert, IncidentGroup, IncidentSeverity, SafeErrorEvent, TelegramNotifierOptions, WotchiClient, WotchiConfig, WotchiDiagnostics, WotchiEventInput, WotchiNotifier, WotchiRequestContext, } from "../../index.js";
|
|
4
|
+
export type { NestWotchiApplication, NestWotchiOptions } from "./register.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HttpAdapterHost } from "@nestjs/core";
|
|
2
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
3
|
+
import type { NestWotchiOptions } from "./request-context.js";
|
|
4
|
+
export type { NestWotchiOptions } from "./request-context.js";
|
|
5
|
+
export interface NestWotchiApplication {
|
|
6
|
+
get(token: typeof HttpAdapterHost): InstanceType<typeof HttpAdapterHost>;
|
|
7
|
+
getHttpAdapter?: () => unknown;
|
|
8
|
+
useGlobalFilters(...filters: unknown[]): unknown;
|
|
9
|
+
}
|
|
10
|
+
export declare function registerWotchiNest(app: unknown, client: WotchiClient, options?: NestWotchiOptions): void;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ArgumentsHost } from "@nestjs/common";
|
|
2
|
+
import { type RequestContextOptions } from "../request-context.js";
|
|
3
|
+
import type { WotchiRequestContext } from "../../core/types.js";
|
|
4
|
+
export type NestWotchiOptions = RequestContextOptions;
|
|
5
|
+
export declare function getNestRequestContext(host: ArgumentsHost, exception: unknown, options?: NestWotchiOptions): WotchiRequestContext | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WotchiRequestContext } from "../core/types.js";
|
|
2
|
+
export interface RequestContextOptions {
|
|
3
|
+
requestIdProperty?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function normalizeRoutePath(value: unknown): string | undefined;
|
|
6
|
+
export interface RequestContextInput {
|
|
7
|
+
request?: unknown;
|
|
8
|
+
method?: unknown;
|
|
9
|
+
route?: unknown;
|
|
10
|
+
statusCode?: unknown;
|
|
11
|
+
options?: RequestContextOptions;
|
|
12
|
+
}
|
|
13
|
+
export declare function buildRequestContext(input: RequestContextInput): WotchiRequestContext | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RequestOptions } from "node:https";
|
|
2
|
+
export interface TelegramHttpRequestOptions extends RequestOptions {
|
|
3
|
+
protocol: "https:";
|
|
4
|
+
hostname: "api.telegram.org";
|
|
5
|
+
method: "POST";
|
|
6
|
+
path: string;
|
|
7
|
+
headers: Record<string, string | number>;
|
|
8
|
+
}
|
|
9
|
+
export interface TelegramHttpResponse {
|
|
10
|
+
statusCode: number;
|
|
11
|
+
headers: Record<string, string | string[] | undefined>;
|
|
12
|
+
body: string;
|
|
13
|
+
}
|
|
14
|
+
export type TelegramRequestFunction = (options: TelegramHttpRequestOptions, body: string, timeoutMs: number) => Promise<TelegramHttpResponse>;
|
|
15
|
+
export interface TelegramSendOptions {
|
|
16
|
+
botToken: string;
|
|
17
|
+
chatId: string;
|
|
18
|
+
text: string;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function sendTelegramMessage(options: TelegramSendOptions, request?: TelegramRequestFunction): Promise<void>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TelegramNotifierOptions, WotchiNotifier } from "../core/types.js";
|
|
2
|
+
import { type TelegramRequestFunction } from "./telegram-http.js";
|
|
3
|
+
export declare function createTelegramNotifier(options: TelegramNotifierOptions, request?: TelegramRequestFunction): WotchiNotifier;
|
|
4
|
+
export declare function telegramNotifier(options: TelegramNotifierOptions): WotchiNotifier;
|