@futurewindai/wotchi 0.1.0-beta.1 → 0.1.0-beta.2
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 +110 -61
- package/SECURITY.md +2 -2
- package/dist/cjs/core/client.js +13 -3
- package/dist/cjs/core/redact.js +31 -2
- package/dist/cjs/integrations/express/error-handler.js +2 -0
- package/dist/cjs/integrations/express/index.js +3 -1
- package/dist/cjs/integrations/express/state.js +30 -0
- package/dist/cjs/integrations/express/status-observer.js +80 -0
- package/dist/cjs/integrations/nest/index.js +2 -1
- package/dist/cjs/integrations/nest/register.js +9 -0
- package/dist/cjs/notifiers/console.js +32 -12
- package/dist/esm/core/client.js +13 -3
- package/dist/esm/core/redact.js +31 -2
- package/dist/esm/integrations/express/error-handler.js +2 -0
- package/dist/esm/integrations/express/index.js +1 -0
- package/dist/esm/integrations/express/state.js +26 -0
- package/dist/esm/integrations/express/status-observer.js +77 -0
- package/dist/esm/integrations/nest/index.js +1 -1
- package/dist/esm/integrations/nest/register.js +8 -0
- package/dist/esm/notifiers/console.js +32 -12
- package/dist/types/core/types.d.ts +2 -0
- package/dist/types/integrations/express/index.d.ts +2 -0
- package/dist/types/integrations/express/state.d.ts +2 -0
- package/dist/types/integrations/express/status-observer.d.ts +11 -0
- package/dist/types/integrations/nest/index.d.ts +2 -1
- package/dist/types/integrations/nest/register.d.ts +3 -0
- package/dist/types/notifiers/console.d.ts +1 -1
- package/dist/types-cjs/core/client.d.cts +2 -0
- package/dist/types-cjs/core/config.d.cts +29 -0
- package/dist/types-cjs/core/diagnostics.d.cts +7 -0
- package/dist/types-cjs/core/errors.d.cts +3 -0
- package/dist/types-cjs/core/fingerprint.d.cts +3 -0
- package/dist/types-cjs/core/group-store.d.cts +15 -0
- package/dist/types-cjs/core/incident-builder.d.cts +3 -0
- package/dist/types-cjs/core/incident-policy.d.cts +15 -0
- package/dist/types-cjs/core/normalize.d.cts +17 -0
- package/dist/types-cjs/core/notification-queue.d.cts +16 -0
- package/dist/types-cjs/core/process-monitor.d.cts +5 -0
- package/dist/types-cjs/core/redact.d.cts +11 -0
- package/dist/types-cjs/core/rolling-window.d.cts +12 -0
- package/dist/types-cjs/core/stack-frame.d.cts +2 -0
- package/dist/types-cjs/core/types.d.cts +111 -0
- package/dist/types-cjs/index.d.cts +9 -0
- package/dist/types-cjs/integrations/express/error-handler.d.cts +4 -0
- package/dist/types-cjs/integrations/express/index.d.cts +9 -0
- package/dist/types-cjs/integrations/express/request-context.d.cts +5 -0
- package/dist/types-cjs/integrations/express/state.d.cts +2 -0
- package/dist/types-cjs/integrations/express/status-observer.d.cts +11 -0
- package/dist/types-cjs/integrations/nest/exception-filter.d.cts +10 -0
- package/dist/types-cjs/integrations/nest/index.d.cts +5 -0
- package/dist/types-cjs/integrations/nest/register.d.cts +13 -0
- package/dist/types-cjs/integrations/nest/request-context.d.cts +5 -0
- package/dist/types-cjs/integrations/request-context.d.cts +13 -0
- package/dist/types-cjs/notifiers/console.d.cts +3 -0
- package/dist/types-cjs/notifiers/telegram-format.d.cts +2 -0
- package/dist/types-cjs/notifiers/telegram-http.d.cts +21 -0
- package/dist/types-cjs/notifiers/telegram.d.cts +4 -0
- package/package.json +26 -6
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createExpressErrorHandler } from "./error-handler.js";
|
|
2
2
|
export { consoleNotifier, createWotchi, telegramNotifier } from "../../index.js";
|
|
3
|
+
export { wotchiStatusObserver } from "./status-observer.js";
|
|
3
4
|
export function wotchiErrorHandler(client, options) {
|
|
4
5
|
return createExpressErrorHandler(client, options);
|
|
5
6
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const WOTCHI_ERROR_CAPTURED = Symbol("wotchi.errorCaptured");
|
|
2
|
+
const asMarkedRequest = (request) => typeof request === "object" && request !== null ? request : undefined;
|
|
3
|
+
export function markExpressErrorCaptured(request) {
|
|
4
|
+
const markedRequest = asMarkedRequest(request);
|
|
5
|
+
if (markedRequest === undefined) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
markedRequest[WOTCHI_ERROR_CAPTURED] = true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// A request object can be frozen by host middleware; the observer remains best-effort.
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function wasExpressErrorCaptured(request) {
|
|
16
|
+
const markedRequest = asMarkedRequest(request);
|
|
17
|
+
if (markedRequest === undefined) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return markedRequest[WOTCHI_ERROR_CAPTURED] === true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getExpressRequestContext } from "./request-context.js";
|
|
2
|
+
import { wasExpressErrorCaptured } from "./state.js";
|
|
3
|
+
const DEFAULT_STATUS_CODES = [401, 403, 429];
|
|
4
|
+
const DEFAULT_STATUS_CLASSES = ["5xx"];
|
|
5
|
+
const MAX_STATUS_CODES = 100;
|
|
6
|
+
const normalizeStatuses = (values, defaults) => {
|
|
7
|
+
const selected = values ?? defaults;
|
|
8
|
+
if (selected.length > MAX_STATUS_CODES) {
|
|
9
|
+
throw new TypeError("statusCodes must contain at most 100 values");
|
|
10
|
+
}
|
|
11
|
+
for (const status of selected) {
|
|
12
|
+
if (!Number.isSafeInteger(status) || status < 100 || status > 599) {
|
|
13
|
+
throw new TypeError("statusCodes must contain HTTP status codes from 100 through 599");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return new Set(selected);
|
|
17
|
+
};
|
|
18
|
+
const normalizeStatusClasses = (values) => {
|
|
19
|
+
const selected = values ?? DEFAULT_STATUS_CLASSES;
|
|
20
|
+
for (const statusClass of selected) {
|
|
21
|
+
if (statusClass !== "4xx" && statusClass !== "5xx") {
|
|
22
|
+
throw new TypeError('statusClasses must contain only "4xx" or "5xx"');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return new Set(selected);
|
|
26
|
+
};
|
|
27
|
+
const normalizeAlertThreshold = (value) => {
|
|
28
|
+
if (value === undefined) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > 1_000_000) {
|
|
32
|
+
throw new TypeError("alertThreshold must be a positive integer no greater than 1000000");
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
};
|
|
36
|
+
const normalizeOptions = (options) => {
|
|
37
|
+
const alertThreshold = normalizeAlertThreshold(options?.alertThreshold);
|
|
38
|
+
return {
|
|
39
|
+
...(options?.requestIdProperty === undefined
|
|
40
|
+
? {}
|
|
41
|
+
: { requestIdProperty: options.requestIdProperty }),
|
|
42
|
+
statusCodes: normalizeStatuses(options?.statusCodes, DEFAULT_STATUS_CODES),
|
|
43
|
+
statusClasses: normalizeStatusClasses(options?.statusClasses),
|
|
44
|
+
ignoreStatusCodes: normalizeStatuses(options?.ignoreStatusCodes, []),
|
|
45
|
+
...(alertThreshold === undefined ? {} : { alertThreshold }),
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const isObservedStatus = (statusCode, options) => !options.ignoreStatusCodes.has(statusCode) &&
|
|
49
|
+
(options.statusCodes.has(statusCode) ||
|
|
50
|
+
(statusCode >= 400 && statusCode < 500 && options.statusClasses.has("4xx")) ||
|
|
51
|
+
(statusCode >= 500 && statusCode < 600 && options.statusClasses.has("5xx")));
|
|
52
|
+
export function wotchiStatusObserver(client, options) {
|
|
53
|
+
const normalizedOptions = normalizeOptions(options);
|
|
54
|
+
return (request, response, next) => {
|
|
55
|
+
response.once("finish", () => {
|
|
56
|
+
const statusCode = response.statusCode;
|
|
57
|
+
if (wasExpressErrorCaptured(request) || !isObservedStatus(statusCode, normalizedOptions)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const requestContext = getExpressRequestContext(request, response, normalizedOptions);
|
|
62
|
+
client.captureEvent({
|
|
63
|
+
level: "error",
|
|
64
|
+
message: `HTTP ${statusCode} response`,
|
|
65
|
+
...(normalizedOptions.alertThreshold === undefined
|
|
66
|
+
? {}
|
|
67
|
+
: { alertThreshold: normalizedOptions.alertThreshold }),
|
|
68
|
+
...(requestContext === undefined ? {} : { request: requestContext }),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Status observation must never change the host response lifecycle.
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
next();
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { consoleNotifier, createWotchi, telegramNotifier } from "../../index.js";
|
|
2
|
-
export { registerWotchiNest } from "./register.js";
|
|
2
|
+
export { registerWotchiNest, registerWotchiNestStatusObserver } from "./register.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HttpAdapterHost } from "@nestjs/core";
|
|
2
2
|
import { WotchiNestExceptionFilter } from "./exception-filter.js";
|
|
3
|
+
import { wotchiStatusObserver } from "../express/status-observer.js";
|
|
3
4
|
export function registerWotchiNest(app, client, options) {
|
|
4
5
|
const application = app;
|
|
5
6
|
const directAdapter = application.getHttpAdapter?.();
|
|
@@ -8,3 +9,10 @@ export function registerWotchiNest(app, client, options) {
|
|
|
8
9
|
: directAdapter;
|
|
9
10
|
application.useGlobalFilters(new WotchiNestExceptionFilter(client, httpAdapter, options));
|
|
10
11
|
}
|
|
12
|
+
export function registerWotchiNestStatusObserver(app, client, options) {
|
|
13
|
+
const application = app;
|
|
14
|
+
if (typeof application.use !== "function") {
|
|
15
|
+
throw new TypeError("Nest application does not expose use(); an Express-based adapter is required");
|
|
16
|
+
}
|
|
17
|
+
application.use(wotchiStatusObserver(client, options));
|
|
18
|
+
}
|
|
@@ -1,19 +1,39 @@
|
|
|
1
|
+
import { redactValue } from "../core/redact.js";
|
|
1
2
|
const MAX_FIELD_LENGTH = 1_000;
|
|
2
3
|
const MAX_ACTIONS = 5;
|
|
3
4
|
const limit = (value, maxLength = MAX_FIELD_LENGTH) => value.slice(0, maxLength);
|
|
4
|
-
|
|
5
|
-
const
|
|
5
|
+
const safeText = (value, maxLength = MAX_FIELD_LENGTH) => {
|
|
6
|
+
const redacted = redactValue(value, { maxStringLength: maxLength });
|
|
7
|
+
return typeof redacted === "string" ? limit(redacted, maxLength) : "[unreadable value]";
|
|
8
|
+
};
|
|
9
|
+
const boundedAlert = (alert) => ({
|
|
10
|
+
title: safeText(alert.title),
|
|
11
|
+
fingerprint: safeText(alert.fingerprint),
|
|
12
|
+
severity: alert.severity,
|
|
13
|
+
summary: safeText(alert.summary),
|
|
14
|
+
suggestedActions: alert.suggestedActions
|
|
6
15
|
.slice(0, MAX_ACTIONS)
|
|
7
|
-
.map((action) =>
|
|
8
|
-
|
|
16
|
+
.map((action) => safeText(action, 300)),
|
|
17
|
+
firstSeenAt: safeText(alert.firstSeenAt, 100),
|
|
18
|
+
lastSeenAt: safeText(alert.lastSeenAt, 100),
|
|
19
|
+
occurrences: Number.isSafeInteger(alert.occurrences) && alert.occurrences >= 0 ? alert.occurrences : 0,
|
|
20
|
+
service: safeText(alert.service, 300),
|
|
21
|
+
environment: safeText(alert.environment, 300),
|
|
22
|
+
});
|
|
23
|
+
export function formatConsoleAlert(alert, format = "text") {
|
|
24
|
+
const bounded = boundedAlert(alert);
|
|
25
|
+
if (format === "json") {
|
|
26
|
+
return JSON.stringify(bounded);
|
|
27
|
+
}
|
|
28
|
+
const actions = bounded.suggestedActions.map((action) => `- ${action}`).join("\n");
|
|
9
29
|
return [
|
|
10
|
-
|
|
11
|
-
`Service: ${
|
|
12
|
-
`Environment: ${
|
|
13
|
-
`Summary: ${
|
|
14
|
-
`Occurrences: ${
|
|
15
|
-
`First seen: ${
|
|
16
|
-
`Last seen: ${
|
|
30
|
+
bounded.title,
|
|
31
|
+
`Service: ${bounded.service}`,
|
|
32
|
+
`Environment: ${bounded.environment}`,
|
|
33
|
+
`Summary: ${bounded.summary}`,
|
|
34
|
+
`Occurrences: ${bounded.occurrences}`,
|
|
35
|
+
`First seen: ${bounded.firstSeenAt}`,
|
|
36
|
+
`Last seen: ${bounded.lastSeenAt}`,
|
|
17
37
|
"Suggested checks:",
|
|
18
38
|
actions,
|
|
19
39
|
].join("\n");
|
|
@@ -23,7 +43,7 @@ export function consoleNotifier(options) {
|
|
|
23
43
|
return {
|
|
24
44
|
name: "console",
|
|
25
45
|
async send(alert) {
|
|
26
|
-
write(formatConsoleAlert(alert));
|
|
46
|
+
write(formatConsoleAlert(alert, options?.format ?? "text"));
|
|
27
47
|
},
|
|
28
48
|
};
|
|
29
49
|
}
|
|
@@ -11,6 +11,7 @@ export interface WotchiEventInput {
|
|
|
11
11
|
kind?: WotchiEventKind;
|
|
12
12
|
message: string;
|
|
13
13
|
error?: unknown;
|
|
14
|
+
alertThreshold?: number;
|
|
14
15
|
metadata?: Record<string, unknown>;
|
|
15
16
|
context?: Record<string, unknown>;
|
|
16
17
|
request?: WotchiRequestContext;
|
|
@@ -59,6 +60,7 @@ export interface WotchiNotifier {
|
|
|
59
60
|
}
|
|
60
61
|
export interface ConsoleNotifierOptions {
|
|
61
62
|
write?: (line: string) => void;
|
|
63
|
+
format?: "text" | "json";
|
|
62
64
|
}
|
|
63
65
|
export interface TelegramNotifierOptions {
|
|
64
66
|
botToken: string;
|
|
@@ -4,4 +4,6 @@ import type { ExpressWotchiOptions } from "./request-context.js";
|
|
|
4
4
|
export { consoleNotifier, createWotchi, telegramNotifier } from "../../index.js";
|
|
5
5
|
export type { ConsoleNotifierOptions, IncidentAlert, IncidentGroup, IncidentSeverity, SafeErrorEvent, TelegramNotifierOptions, WotchiClient, WotchiConfig, WotchiDiagnostics, WotchiEventInput, WotchiNotifier, WotchiRequestContext, } from "../../index.js";
|
|
6
6
|
export type { ExpressWotchiOptions } from "./request-context.js";
|
|
7
|
+
export type { WotchiStatusClass, WotchiStatusObserverOptions } from "./status-observer.js";
|
|
8
|
+
export { wotchiStatusObserver } from "./status-observer.js";
|
|
7
9
|
export declare function wotchiErrorHandler(client: WotchiClient, options?: ExpressWotchiOptions): ReturnType<typeof createExpressErrorHandler>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RequestHandler } from "express";
|
|
2
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
3
|
+
import type { RequestContextOptions } from "../request-context.js";
|
|
4
|
+
export type WotchiStatusClass = "4xx" | "5xx";
|
|
5
|
+
export interface WotchiStatusObserverOptions extends RequestContextOptions {
|
|
6
|
+
statusCodes?: readonly number[];
|
|
7
|
+
statusClasses?: readonly WotchiStatusClass[];
|
|
8
|
+
ignoreStatusCodes?: readonly number[];
|
|
9
|
+
alertThreshold?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function wotchiStatusObserver(client: WotchiClient, options?: WotchiStatusObserverOptions): RequestHandler;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { consoleNotifier, createWotchi, telegramNotifier } from "../../index.js";
|
|
2
|
-
export { registerWotchiNest } from "./register.js";
|
|
2
|
+
export { registerWotchiNest, registerWotchiNestStatusObserver } from "./register.js";
|
|
3
3
|
export type { ConsoleNotifierOptions, IncidentAlert, IncidentGroup, IncidentSeverity, SafeErrorEvent, TelegramNotifierOptions, WotchiClient, WotchiConfig, WotchiDiagnostics, WotchiEventInput, WotchiNotifier, WotchiRequestContext, } from "../../index.js";
|
|
4
4
|
export type { NestWotchiApplication, NestWotchiOptions } from "./register.js";
|
|
5
|
+
export type { WotchiStatusClass, WotchiStatusObserverOptions } from "../express/status-observer.js";
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { HttpAdapterHost } from "@nestjs/core";
|
|
2
2
|
import type { WotchiClient } from "../../core/types.js";
|
|
3
3
|
import type { NestWotchiOptions } from "./request-context.js";
|
|
4
|
+
import type { WotchiStatusObserverOptions } from "../express/status-observer.js";
|
|
4
5
|
export type { NestWotchiOptions } from "./request-context.js";
|
|
5
6
|
export interface NestWotchiApplication {
|
|
6
7
|
get(token: typeof HttpAdapterHost): InstanceType<typeof HttpAdapterHost>;
|
|
7
8
|
getHttpAdapter?: () => unknown;
|
|
9
|
+
use?: (...middleware: unknown[]) => unknown;
|
|
8
10
|
useGlobalFilters(...filters: unknown[]): unknown;
|
|
9
11
|
}
|
|
10
12
|
export declare function registerWotchiNest(app: unknown, client: WotchiClient, options?: NestWotchiOptions): void;
|
|
13
|
+
export declare function registerWotchiNestStatusObserver(app: unknown, client: WotchiClient, options?: WotchiStatusObserverOptions): void;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ConsoleNotifierOptions, IncidentAlert, WotchiNotifier } from "../core/types.js";
|
|
2
|
-
export declare function formatConsoleAlert(alert: IncidentAlert): string;
|
|
2
|
+
export declare function formatConsoleAlert(alert: IncidentAlert, format?: "text" | "json"): string;
|
|
3
3
|
export declare function consoleNotifier(options?: ConsoleNotifierOptions): WotchiNotifier;
|
|
@@ -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,111 @@
|
|
|
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
|
+
alertThreshold?: number;
|
|
15
|
+
metadata?: Record<string, unknown>;
|
|
16
|
+
context?: Record<string, unknown>;
|
|
17
|
+
request?: WotchiRequestContext;
|
|
18
|
+
}
|
|
19
|
+
export interface SafeErrorEvent {
|
|
20
|
+
id: string;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
service: string;
|
|
23
|
+
environment: string;
|
|
24
|
+
release?: string;
|
|
25
|
+
error: {
|
|
26
|
+
name: string;
|
|
27
|
+
message: string;
|
|
28
|
+
stack?: string;
|
|
29
|
+
code?: string;
|
|
30
|
+
};
|
|
31
|
+
request?: WotchiRequestContext;
|
|
32
|
+
context?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
export interface IncidentGroup {
|
|
35
|
+
fingerprint: string;
|
|
36
|
+
firstSeenAt: string;
|
|
37
|
+
lastSeenAt: string;
|
|
38
|
+
totalCount: number;
|
|
39
|
+
windowCount: number;
|
|
40
|
+
sample: SafeErrorEvent;
|
|
41
|
+
lastAlertedAt?: string;
|
|
42
|
+
severity: IncidentSeverity;
|
|
43
|
+
}
|
|
44
|
+
export interface IncidentAlert {
|
|
45
|
+
id: string;
|
|
46
|
+
fingerprint: string;
|
|
47
|
+
title: string;
|
|
48
|
+
severity: IncidentSeverity;
|
|
49
|
+
summary: string;
|
|
50
|
+
suggestedActions: string[];
|
|
51
|
+
firstSeenAt: string;
|
|
52
|
+
lastSeenAt: string;
|
|
53
|
+
occurrences: number;
|
|
54
|
+
service: string;
|
|
55
|
+
environment: string;
|
|
56
|
+
}
|
|
57
|
+
export interface WotchiNotifier {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
send(alert: IncidentAlert): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export interface ConsoleNotifierOptions {
|
|
62
|
+
write?: (line: string) => void;
|
|
63
|
+
format?: "text" | "json";
|
|
64
|
+
}
|
|
65
|
+
export interface TelegramNotifierOptions {
|
|
66
|
+
botToken: string;
|
|
67
|
+
chatId: string;
|
|
68
|
+
timeoutMs?: number;
|
|
69
|
+
}
|
|
70
|
+
export interface WotchiConfig {
|
|
71
|
+
service: string;
|
|
72
|
+
environment: string;
|
|
73
|
+
release?: string;
|
|
74
|
+
enabled?: boolean;
|
|
75
|
+
grouping?: {
|
|
76
|
+
windowMs?: number;
|
|
77
|
+
alertThreshold?: number;
|
|
78
|
+
cooldownMs?: number;
|
|
79
|
+
maxGroups?: number;
|
|
80
|
+
maxEventsPerWindow?: number;
|
|
81
|
+
};
|
|
82
|
+
queue?: {
|
|
83
|
+
maxPendingAlerts?: number;
|
|
84
|
+
concurrency?: 1;
|
|
85
|
+
};
|
|
86
|
+
privacy?: {
|
|
87
|
+
redactKeys?: string[];
|
|
88
|
+
maxDepth?: number;
|
|
89
|
+
maxKeys?: number;
|
|
90
|
+
maxStringLength?: number;
|
|
91
|
+
maxStackLength?: number;
|
|
92
|
+
};
|
|
93
|
+
notifiers: WotchiNotifier[];
|
|
94
|
+
}
|
|
95
|
+
export interface WotchiDiagnostics {
|
|
96
|
+
capturedEvents: number;
|
|
97
|
+
captureFailures: number;
|
|
98
|
+
groupsEvicted: number;
|
|
99
|
+
alertsQueued: number;
|
|
100
|
+
alertsDropped: number;
|
|
101
|
+
alertsSent: number;
|
|
102
|
+
notifierFailures: number;
|
|
103
|
+
activeGroups: number;
|
|
104
|
+
pendingAlerts: number;
|
|
105
|
+
}
|
|
106
|
+
export interface WotchiClient {
|
|
107
|
+
captureException(error: unknown, context?: Record<string, unknown>): void;
|
|
108
|
+
captureEvent(event: WotchiEventInput): void;
|
|
109
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
110
|
+
getDiagnostics(): Readonly<WotchiDiagnostics>;
|
|
111
|
+
}
|
|
@@ -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,9 @@
|
|
|
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 type { WotchiStatusClass, WotchiStatusObserverOptions } from "./status-observer.js";
|
|
8
|
+
export { wotchiStatusObserver } from "./status-observer.js";
|
|
9
|
+
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,11 @@
|
|
|
1
|
+
import type { RequestHandler } from "express";
|
|
2
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
3
|
+
import type { RequestContextOptions } from "../request-context.js";
|
|
4
|
+
export type WotchiStatusClass = "4xx" | "5xx";
|
|
5
|
+
export interface WotchiStatusObserverOptions extends RequestContextOptions {
|
|
6
|
+
statusCodes?: readonly number[];
|
|
7
|
+
statusClasses?: readonly WotchiStatusClass[];
|
|
8
|
+
ignoreStatusCodes?: readonly number[];
|
|
9
|
+
alertThreshold?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function wotchiStatusObserver(client: WotchiClient, options?: WotchiStatusObserverOptions): RequestHandler;
|
|
@@ -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,5 @@
|
|
|
1
|
+
export { consoleNotifier, createWotchi, telegramNotifier } from "../../index.js";
|
|
2
|
+
export { registerWotchiNest, registerWotchiNestStatusObserver } 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";
|
|
5
|
+
export type { WotchiStatusClass, WotchiStatusObserverOptions } from "../express/status-observer.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HttpAdapterHost } from "@nestjs/core";
|
|
2
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
3
|
+
import type { NestWotchiOptions } from "./request-context.js";
|
|
4
|
+
import type { WotchiStatusObserverOptions } from "../express/status-observer.js";
|
|
5
|
+
export type { NestWotchiOptions } from "./request-context.js";
|
|
6
|
+
export interface NestWotchiApplication {
|
|
7
|
+
get(token: typeof HttpAdapterHost): InstanceType<typeof HttpAdapterHost>;
|
|
8
|
+
getHttpAdapter?: () => unknown;
|
|
9
|
+
use?: (...middleware: unknown[]) => unknown;
|
|
10
|
+
useGlobalFilters(...filters: unknown[]): unknown;
|
|
11
|
+
}
|
|
12
|
+
export declare function registerWotchiNest(app: unknown, client: WotchiClient, options?: NestWotchiOptions): void;
|
|
13
|
+
export declare function registerWotchiNestStatusObserver(app: unknown, client: WotchiClient, options?: WotchiStatusObserverOptions): 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;
|