@futurewindai/wotchi 0.1.0-beta.5 → 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 +78 -24
- package/SECURITY.md +2 -2
- package/dist/cjs/core/admission.js +22 -0
- package/dist/cjs/core/client.js +46 -0
- package/dist/cjs/core/config.js +16 -0
- package/dist/cjs/core/diagnostics.js +4 -0
- package/dist/cjs/core/group-store.js +7 -8
- package/dist/cjs/core/limits.js +5 -1
- package/dist/cjs/core/normalize.js +6 -1
- package/dist/cjs/core/notification-queue.js +77 -8
- package/dist/cjs/core/prometheus.js +129 -0
- package/dist/cjs/core/redact.js +4 -1
- package/dist/cjs/core/runtime-monitor.js +109 -0
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/integrations/express/error-handler.js +21 -5
- package/dist/cjs/integrations/nest/capture.js +21 -0
- package/dist/cjs/integrations/nest/exception-filter.js +62 -14
- package/dist/cjs/integrations/nest/filter-wrapper.js +17 -0
- package/dist/cjs/integrations/nest/index.js +6 -1
- package/dist/cjs/integrations/nest/module.js +44 -0
- package/dist/cjs/integrations/nest/register.js +15 -1
- package/dist/esm/core/admission.js +19 -0
- package/dist/esm/core/client.js +46 -0
- package/dist/esm/core/config.js +17 -1
- package/dist/esm/core/diagnostics.js +4 -0
- package/dist/esm/core/group-store.js +7 -8
- package/dist/esm/core/limits.js +4 -0
- package/dist/esm/core/normalize.js +6 -1
- package/dist/esm/core/notification-queue.js +78 -9
- package/dist/esm/core/prometheus.js +125 -0
- package/dist/esm/core/redact.js +4 -1
- package/dist/esm/core/runtime-monitor.js +106 -0
- package/dist/esm/index.js +3 -1
- package/dist/esm/integrations/express/error-handler.js +21 -5
- package/dist/esm/integrations/nest/capture.js +17 -0
- package/dist/esm/integrations/nest/exception-filter.js +63 -15
- package/dist/esm/integrations/nest/filter-wrapper.js +14 -0
- package/dist/esm/integrations/nest/index.js +2 -0
- package/dist/esm/integrations/nest/module.js +41 -0
- package/dist/esm/integrations/nest/register.js +15 -1
- package/dist/types/core/admission.d.ts +10 -0
- package/dist/types/core/config.d.ts +10 -0
- package/dist/types/core/diagnostics.d.ts +3 -1
- package/dist/types/core/incident-policy.d.ts +1 -1
- package/dist/types/core/limits.d.ts +4 -0
- package/dist/types/core/notification-queue.d.ts +9 -0
- 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 +16 -1
- package/dist/types/index.d.ts +5 -1
- 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 +4 -0
- package/dist/types/integrations/nest/module.d.ts +9 -0
- package/dist/types/integrations/nest/register.d.ts +3 -0
- package/dist/types-cjs/core/admission.d.cts +10 -0
- package/dist/types-cjs/core/config.d.cts +10 -0
- package/dist/types-cjs/core/diagnostics.d.cts +3 -1
- package/dist/types-cjs/core/incident-policy.d.cts +1 -1
- package/dist/types-cjs/core/limits.d.cts +4 -0
- package/dist/types-cjs/core/notification-queue.d.cts +9 -0
- 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 +16 -1
- package/dist/types-cjs/index.d.cts +5 -1
- 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 +4 -0
- package/dist/types-cjs/integrations/nest/module.d.cts +9 -0
- package/dist/types-cjs/integrations/nest/register.d.cts +3 -0
- package/package.json +12 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { markExpressErrorCaptured } from "../express/state.js";
|
|
2
|
+
import { getNestRequestContext } from "./request-context.js";
|
|
3
|
+
export const captureWotchiNestException = (client, exception, host, options) => {
|
|
4
|
+
try {
|
|
5
|
+
markExpressErrorCaptured(host.switchToHttp().getRequest());
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
// Non-HTTP NestJS contexts do not expose an Express request to mark.
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const requestContext = getNestRequestContext(host, exception, options);
|
|
12
|
+
client.captureException(exception, undefined, requestContext === undefined ? undefined : { request: requestContext });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// A capture failure must never change NestJS exception handling.
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -7,35 +7,83 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import { Catch } from "@nestjs/common";
|
|
10
|
+
import { Catch, HttpException } from "@nestjs/common";
|
|
11
11
|
import { BaseExceptionFilter } from "@nestjs/core";
|
|
12
|
-
import {
|
|
13
|
-
|
|
12
|
+
import { captureWotchiNestException } from "./capture.js";
|
|
13
|
+
const FILTER_CATCH_EXCEPTIONS = "__filterCatchExceptions__";
|
|
14
|
+
const matchesException = (filter, exception) => {
|
|
15
|
+
const reflection = Reflect;
|
|
16
|
+
const exceptionTypes = reflection.getMetadata?.(FILTER_CATCH_EXCEPTIONS, filter.constructor);
|
|
17
|
+
if (!Array.isArray(exceptionTypes) || exceptionTypes.length === 0) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return exceptionTypes.some((exceptionType) => typeof exceptionType === "function" &&
|
|
21
|
+
exception instanceof exceptionType);
|
|
22
|
+
};
|
|
14
23
|
let WotchiNestExceptionFilter = class WotchiNestExceptionFilter extends BaseExceptionFilter {
|
|
15
|
-
constructor(client, applicationRef, options) {
|
|
24
|
+
constructor(client, applicationRef, options, previousGlobalFilters = []) {
|
|
16
25
|
super(applicationRef);
|
|
17
26
|
this.client = client;
|
|
18
27
|
this.options = options;
|
|
28
|
+
this.previousGlobalFilters = previousGlobalFilters;
|
|
19
29
|
}
|
|
20
30
|
catch(exception, host) {
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
captureWotchiNestException(this.client, exception, host, this.options);
|
|
32
|
+
const existingFilter = this.previousGlobalFilters.find((filter) => matchesException(filter, exception));
|
|
33
|
+
if (existingFilter !== undefined) {
|
|
34
|
+
existingFilter.catch(exception, host);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.respondWithoutLoggingRawUnknownException(exception, host);
|
|
38
|
+
}
|
|
39
|
+
respondWithoutLoggingRawUnknownException(exception, host) {
|
|
40
|
+
if (this.applicationRef === undefined) {
|
|
41
|
+
this.respondThroughExpressAdapter(exception, host);
|
|
42
|
+
return;
|
|
23
43
|
}
|
|
24
|
-
|
|
25
|
-
|
|
44
|
+
if (exception instanceof HttpException) {
|
|
45
|
+
super.catch(exception, host);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const response = host.getArgByIndex(1);
|
|
49
|
+
if (this.applicationRef.isHeadersSent(response)) {
|
|
50
|
+
this.applicationRef.end(response);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (this.isHttpError(exception)) {
|
|
54
|
+
this.applicationRef.reply(response, { statusCode: exception.statusCode, message: exception.message }, exception.statusCode);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.applicationRef.reply(response, { statusCode: 500, message: "Internal server error" }, 500);
|
|
58
|
+
}
|
|
59
|
+
respondThroughExpressAdapter(exception, host) {
|
|
60
|
+
const response = host.switchToHttp().getResponse();
|
|
61
|
+
if (response.headersSent) {
|
|
62
|
+
response.end();
|
|
63
|
+
return;
|
|
26
64
|
}
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
65
|
+
if (exception instanceof HttpException) {
|
|
66
|
+
const statusCode = exception.getStatus();
|
|
67
|
+
const exceptionResponse = exception.getResponse();
|
|
68
|
+
response
|
|
69
|
+
.status(statusCode)
|
|
70
|
+
.json(typeof exceptionResponse === "object" && exceptionResponse !== null
|
|
71
|
+
? exceptionResponse
|
|
72
|
+
: { statusCode, message: exceptionResponse });
|
|
73
|
+
return;
|
|
30
74
|
}
|
|
31
|
-
|
|
32
|
-
|
|
75
|
+
if (this.isHttpError(exception)) {
|
|
76
|
+
response.status(exception.statusCode).json({
|
|
77
|
+
statusCode: exception.statusCode,
|
|
78
|
+
message: exception.message,
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
33
81
|
}
|
|
34
|
-
|
|
82
|
+
response.status(500).json({ statusCode: 500, message: "Internal server error" });
|
|
35
83
|
}
|
|
36
84
|
};
|
|
37
85
|
WotchiNestExceptionFilter = __decorate([
|
|
38
86
|
Catch(),
|
|
39
|
-
__metadata("design:paramtypes", [Object, Object, Object])
|
|
87
|
+
__metadata("design:paramtypes", [Object, Object, Object, Array])
|
|
40
88
|
], WotchiNestExceptionFilter);
|
|
41
89
|
export { WotchiNestExceptionFilter };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { captureWotchiNestException } from "./capture.js";
|
|
2
|
+
export function withWotchiNestFilter(client, filter, options) {
|
|
3
|
+
const wrapped = Object.create(filter);
|
|
4
|
+
Object.defineProperty(wrapped, "catch", {
|
|
5
|
+
configurable: false,
|
|
6
|
+
enumerable: false,
|
|
7
|
+
value: (exception, host) => {
|
|
8
|
+
captureWotchiNestException(client, exception, host, options);
|
|
9
|
+
filter.catch(exception, host);
|
|
10
|
+
},
|
|
11
|
+
writable: false,
|
|
12
|
+
});
|
|
13
|
+
return wrapped;
|
|
14
|
+
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { consoleNotifier, createWotchi, telegramNotifier, webhookNotifier } from "../../index.js";
|
|
2
|
+
export { withWotchiNestFilter } from "./filter-wrapper.js";
|
|
3
|
+
export { WotchiModule, WOTCHI_CLIENT } from "./module.js";
|
|
2
4
|
export { registerWotchiNest, registerWotchiNestStatusObserver } from "./register.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var WotchiModule_1;
|
|
8
|
+
import { Global, Module } from "@nestjs/common";
|
|
9
|
+
import { APP_FILTER } from "@nestjs/core";
|
|
10
|
+
import { createWotchi } from "../../core/client.js";
|
|
11
|
+
import { WotchiNestExceptionFilter } from "./exception-filter.js";
|
|
12
|
+
export const WOTCHI_CLIENT = Symbol("@futurewindai/wotchi/client");
|
|
13
|
+
let WotchiModule = WotchiModule_1 = class WotchiModule {
|
|
14
|
+
static forRoot(config, options) {
|
|
15
|
+
const registerGlobalFilter = options?.registerGlobalFilter ?? true;
|
|
16
|
+
const providers = [
|
|
17
|
+
{
|
|
18
|
+
provide: WOTCHI_CLIENT,
|
|
19
|
+
useFactory: () => createWotchi(config),
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
if (registerGlobalFilter) {
|
|
23
|
+
providers.push({
|
|
24
|
+
provide: APP_FILTER,
|
|
25
|
+
useFactory: (client) => new WotchiNestExceptionFilter(client),
|
|
26
|
+
inject: [WOTCHI_CLIENT],
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
module: WotchiModule_1,
|
|
31
|
+
global: true,
|
|
32
|
+
providers,
|
|
33
|
+
exports: [WOTCHI_CLIENT],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
WotchiModule = WotchiModule_1 = __decorate([
|
|
38
|
+
Global(),
|
|
39
|
+
Module({})
|
|
40
|
+
], WotchiModule);
|
|
41
|
+
export { WotchiModule };
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import { HttpAdapterHost } from "@nestjs/core";
|
|
2
2
|
import { WotchiNestExceptionFilter } from "./exception-filter.js";
|
|
3
3
|
import { wotchiStatusObserver } from "../express/status-observer.js";
|
|
4
|
+
const isNestExceptionFilter = (value) => typeof value === "object" &&
|
|
5
|
+
value !== null &&
|
|
6
|
+
"catch" in value &&
|
|
7
|
+
typeof value.catch === "function";
|
|
8
|
+
const getPreviousGlobalFilters = (application) => {
|
|
9
|
+
const configuredFilters = application.config?.getGlobalFilters?.();
|
|
10
|
+
if (!Array.isArray(configuredFilters)) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
return configuredFilters
|
|
14
|
+
.filter(isNestExceptionFilter)
|
|
15
|
+
.filter((filter) => !(filter instanceof WotchiNestExceptionFilter))
|
|
16
|
+
.reverse();
|
|
17
|
+
};
|
|
4
18
|
export function registerWotchiNest(app, client, options) {
|
|
5
19
|
const application = app;
|
|
6
20
|
const directAdapter = application.getHttpAdapter?.();
|
|
7
21
|
const httpAdapter = directAdapter === undefined
|
|
8
22
|
? application.get(HttpAdapterHost).httpAdapter
|
|
9
23
|
: directAdapter;
|
|
10
|
-
application.useGlobalFilters(new WotchiNestExceptionFilter(client, httpAdapter, options));
|
|
24
|
+
application.useGlobalFilters(new WotchiNestExceptionFilter(client, httpAdapter, options, getPreviousGlobalFilters(application)));
|
|
11
25
|
}
|
|
12
26
|
export function registerWotchiNestStatusObserver(app, client, options) {
|
|
13
27
|
const application = app;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface CaptureAdmissionOptions {
|
|
2
|
+
maxEventsPerSecond: number;
|
|
3
|
+
burst: number;
|
|
4
|
+
now?: () => number;
|
|
5
|
+
}
|
|
6
|
+
export interface CaptureAdmission {
|
|
7
|
+
tryAcquire(): boolean;
|
|
8
|
+
}
|
|
9
|
+
/** A small token bucket used before normalization to bound overload work. */
|
|
10
|
+
export declare function createCaptureAdmission(options: CaptureAdmissionOptions): CaptureAdmission;
|
|
@@ -22,6 +22,16 @@ export interface NormalizedWotchiConfig {
|
|
|
22
22
|
readonly queue: {
|
|
23
23
|
readonly maxPendingAlerts: number;
|
|
24
24
|
readonly concurrency: 1;
|
|
25
|
+
readonly notifierTimeoutMs: number;
|
|
26
|
+
readonly notifierCircuitBreaker: {
|
|
27
|
+
readonly failureThreshold: number;
|
|
28
|
+
readonly cooldownMs: number;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
readonly overload?: {
|
|
32
|
+
readonly maxEventsPerSecond: number;
|
|
33
|
+
readonly burst: number;
|
|
34
|
+
readonly alertCooldownMs: number;
|
|
25
35
|
};
|
|
26
36
|
readonly privacy: {
|
|
27
37
|
readonly redactKeys: readonly string[];
|
|
@@ -6,6 +6,8 @@ export interface DiagnosticsState {
|
|
|
6
6
|
filterFailures: number;
|
|
7
7
|
beforeSendFailures: number;
|
|
8
8
|
eventsSuppressed: number;
|
|
9
|
+
eventsDroppedOverload: number;
|
|
10
|
+
capturesAfterShutdown: number;
|
|
9
11
|
}
|
|
10
12
|
export declare function createDiagnosticsState(): DiagnosticsState;
|
|
11
|
-
export declare function snapshotDiagnostics(state: DiagnosticsState, values: Omit<WotchiDiagnostics, "capturedEvents" | "captureFailures" | "fingerprintCallbackFailures" | "filterFailures" | "beforeSendFailures" | "eventsSuppressed">): Readonly<WotchiDiagnostics>;
|
|
13
|
+
export declare function snapshotDiagnostics(state: DiagnosticsState, values: Omit<WotchiDiagnostics, "capturedEvents" | "captureFailures" | "fingerprintCallbackFailures" | "filterFailures" | "beforeSendFailures" | "eventsSuppressed" | "eventsDroppedOverload" | "capturesAfterShutdown">): Readonly<WotchiDiagnostics>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IncidentGroup, IncidentSeverity } from "./types.js";
|
|
2
|
-
export type IncidentEventKind = "error" | "manual" | "process-monitor";
|
|
2
|
+
export type IncidentEventKind = "error" | "manual" | "process-monitor" | "runtime-monitor";
|
|
3
3
|
export interface IncidentPolicyInput {
|
|
4
4
|
group: IncidentGroup;
|
|
5
5
|
now: number;
|
|
@@ -4,6 +4,10 @@ export declare const MAX_PENDING_ALERTS = 10000;
|
|
|
4
4
|
export declare const MAX_WINDOW_MS: number;
|
|
5
5
|
export declare const MAX_ALERT_THRESHOLD = 1000000;
|
|
6
6
|
export declare const MAX_COOLDOWN_MS: number;
|
|
7
|
+
export declare const MAX_CAPTURE_RATE = 1000000;
|
|
8
|
+
export declare const MAX_NOTIFIER_TIMEOUT_MS = 60000;
|
|
9
|
+
export declare const MAX_CIRCUIT_BREAKER_COOLDOWN_MS: number;
|
|
10
|
+
export declare const MAX_CIRCUIT_BREAKER_FAILURES = 100;
|
|
7
11
|
export declare const MAX_NORMALIZATION_DEPTH = 20;
|
|
8
12
|
export declare const MAX_NORMALIZATION_KEYS = 10000;
|
|
9
13
|
export declare const MAX_NORMALIZATION_STRING_LENGTH = 32768;
|
|
@@ -2,6 +2,11 @@ import type { IncidentAlert, WotchiNotifier } from "./types.js";
|
|
|
2
2
|
export interface NotificationQueueOptions {
|
|
3
3
|
maxPendingAlerts: number;
|
|
4
4
|
concurrency: 1;
|
|
5
|
+
notifierTimeoutMs?: number;
|
|
6
|
+
notifierCircuitBreaker?: {
|
|
7
|
+
failureThreshold: number;
|
|
8
|
+
cooldownMs: number;
|
|
9
|
+
};
|
|
5
10
|
onNotifierError?: (error: unknown, notifier: WotchiNotifier) => void;
|
|
6
11
|
}
|
|
7
12
|
export interface NotificationJobResult {
|
|
@@ -11,10 +16,14 @@ export interface NotificationJobResult {
|
|
|
11
16
|
export interface NotificationQueue {
|
|
12
17
|
enqueue(alert: IncidentAlert, notifiers: readonly WotchiNotifier[], onComplete?: (result: NotificationJobResult) => void): boolean;
|
|
13
18
|
flush(timeoutMs?: number): Promise<void>;
|
|
19
|
+
close(timeoutMs?: number): Promise<void>;
|
|
20
|
+
isClosed(): boolean;
|
|
14
21
|
pending(): number;
|
|
15
22
|
alertsQueued(): number;
|
|
16
23
|
alertsDropped(): number;
|
|
17
24
|
alertsSent(): number;
|
|
18
25
|
notifierFailures(): number;
|
|
26
|
+
notifierTimeouts(): number;
|
|
27
|
+
notifierCircuitOpenSkips(): number;
|
|
19
28
|
}
|
|
20
29
|
export declare function createNotificationQueue(options: NotificationQueueOptions): NotificationQueue;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WotchiClient } from "./types.js";
|
|
2
|
+
export declare const PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
3
|
+
export interface WotchiPrometheusExporter {
|
|
4
|
+
readonly contentType: typeof PROMETHEUS_CONTENT_TYPE;
|
|
5
|
+
render(): string;
|
|
6
|
+
}
|
|
7
|
+
export declare function createWotchiPrometheusExporter(client: Pick<WotchiClient, "getDiagnostics">): WotchiPrometheusExporter;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { WotchiClient } from "./types.js";
|
|
2
|
+
export interface WotchiRuntimeWatcherOptions {
|
|
3
|
+
intervalMs?: number;
|
|
4
|
+
cpuPercent?: number;
|
|
5
|
+
rssBytes?: number;
|
|
6
|
+
heapUsedBytes?: number;
|
|
7
|
+
eventLoopDelayMs?: number;
|
|
8
|
+
pendingAlerts?: number;
|
|
9
|
+
notifierFailures?: number;
|
|
10
|
+
alertThreshold?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface WotchiRuntimeWatcherHandle {
|
|
13
|
+
unregister(): void;
|
|
14
|
+
}
|
|
15
|
+
export declare function registerWotchiRuntimeWatcher(client: Pick<WotchiClient, "captureEvent" | "getDiagnostics">, options?: WotchiRuntimeWatcherOptions): WotchiRuntimeWatcherHandle;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type IncidentSeverity = "low" | "medium" | "high" | "critical";
|
|
2
|
-
export type WotchiEventKind = "error" | "manual" | "process-monitor";
|
|
2
|
+
export type WotchiEventKind = "error" | "manual" | "process-monitor" | "runtime-monitor";
|
|
3
3
|
export interface WotchiTraceContext {
|
|
4
4
|
traceId?: string;
|
|
5
5
|
spanId?: string;
|
|
@@ -160,6 +160,16 @@ export interface WotchiConfig {
|
|
|
160
160
|
queue?: {
|
|
161
161
|
maxPendingAlerts?: number;
|
|
162
162
|
concurrency?: 1;
|
|
163
|
+
notifierTimeoutMs?: number;
|
|
164
|
+
notifierCircuitBreaker?: {
|
|
165
|
+
failureThreshold?: number;
|
|
166
|
+
cooldownMs?: number;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
overload?: {
|
|
170
|
+
maxEventsPerSecond?: number;
|
|
171
|
+
burst?: number;
|
|
172
|
+
alertCooldownMs?: number;
|
|
163
173
|
};
|
|
164
174
|
privacy?: {
|
|
165
175
|
redactKeys?: string[];
|
|
@@ -182,8 +192,12 @@ export interface WotchiDiagnostics {
|
|
|
182
192
|
filterFailures: number;
|
|
183
193
|
beforeSendFailures: number;
|
|
184
194
|
eventsSuppressed: number;
|
|
195
|
+
eventsDroppedOverload: number;
|
|
196
|
+
capturesAfterShutdown: number;
|
|
185
197
|
activeGroups: number;
|
|
186
198
|
pendingAlerts: number;
|
|
199
|
+
notifierTimeouts: number;
|
|
200
|
+
notifierCircuitOpenSkips: number;
|
|
187
201
|
}
|
|
188
202
|
export type WotchiTestAlertStatus = "sent" | "queue-full" | "timeout" | "notifier-failed";
|
|
189
203
|
export interface WotchiTestAlertResult {
|
|
@@ -201,5 +215,6 @@ export interface WotchiClient {
|
|
|
201
215
|
captureEvent(event: WotchiEventInput): void;
|
|
202
216
|
testAlert(): Promise<WotchiTestAlertResult>;
|
|
203
217
|
flush(timeoutMs?: number): Promise<void>;
|
|
218
|
+
shutdown(timeoutMs?: number): Promise<void>;
|
|
204
219
|
getDiagnostics(): Readonly<WotchiDiagnostics>;
|
|
205
220
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { createWotchi } from "./core/client.js";
|
|
2
2
|
import { registerWotchiProcessMonitor } from "./core/process-monitor.js";
|
|
3
|
+
import { createWotchiPrometheusExporter, PROMETHEUS_CONTENT_TYPE } from "./core/prometheus.js";
|
|
4
|
+
import { registerWotchiRuntimeWatcher } from "./core/runtime-monitor.js";
|
|
3
5
|
import { consoleNotifier } from "./notifiers/console.js";
|
|
4
6
|
import { telegramNotifier } from "./notifiers/telegram.js";
|
|
5
7
|
import { webhookNotifier } from "./notifiers/webhook.js";
|
|
6
8
|
export { WotchiConfigurationError } from "./core/errors.js";
|
|
7
9
|
export type { ProcessMonitorHandle } from "./core/process-monitor.js";
|
|
8
10
|
export type { NormalizedWotchiConfig } from "./core/config.js";
|
|
11
|
+
export type { WotchiPrometheusExporter } from "./core/prometheus.js";
|
|
12
|
+
export type { WotchiRuntimeWatcherHandle, WotchiRuntimeWatcherOptions, } from "./core/runtime-monitor.js";
|
|
9
13
|
export type { ConsoleNotifierOptions, IncidentAlert, IncidentGroup, IncidentSeverity, SafeErrorEvent, TelegramNotifierOptions, WebhookNotifierOptions, WotchiBeforeSend, WotchiCaptureOptions, WotchiClient, WotchiConfig, WotchiDiagnostics, WotchiEventFilter, WotchiEventInput, WotchiEventKind, WotchiFingerprintOverride, WotchiFingerprintCallback, WotchiIncidentRule, WotchiLinkTemplates, WotchiLinks, WotchiNotifier, WotchiRequestContext, WotchiTags, WotchiTestAlertResult, WotchiTestAlertStatus, WotchiTraceContext, } from "./core/types.js";
|
|
10
14
|
export type { WebhookRequestFunction, WebhookRequestOptions, WebhookResponse, } from "./notifiers/webhook-http.js";
|
|
11
|
-
export { consoleNotifier, createWotchi, registerWotchiProcessMonitor, telegramNotifier, webhookNotifier, };
|
|
15
|
+
export { consoleNotifier, createWotchi, createWotchiPrometheusExporter, PROMETHEUS_CONTENT_TYPE, registerWotchiProcessMonitor, registerWotchiRuntimeWatcher, telegramNotifier, webhookNotifier, };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ArgumentsHost } from "@nestjs/common";
|
|
2
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
3
|
+
import { type NestWotchiOptions } from "./request-context.js";
|
|
4
|
+
export declare const captureWotchiNestException: (client: WotchiClient, exception: unknown, host: ArgumentsHost, options?: NestWotchiOptions) => void;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type ArgumentsHost, type HttpServer } from "@nestjs/common";
|
|
2
2
|
import { BaseExceptionFilter } from "@nestjs/core";
|
|
3
3
|
import type { WotchiClient } from "../../core/types.js";
|
|
4
|
-
import {
|
|
4
|
+
import type { NestExceptionFilterLike } from "./filter-wrapper.js";
|
|
5
|
+
import type { NestWotchiOptions } from "./request-context.js";
|
|
5
6
|
export declare class WotchiNestExceptionFilter extends BaseExceptionFilter<unknown> {
|
|
6
7
|
private readonly client;
|
|
7
8
|
private readonly options?;
|
|
8
|
-
|
|
9
|
+
private readonly previousGlobalFilters;
|
|
10
|
+
constructor(client: WotchiClient, applicationRef?: HttpServer, options?: NestWotchiOptions | undefined, previousGlobalFilters?: readonly NestExceptionFilterLike[]);
|
|
9
11
|
catch(exception: unknown, host: ArgumentsHost): void;
|
|
12
|
+
private respondWithoutLoggingRawUnknownException;
|
|
13
|
+
private respondThroughExpressAdapter;
|
|
10
14
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ArgumentsHost } from "@nestjs/common";
|
|
2
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
3
|
+
import type { NestWotchiOptions } from "./request-context.js";
|
|
4
|
+
export interface NestExceptionFilterLike {
|
|
5
|
+
catch(exception: unknown, host: ArgumentsHost): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function withWotchiNestFilter<TFilter extends NestExceptionFilterLike>(client: WotchiClient, filter: TFilter, options?: NestWotchiOptions): TFilter;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { consoleNotifier, createWotchi, telegramNotifier, webhookNotifier } from "../../index.js";
|
|
2
|
+
export { withWotchiNestFilter } from "./filter-wrapper.js";
|
|
3
|
+
export { WotchiModule, WOTCHI_CLIENT } from "./module.js";
|
|
2
4
|
export { registerWotchiNest, registerWotchiNestStatusObserver } from "./register.js";
|
|
3
5
|
export type { ConsoleNotifierOptions, IncidentAlert, IncidentGroup, IncidentSeverity, SafeErrorEvent, TelegramNotifierOptions, WebhookNotifierOptions, WotchiBeforeSend, WotchiCaptureOptions, WotchiClient, WotchiConfig, WotchiDiagnostics, WotchiEventFilter, WotchiEventInput, WotchiFingerprintCallback, WotchiFingerprintOverride, WotchiIncidentRule, WotchiLinkTemplates, WotchiLinks, WotchiNotifier, WotchiRequestContext, WotchiTags, WotchiTestAlertResult, WotchiTestAlertStatus, WotchiTraceContext, } from "../../index.js";
|
|
4
6
|
export type { NestWotchiApplication, NestWotchiOptions } from "./register.js";
|
|
7
|
+
export type { NestExceptionFilterLike } from "./filter-wrapper.js";
|
|
8
|
+
export type { WotchiNestModuleOptions } from "./module.js";
|
|
5
9
|
export type { WotchiStatusClass, WotchiStatusObserverOptions } from "../express/status-observer.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DynamicModule } from "@nestjs/common";
|
|
2
|
+
import type { WotchiConfig } from "../../core/types.js";
|
|
3
|
+
export declare const WOTCHI_CLIENT: unique symbol;
|
|
4
|
+
export interface WotchiNestModuleOptions {
|
|
5
|
+
registerGlobalFilter?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class WotchiModule {
|
|
8
|
+
static forRoot(config: WotchiConfig, options?: WotchiNestModuleOptions): DynamicModule;
|
|
9
|
+
}
|
|
@@ -8,6 +8,9 @@ export interface NestWotchiApplication {
|
|
|
8
8
|
getHttpAdapter?: () => unknown;
|
|
9
9
|
use?: (...middleware: unknown[]) => unknown;
|
|
10
10
|
useGlobalFilters(...filters: unknown[]): unknown;
|
|
11
|
+
config?: {
|
|
12
|
+
getGlobalFilters?: () => unknown[];
|
|
13
|
+
};
|
|
11
14
|
}
|
|
12
15
|
export declare function registerWotchiNest(app: unknown, client: WotchiClient, options?: NestWotchiOptions): void;
|
|
13
16
|
export declare function registerWotchiNestStatusObserver(app: unknown, client: WotchiClient, options?: WotchiStatusObserverOptions): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface CaptureAdmissionOptions {
|
|
2
|
+
maxEventsPerSecond: number;
|
|
3
|
+
burst: number;
|
|
4
|
+
now?: () => number;
|
|
5
|
+
}
|
|
6
|
+
export interface CaptureAdmission {
|
|
7
|
+
tryAcquire(): boolean;
|
|
8
|
+
}
|
|
9
|
+
/** A small token bucket used before normalization to bound overload work. */
|
|
10
|
+
export declare function createCaptureAdmission(options: CaptureAdmissionOptions): CaptureAdmission;
|
|
@@ -22,6 +22,16 @@ export interface NormalizedWotchiConfig {
|
|
|
22
22
|
readonly queue: {
|
|
23
23
|
readonly maxPendingAlerts: number;
|
|
24
24
|
readonly concurrency: 1;
|
|
25
|
+
readonly notifierTimeoutMs: number;
|
|
26
|
+
readonly notifierCircuitBreaker: {
|
|
27
|
+
readonly failureThreshold: number;
|
|
28
|
+
readonly cooldownMs: number;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
readonly overload?: {
|
|
32
|
+
readonly maxEventsPerSecond: number;
|
|
33
|
+
readonly burst: number;
|
|
34
|
+
readonly alertCooldownMs: number;
|
|
25
35
|
};
|
|
26
36
|
readonly privacy: {
|
|
27
37
|
readonly redactKeys: readonly string[];
|
|
@@ -6,6 +6,8 @@ export interface DiagnosticsState {
|
|
|
6
6
|
filterFailures: number;
|
|
7
7
|
beforeSendFailures: number;
|
|
8
8
|
eventsSuppressed: number;
|
|
9
|
+
eventsDroppedOverload: number;
|
|
10
|
+
capturesAfterShutdown: number;
|
|
9
11
|
}
|
|
10
12
|
export declare function createDiagnosticsState(): DiagnosticsState;
|
|
11
|
-
export declare function snapshotDiagnostics(state: DiagnosticsState, values: Omit<WotchiDiagnostics, "capturedEvents" | "captureFailures" | "fingerprintCallbackFailures" | "filterFailures" | "beforeSendFailures" | "eventsSuppressed">): Readonly<WotchiDiagnostics>;
|
|
13
|
+
export declare function snapshotDiagnostics(state: DiagnosticsState, values: Omit<WotchiDiagnostics, "capturedEvents" | "captureFailures" | "fingerprintCallbackFailures" | "filterFailures" | "beforeSendFailures" | "eventsSuppressed" | "eventsDroppedOverload" | "capturesAfterShutdown">): Readonly<WotchiDiagnostics>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IncidentGroup, IncidentSeverity } from "./types.js";
|
|
2
|
-
export type IncidentEventKind = "error" | "manual" | "process-monitor";
|
|
2
|
+
export type IncidentEventKind = "error" | "manual" | "process-monitor" | "runtime-monitor";
|
|
3
3
|
export interface IncidentPolicyInput {
|
|
4
4
|
group: IncidentGroup;
|
|
5
5
|
now: number;
|
|
@@ -4,6 +4,10 @@ export declare const MAX_PENDING_ALERTS = 10000;
|
|
|
4
4
|
export declare const MAX_WINDOW_MS: number;
|
|
5
5
|
export declare const MAX_ALERT_THRESHOLD = 1000000;
|
|
6
6
|
export declare const MAX_COOLDOWN_MS: number;
|
|
7
|
+
export declare const MAX_CAPTURE_RATE = 1000000;
|
|
8
|
+
export declare const MAX_NOTIFIER_TIMEOUT_MS = 60000;
|
|
9
|
+
export declare const MAX_CIRCUIT_BREAKER_COOLDOWN_MS: number;
|
|
10
|
+
export declare const MAX_CIRCUIT_BREAKER_FAILURES = 100;
|
|
7
11
|
export declare const MAX_NORMALIZATION_DEPTH = 20;
|
|
8
12
|
export declare const MAX_NORMALIZATION_KEYS = 10000;
|
|
9
13
|
export declare const MAX_NORMALIZATION_STRING_LENGTH = 32768;
|
|
@@ -2,6 +2,11 @@ import type { IncidentAlert, WotchiNotifier } from "./types.js";
|
|
|
2
2
|
export interface NotificationQueueOptions {
|
|
3
3
|
maxPendingAlerts: number;
|
|
4
4
|
concurrency: 1;
|
|
5
|
+
notifierTimeoutMs?: number;
|
|
6
|
+
notifierCircuitBreaker?: {
|
|
7
|
+
failureThreshold: number;
|
|
8
|
+
cooldownMs: number;
|
|
9
|
+
};
|
|
5
10
|
onNotifierError?: (error: unknown, notifier: WotchiNotifier) => void;
|
|
6
11
|
}
|
|
7
12
|
export interface NotificationJobResult {
|
|
@@ -11,10 +16,14 @@ export interface NotificationJobResult {
|
|
|
11
16
|
export interface NotificationQueue {
|
|
12
17
|
enqueue(alert: IncidentAlert, notifiers: readonly WotchiNotifier[], onComplete?: (result: NotificationJobResult) => void): boolean;
|
|
13
18
|
flush(timeoutMs?: number): Promise<void>;
|
|
19
|
+
close(timeoutMs?: number): Promise<void>;
|
|
20
|
+
isClosed(): boolean;
|
|
14
21
|
pending(): number;
|
|
15
22
|
alertsQueued(): number;
|
|
16
23
|
alertsDropped(): number;
|
|
17
24
|
alertsSent(): number;
|
|
18
25
|
notifierFailures(): number;
|
|
26
|
+
notifierTimeouts(): number;
|
|
27
|
+
notifierCircuitOpenSkips(): number;
|
|
19
28
|
}
|
|
20
29
|
export declare function createNotificationQueue(options: NotificationQueueOptions): NotificationQueue;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WotchiClient } from "./types.js";
|
|
2
|
+
export declare const PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
3
|
+
export interface WotchiPrometheusExporter {
|
|
4
|
+
readonly contentType: typeof PROMETHEUS_CONTENT_TYPE;
|
|
5
|
+
render(): string;
|
|
6
|
+
}
|
|
7
|
+
export declare function createWotchiPrometheusExporter(client: Pick<WotchiClient, "getDiagnostics">): WotchiPrometheusExporter;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { WotchiClient } from "./types.js";
|
|
2
|
+
export interface WotchiRuntimeWatcherOptions {
|
|
3
|
+
intervalMs?: number;
|
|
4
|
+
cpuPercent?: number;
|
|
5
|
+
rssBytes?: number;
|
|
6
|
+
heapUsedBytes?: number;
|
|
7
|
+
eventLoopDelayMs?: number;
|
|
8
|
+
pendingAlerts?: number;
|
|
9
|
+
notifierFailures?: number;
|
|
10
|
+
alertThreshold?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface WotchiRuntimeWatcherHandle {
|
|
13
|
+
unregister(): void;
|
|
14
|
+
}
|
|
15
|
+
export declare function registerWotchiRuntimeWatcher(client: Pick<WotchiClient, "captureEvent" | "getDiagnostics">, options?: WotchiRuntimeWatcherOptions): WotchiRuntimeWatcherHandle;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type IncidentSeverity = "low" | "medium" | "high" | "critical";
|
|
2
|
-
export type WotchiEventKind = "error" | "manual" | "process-monitor";
|
|
2
|
+
export type WotchiEventKind = "error" | "manual" | "process-monitor" | "runtime-monitor";
|
|
3
3
|
export interface WotchiTraceContext {
|
|
4
4
|
traceId?: string;
|
|
5
5
|
spanId?: string;
|
|
@@ -160,6 +160,16 @@ export interface WotchiConfig {
|
|
|
160
160
|
queue?: {
|
|
161
161
|
maxPendingAlerts?: number;
|
|
162
162
|
concurrency?: 1;
|
|
163
|
+
notifierTimeoutMs?: number;
|
|
164
|
+
notifierCircuitBreaker?: {
|
|
165
|
+
failureThreshold?: number;
|
|
166
|
+
cooldownMs?: number;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
overload?: {
|
|
170
|
+
maxEventsPerSecond?: number;
|
|
171
|
+
burst?: number;
|
|
172
|
+
alertCooldownMs?: number;
|
|
163
173
|
};
|
|
164
174
|
privacy?: {
|
|
165
175
|
redactKeys?: string[];
|
|
@@ -182,8 +192,12 @@ export interface WotchiDiagnostics {
|
|
|
182
192
|
filterFailures: number;
|
|
183
193
|
beforeSendFailures: number;
|
|
184
194
|
eventsSuppressed: number;
|
|
195
|
+
eventsDroppedOverload: number;
|
|
196
|
+
capturesAfterShutdown: number;
|
|
185
197
|
activeGroups: number;
|
|
186
198
|
pendingAlerts: number;
|
|
199
|
+
notifierTimeouts: number;
|
|
200
|
+
notifierCircuitOpenSkips: number;
|
|
187
201
|
}
|
|
188
202
|
export type WotchiTestAlertStatus = "sent" | "queue-full" | "timeout" | "notifier-failed";
|
|
189
203
|
export interface WotchiTestAlertResult {
|
|
@@ -201,5 +215,6 @@ export interface WotchiClient {
|
|
|
201
215
|
captureEvent(event: WotchiEventInput): void;
|
|
202
216
|
testAlert(): Promise<WotchiTestAlertResult>;
|
|
203
217
|
flush(timeoutMs?: number): Promise<void>;
|
|
218
|
+
shutdown(timeoutMs?: number): Promise<void>;
|
|
204
219
|
getDiagnostics(): Readonly<WotchiDiagnostics>;
|
|
205
220
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { createWotchi } from "./core/client.js";
|
|
2
2
|
import { registerWotchiProcessMonitor } from "./core/process-monitor.js";
|
|
3
|
+
import { createWotchiPrometheusExporter, PROMETHEUS_CONTENT_TYPE } from "./core/prometheus.js";
|
|
4
|
+
import { registerWotchiRuntimeWatcher } from "./core/runtime-monitor.js";
|
|
3
5
|
import { consoleNotifier } from "./notifiers/console.js";
|
|
4
6
|
import { telegramNotifier } from "./notifiers/telegram.js";
|
|
5
7
|
import { webhookNotifier } from "./notifiers/webhook.js";
|
|
6
8
|
export { WotchiConfigurationError } from "./core/errors.js";
|
|
7
9
|
export type { ProcessMonitorHandle } from "./core/process-monitor.js";
|
|
8
10
|
export type { NormalizedWotchiConfig } from "./core/config.js";
|
|
11
|
+
export type { WotchiPrometheusExporter } from "./core/prometheus.js";
|
|
12
|
+
export type { WotchiRuntimeWatcherHandle, WotchiRuntimeWatcherOptions, } from "./core/runtime-monitor.js";
|
|
9
13
|
export type { ConsoleNotifierOptions, IncidentAlert, IncidentGroup, IncidentSeverity, SafeErrorEvent, TelegramNotifierOptions, WebhookNotifierOptions, WotchiBeforeSend, WotchiCaptureOptions, WotchiClient, WotchiConfig, WotchiDiagnostics, WotchiEventFilter, WotchiEventInput, WotchiEventKind, WotchiFingerprintOverride, WotchiFingerprintCallback, WotchiIncidentRule, WotchiLinkTemplates, WotchiLinks, WotchiNotifier, WotchiRequestContext, WotchiTags, WotchiTestAlertResult, WotchiTestAlertStatus, WotchiTraceContext, } from "./core/types.js";
|
|
10
14
|
export type { WebhookRequestFunction, WebhookRequestOptions, WebhookResponse, } from "./notifiers/webhook-http.js";
|
|
11
|
-
export { consoleNotifier, createWotchi, registerWotchiProcessMonitor, telegramNotifier, webhookNotifier, };
|
|
15
|
+
export { consoleNotifier, createWotchi, createWotchiPrometheusExporter, PROMETHEUS_CONTENT_TYPE, registerWotchiProcessMonitor, registerWotchiRuntimeWatcher, telegramNotifier, webhookNotifier, };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ArgumentsHost } from "@nestjs/common";
|
|
2
|
+
import type { WotchiClient } from "../../core/types.js";
|
|
3
|
+
import { type NestWotchiOptions } from "./request-context.js";
|
|
4
|
+
export declare const captureWotchiNestException: (client: WotchiClient, exception: unknown, host: ArgumentsHost, options?: NestWotchiOptions) => void;
|