@aidenappleby/monitor-js 1.0.0
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/dist/index.d.mts +112 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +298 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +270 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
type LogLevel = "debug" | "info" | "warn" | "error" | "fatal";
|
|
2
|
+
interface MonitorConfig {
|
|
3
|
+
/** Service name reported with every event */
|
|
4
|
+
service: string;
|
|
5
|
+
/** Monitor ingest URL (e.g. https://monitor-ingest.appleby.cloud/v1/events) */
|
|
6
|
+
ingestUrl: string;
|
|
7
|
+
/** API key for authentication */
|
|
8
|
+
apiKey: string;
|
|
9
|
+
/** Environment name (default: "production") */
|
|
10
|
+
env?: string;
|
|
11
|
+
/** Flush interval in milliseconds (default: 2000) */
|
|
12
|
+
flushInterval?: number;
|
|
13
|
+
/** Max batch size before auto-flush (default: 20) */
|
|
14
|
+
batchSize?: number;
|
|
15
|
+
/** Enable automatic error capture via window.onerror (default: true) */
|
|
16
|
+
captureErrors?: boolean;
|
|
17
|
+
/** Enable automatic unhandled promise rejection capture (default: true) */
|
|
18
|
+
captureUnhandledRejections?: boolean;
|
|
19
|
+
/** Enable debug logging to console (default: false) */
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface MonitorEvent {
|
|
23
|
+
timestamp: string;
|
|
24
|
+
service: string;
|
|
25
|
+
env: string;
|
|
26
|
+
job_id: string;
|
|
27
|
+
request_id: string;
|
|
28
|
+
trace_id: string;
|
|
29
|
+
user_id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
level: string;
|
|
32
|
+
data: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
interface EmitOptions {
|
|
35
|
+
/** Request ID for cross-service correlation */
|
|
36
|
+
requestId?: string;
|
|
37
|
+
/** Trace ID for distributed tracing */
|
|
38
|
+
traceId?: string;
|
|
39
|
+
/** User ID associated with this event */
|
|
40
|
+
userId?: string;
|
|
41
|
+
/** Arbitrary data payload */
|
|
42
|
+
data?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare class Monitor {
|
|
46
|
+
private config;
|
|
47
|
+
private queue;
|
|
48
|
+
private timer;
|
|
49
|
+
private userId;
|
|
50
|
+
private jobId;
|
|
51
|
+
private active;
|
|
52
|
+
constructor(config: MonitorConfig);
|
|
53
|
+
/** Set a persistent user ID for all subsequent events */
|
|
54
|
+
setUser(userId: string): void;
|
|
55
|
+
/** Clear the user ID */
|
|
56
|
+
clearUser(): void;
|
|
57
|
+
/** Set a persistent job ID (session-level identifier) */
|
|
58
|
+
setJobId(jobId: string): void;
|
|
59
|
+
/** Emit an event at a specific level */
|
|
60
|
+
emit(name: string, level: LogLevel, opts?: EmitOptions): void;
|
|
61
|
+
/** Emit a debug event */
|
|
62
|
+
debug(name: string, opts?: EmitOptions): void;
|
|
63
|
+
/** Emit an info event */
|
|
64
|
+
info(name: string, opts?: EmitOptions): void;
|
|
65
|
+
/** Emit a warning event */
|
|
66
|
+
warn(name: string, opts?: EmitOptions): void;
|
|
67
|
+
/** Emit an error event */
|
|
68
|
+
error(name: string, opts?: EmitOptions): void;
|
|
69
|
+
/** Emit a fatal event */
|
|
70
|
+
fatal(name: string, opts?: EmitOptions): void;
|
|
71
|
+
/** Flush all queued events to the ingest endpoint */
|
|
72
|
+
flush(): void;
|
|
73
|
+
/** Stop the monitor and flush remaining events */
|
|
74
|
+
shutdown(): void;
|
|
75
|
+
private start;
|
|
76
|
+
private handleVisibilityChange;
|
|
77
|
+
private handlePageHide;
|
|
78
|
+
private errorHandler;
|
|
79
|
+
private rejectionHandler;
|
|
80
|
+
private installErrorHandler;
|
|
81
|
+
private installRejectionHandler;
|
|
82
|
+
private removeListeners;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface AxiosInstance {
|
|
86
|
+
interceptors: {
|
|
87
|
+
request: {
|
|
88
|
+
use: (onFulfilled: (config: any) => any) => void;
|
|
89
|
+
};
|
|
90
|
+
response: {
|
|
91
|
+
use: (onFulfilled: (response: any) => any, onRejected: (error: any) => any) => void;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
interface AxiosMonitorOptions {
|
|
96
|
+
/** Only report events for responses with these status codes or above (default: 400) */
|
|
97
|
+
minStatus?: number;
|
|
98
|
+
/** Report successful requests too (default: false) */
|
|
99
|
+
reportSuccess?: boolean;
|
|
100
|
+
/** Paths to ignore (e.g. ["/healthcheck", "/api/health"]) */
|
|
101
|
+
ignorePaths?: string[];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Attaches Monitor interceptors to an Axios instance.
|
|
105
|
+
* Automatically reports API failures with request_id correlation.
|
|
106
|
+
*
|
|
107
|
+
* Works with both standard axios error handling AND `validateStatus: () => true`
|
|
108
|
+
* (where all HTTP responses go through the fulfilled handler).
|
|
109
|
+
*/
|
|
110
|
+
declare function attachAxiosMonitor(axiosInstance: AxiosInstance, monitor: Monitor, opts?: AxiosMonitorOptions): void;
|
|
111
|
+
|
|
112
|
+
export { type AxiosMonitorOptions, type EmitOptions, type LogLevel, Monitor, type MonitorConfig, type MonitorEvent, attachAxiosMonitor };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
type LogLevel = "debug" | "info" | "warn" | "error" | "fatal";
|
|
2
|
+
interface MonitorConfig {
|
|
3
|
+
/** Service name reported with every event */
|
|
4
|
+
service: string;
|
|
5
|
+
/** Monitor ingest URL (e.g. https://monitor-ingest.appleby.cloud/v1/events) */
|
|
6
|
+
ingestUrl: string;
|
|
7
|
+
/** API key for authentication */
|
|
8
|
+
apiKey: string;
|
|
9
|
+
/** Environment name (default: "production") */
|
|
10
|
+
env?: string;
|
|
11
|
+
/** Flush interval in milliseconds (default: 2000) */
|
|
12
|
+
flushInterval?: number;
|
|
13
|
+
/** Max batch size before auto-flush (default: 20) */
|
|
14
|
+
batchSize?: number;
|
|
15
|
+
/** Enable automatic error capture via window.onerror (default: true) */
|
|
16
|
+
captureErrors?: boolean;
|
|
17
|
+
/** Enable automatic unhandled promise rejection capture (default: true) */
|
|
18
|
+
captureUnhandledRejections?: boolean;
|
|
19
|
+
/** Enable debug logging to console (default: false) */
|
|
20
|
+
debug?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface MonitorEvent {
|
|
23
|
+
timestamp: string;
|
|
24
|
+
service: string;
|
|
25
|
+
env: string;
|
|
26
|
+
job_id: string;
|
|
27
|
+
request_id: string;
|
|
28
|
+
trace_id: string;
|
|
29
|
+
user_id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
level: string;
|
|
32
|
+
data: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
interface EmitOptions {
|
|
35
|
+
/** Request ID for cross-service correlation */
|
|
36
|
+
requestId?: string;
|
|
37
|
+
/** Trace ID for distributed tracing */
|
|
38
|
+
traceId?: string;
|
|
39
|
+
/** User ID associated with this event */
|
|
40
|
+
userId?: string;
|
|
41
|
+
/** Arbitrary data payload */
|
|
42
|
+
data?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare class Monitor {
|
|
46
|
+
private config;
|
|
47
|
+
private queue;
|
|
48
|
+
private timer;
|
|
49
|
+
private userId;
|
|
50
|
+
private jobId;
|
|
51
|
+
private active;
|
|
52
|
+
constructor(config: MonitorConfig);
|
|
53
|
+
/** Set a persistent user ID for all subsequent events */
|
|
54
|
+
setUser(userId: string): void;
|
|
55
|
+
/** Clear the user ID */
|
|
56
|
+
clearUser(): void;
|
|
57
|
+
/** Set a persistent job ID (session-level identifier) */
|
|
58
|
+
setJobId(jobId: string): void;
|
|
59
|
+
/** Emit an event at a specific level */
|
|
60
|
+
emit(name: string, level: LogLevel, opts?: EmitOptions): void;
|
|
61
|
+
/** Emit a debug event */
|
|
62
|
+
debug(name: string, opts?: EmitOptions): void;
|
|
63
|
+
/** Emit an info event */
|
|
64
|
+
info(name: string, opts?: EmitOptions): void;
|
|
65
|
+
/** Emit a warning event */
|
|
66
|
+
warn(name: string, opts?: EmitOptions): void;
|
|
67
|
+
/** Emit an error event */
|
|
68
|
+
error(name: string, opts?: EmitOptions): void;
|
|
69
|
+
/** Emit a fatal event */
|
|
70
|
+
fatal(name: string, opts?: EmitOptions): void;
|
|
71
|
+
/** Flush all queued events to the ingest endpoint */
|
|
72
|
+
flush(): void;
|
|
73
|
+
/** Stop the monitor and flush remaining events */
|
|
74
|
+
shutdown(): void;
|
|
75
|
+
private start;
|
|
76
|
+
private handleVisibilityChange;
|
|
77
|
+
private handlePageHide;
|
|
78
|
+
private errorHandler;
|
|
79
|
+
private rejectionHandler;
|
|
80
|
+
private installErrorHandler;
|
|
81
|
+
private installRejectionHandler;
|
|
82
|
+
private removeListeners;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface AxiosInstance {
|
|
86
|
+
interceptors: {
|
|
87
|
+
request: {
|
|
88
|
+
use: (onFulfilled: (config: any) => any) => void;
|
|
89
|
+
};
|
|
90
|
+
response: {
|
|
91
|
+
use: (onFulfilled: (response: any) => any, onRejected: (error: any) => any) => void;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
interface AxiosMonitorOptions {
|
|
96
|
+
/** Only report events for responses with these status codes or above (default: 400) */
|
|
97
|
+
minStatus?: number;
|
|
98
|
+
/** Report successful requests too (default: false) */
|
|
99
|
+
reportSuccess?: boolean;
|
|
100
|
+
/** Paths to ignore (e.g. ["/healthcheck", "/api/health"]) */
|
|
101
|
+
ignorePaths?: string[];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Attaches Monitor interceptors to an Axios instance.
|
|
105
|
+
* Automatically reports API failures with request_id correlation.
|
|
106
|
+
*
|
|
107
|
+
* Works with both standard axios error handling AND `validateStatus: () => true`
|
|
108
|
+
* (where all HTTP responses go through the fulfilled handler).
|
|
109
|
+
*/
|
|
110
|
+
declare function attachAxiosMonitor(axiosInstance: AxiosInstance, monitor: Monitor, opts?: AxiosMonitorOptions): void;
|
|
111
|
+
|
|
112
|
+
export { type AxiosMonitorOptions, type EmitOptions, type LogLevel, Monitor, type MonitorConfig, type MonitorEvent, attachAxiosMonitor };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Monitor: () => Monitor,
|
|
24
|
+
attachAxiosMonitor: () => attachAxiosMonitor
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/client.ts
|
|
29
|
+
var DEFAULT_FLUSH_INTERVAL = 2e3;
|
|
30
|
+
var DEFAULT_BATCH_SIZE = 20;
|
|
31
|
+
var MAX_QUEUE_SIZE = 500;
|
|
32
|
+
var Monitor = class {
|
|
33
|
+
config;
|
|
34
|
+
queue = [];
|
|
35
|
+
timer = null;
|
|
36
|
+
userId = "";
|
|
37
|
+
jobId = "";
|
|
38
|
+
active = false;
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this.config = {
|
|
41
|
+
service: config.service,
|
|
42
|
+
ingestUrl: config.ingestUrl,
|
|
43
|
+
apiKey: config.apiKey,
|
|
44
|
+
env: config.env ?? "production",
|
|
45
|
+
flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
46
|
+
batchSize: config.batchSize ?? DEFAULT_BATCH_SIZE,
|
|
47
|
+
debug: config.debug ?? false
|
|
48
|
+
};
|
|
49
|
+
this.start();
|
|
50
|
+
if (config.captureErrors !== false) {
|
|
51
|
+
this.installErrorHandler();
|
|
52
|
+
}
|
|
53
|
+
if (config.captureUnhandledRejections !== false) {
|
|
54
|
+
this.installRejectionHandler();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Set a persistent user ID for all subsequent events */
|
|
58
|
+
setUser(userId) {
|
|
59
|
+
this.userId = userId;
|
|
60
|
+
}
|
|
61
|
+
/** Clear the user ID */
|
|
62
|
+
clearUser() {
|
|
63
|
+
this.userId = "";
|
|
64
|
+
}
|
|
65
|
+
/** Set a persistent job ID (session-level identifier) */
|
|
66
|
+
setJobId(jobId) {
|
|
67
|
+
this.jobId = jobId;
|
|
68
|
+
}
|
|
69
|
+
/** Emit an event at a specific level */
|
|
70
|
+
emit(name, level, opts) {
|
|
71
|
+
if (!this.active) return;
|
|
72
|
+
const event = {
|
|
73
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
74
|
+
service: this.config.service,
|
|
75
|
+
env: this.config.env,
|
|
76
|
+
job_id: this.jobId,
|
|
77
|
+
request_id: opts?.requestId ?? "",
|
|
78
|
+
trace_id: opts?.traceId ?? "",
|
|
79
|
+
user_id: opts?.userId ?? this.userId,
|
|
80
|
+
name,
|
|
81
|
+
level,
|
|
82
|
+
data: opts?.data ?? {}
|
|
83
|
+
};
|
|
84
|
+
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
85
|
+
this.queue.shift();
|
|
86
|
+
}
|
|
87
|
+
this.queue.push(event);
|
|
88
|
+
if (this.config.debug) {
|
|
89
|
+
console.debug(`[monitor] ${level} ${name}`, opts?.data);
|
|
90
|
+
}
|
|
91
|
+
if (this.queue.length >= this.config.batchSize) {
|
|
92
|
+
this.flush();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Emit a debug event */
|
|
96
|
+
debug(name, opts) {
|
|
97
|
+
this.emit(name, "debug", opts);
|
|
98
|
+
}
|
|
99
|
+
/** Emit an info event */
|
|
100
|
+
info(name, opts) {
|
|
101
|
+
this.emit(name, "info", opts);
|
|
102
|
+
}
|
|
103
|
+
/** Emit a warning event */
|
|
104
|
+
warn(name, opts) {
|
|
105
|
+
this.emit(name, "warn", opts);
|
|
106
|
+
}
|
|
107
|
+
/** Emit an error event */
|
|
108
|
+
error(name, opts) {
|
|
109
|
+
this.emit(name, "error", opts);
|
|
110
|
+
}
|
|
111
|
+
/** Emit a fatal event */
|
|
112
|
+
fatal(name, opts) {
|
|
113
|
+
this.emit(name, "fatal", opts);
|
|
114
|
+
}
|
|
115
|
+
/** Flush all queued events to the ingest endpoint */
|
|
116
|
+
flush() {
|
|
117
|
+
if (this.queue.length === 0) return;
|
|
118
|
+
const batch = this.queue.splice(0);
|
|
119
|
+
const payload = batch.map((e) => JSON.stringify(e)).join("\n");
|
|
120
|
+
if (typeof fetch === "undefined") return;
|
|
121
|
+
fetch(this.config.ingestUrl, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: {
|
|
124
|
+
"Content-Type": "application/x-ndjson",
|
|
125
|
+
"X-Api-Key": this.config.apiKey
|
|
126
|
+
},
|
|
127
|
+
body: payload,
|
|
128
|
+
keepalive: true
|
|
129
|
+
}).catch((err) => {
|
|
130
|
+
if (this.config.debug) {
|
|
131
|
+
console.warn("[monitor] flush failed:", err);
|
|
132
|
+
}
|
|
133
|
+
if (this.queue.length + batch.length <= MAX_QUEUE_SIZE) {
|
|
134
|
+
this.queue = batch.concat(this.queue);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/** Stop the monitor and flush remaining events */
|
|
139
|
+
shutdown() {
|
|
140
|
+
if (this.timer) {
|
|
141
|
+
clearInterval(this.timer);
|
|
142
|
+
this.timer = null;
|
|
143
|
+
}
|
|
144
|
+
this.flush();
|
|
145
|
+
this.removeListeners();
|
|
146
|
+
this.active = false;
|
|
147
|
+
}
|
|
148
|
+
start() {
|
|
149
|
+
if (this.active) return;
|
|
150
|
+
this.active = true;
|
|
151
|
+
this.timer = setInterval(() => this.flush(), this.config.flushInterval);
|
|
152
|
+
if (typeof document !== "undefined") {
|
|
153
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
154
|
+
}
|
|
155
|
+
if (typeof window !== "undefined") {
|
|
156
|
+
window.addEventListener("pagehide", this.handlePageHide);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
handleVisibilityChange = () => {
|
|
160
|
+
if (document.visibilityState === "hidden") {
|
|
161
|
+
this.flush();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
handlePageHide = () => {
|
|
165
|
+
this.flush();
|
|
166
|
+
};
|
|
167
|
+
errorHandler = (event) => {
|
|
168
|
+
this.emit("client.error.uncaught", "error", {
|
|
169
|
+
data: {
|
|
170
|
+
message: event.message,
|
|
171
|
+
filename: event.filename,
|
|
172
|
+
lineno: event.lineno,
|
|
173
|
+
colno: event.colno,
|
|
174
|
+
stack: event.error?.stack
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
rejectionHandler = (event) => {
|
|
179
|
+
const reason = event.reason;
|
|
180
|
+
this.emit("client.error.unhandled_rejection", "error", {
|
|
181
|
+
data: {
|
|
182
|
+
message: reason?.message ?? String(reason),
|
|
183
|
+
stack: reason?.stack
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
installErrorHandler() {
|
|
188
|
+
if (typeof window !== "undefined") {
|
|
189
|
+
window.addEventListener("error", this.errorHandler);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
installRejectionHandler() {
|
|
193
|
+
if (typeof window !== "undefined") {
|
|
194
|
+
window.addEventListener("unhandledrejection", this.rejectionHandler);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
removeListeners() {
|
|
198
|
+
if (typeof window !== "undefined") {
|
|
199
|
+
window.removeEventListener("error", this.errorHandler);
|
|
200
|
+
window.removeEventListener("unhandledrejection", this.rejectionHandler);
|
|
201
|
+
window.removeEventListener("pagehide", this.handlePageHide);
|
|
202
|
+
}
|
|
203
|
+
if (typeof document !== "undefined") {
|
|
204
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/axios.ts
|
|
210
|
+
function attachAxiosMonitor(axiosInstance, monitor, opts) {
|
|
211
|
+
const minStatus = opts?.minStatus ?? 400;
|
|
212
|
+
const reportSuccess = opts?.reportSuccess ?? false;
|
|
213
|
+
const ignorePaths = opts?.ignorePaths ?? [];
|
|
214
|
+
axiosInstance.interceptors.request.use((config) => {
|
|
215
|
+
config.metadata = { startTime: Date.now() };
|
|
216
|
+
return config;
|
|
217
|
+
});
|
|
218
|
+
axiosInstance.interceptors.response.use(
|
|
219
|
+
(response) => {
|
|
220
|
+
const url = response.config?.url ?? "";
|
|
221
|
+
if (ignorePaths.some((p) => url.includes(p))) return response;
|
|
222
|
+
const statusCode = response.status ?? 0;
|
|
223
|
+
const requestId = response.headers?.["x-request-id"] ?? "";
|
|
224
|
+
const durationMs = response.config?.metadata?.startTime ? Date.now() - response.config.metadata.startTime : void 0;
|
|
225
|
+
if (statusCode >= minStatus) {
|
|
226
|
+
const level = statusCode >= 500 ? "error" : "warn";
|
|
227
|
+
const name = statusCode >= 500 ? "api.request.server_error" : "api.request.client_error";
|
|
228
|
+
monitor.emit(name, level, {
|
|
229
|
+
requestId,
|
|
230
|
+
data: {
|
|
231
|
+
method: (response.config?.method ?? "").toUpperCase(),
|
|
232
|
+
url,
|
|
233
|
+
status_code: statusCode,
|
|
234
|
+
error: response.data?.error,
|
|
235
|
+
error_message: response.data?.error_message,
|
|
236
|
+
duration_ms: durationMs
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
return response;
|
|
240
|
+
}
|
|
241
|
+
if (reportSuccess && statusCode > 0) {
|
|
242
|
+
monitor.info("api.request.success", {
|
|
243
|
+
requestId,
|
|
244
|
+
data: {
|
|
245
|
+
method: (response.config?.method ?? "").toUpperCase(),
|
|
246
|
+
url,
|
|
247
|
+
status_code: statusCode,
|
|
248
|
+
duration_ms: durationMs
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return response;
|
|
253
|
+
},
|
|
254
|
+
(error) => {
|
|
255
|
+
const url = error.config?.url ?? "";
|
|
256
|
+
if (ignorePaths.some((p) => url.includes(p))) {
|
|
257
|
+
return Promise.reject(error);
|
|
258
|
+
}
|
|
259
|
+
const durationMs = error.config?.metadata?.startTime ? Date.now() - error.config.metadata.startTime : void 0;
|
|
260
|
+
if (!error.response) {
|
|
261
|
+
monitor.error("api.request.network_error", {
|
|
262
|
+
data: {
|
|
263
|
+
method: (error.config?.method ?? "").toUpperCase(),
|
|
264
|
+
url,
|
|
265
|
+
error_code: error.code,
|
|
266
|
+
error_message: error.message,
|
|
267
|
+
duration_ms: durationMs
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
return Promise.reject(error);
|
|
271
|
+
}
|
|
272
|
+
const statusCode = error.response.status ?? 0;
|
|
273
|
+
const requestId = error.response.headers?.["x-request-id"] ?? "";
|
|
274
|
+
if (statusCode >= minStatus) {
|
|
275
|
+
const level = statusCode >= 500 ? "error" : "warn";
|
|
276
|
+
const name = statusCode >= 500 ? "api.request.server_error" : "api.request.client_error";
|
|
277
|
+
monitor.emit(name, level, {
|
|
278
|
+
requestId,
|
|
279
|
+
data: {
|
|
280
|
+
method: (error.config?.method ?? "").toUpperCase(),
|
|
281
|
+
url,
|
|
282
|
+
status_code: statusCode,
|
|
283
|
+
error: error.response.data?.error,
|
|
284
|
+
error_message: error.response.data?.error_message,
|
|
285
|
+
duration_ms: durationMs
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return Promise.reject(error);
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
294
|
+
0 && (module.exports = {
|
|
295
|
+
Monitor,
|
|
296
|
+
attachAxiosMonitor
|
|
297
|
+
});
|
|
298
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/axios.ts"],"sourcesContent":["export { Monitor } from \"./client\";\nexport { attachAxiosMonitor } from \"./axios\";\nexport type { MonitorConfig, MonitorEvent, EmitOptions, LogLevel } from \"./types\";\nexport type { AxiosMonitorOptions } from \"./axios\";\n","import type { MonitorConfig, MonitorEvent, EmitOptions, LogLevel } from \"./types\";\n\nconst DEFAULT_FLUSH_INTERVAL = 2000;\nconst DEFAULT_BATCH_SIZE = 20;\nconst MAX_QUEUE_SIZE = 500;\n\nexport class Monitor {\n private config: Required<\n Pick<MonitorConfig, \"service\" | \"ingestUrl\" | \"apiKey\" | \"env\" | \"flushInterval\" | \"batchSize\" | \"debug\">\n >;\n private queue: MonitorEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private userId: string = \"\";\n private jobId: string = \"\";\n private active = false;\n\n constructor(config: MonitorConfig) {\n this.config = {\n service: config.service,\n ingestUrl: config.ingestUrl,\n apiKey: config.apiKey,\n env: config.env ?? \"production\",\n flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,\n batchSize: config.batchSize ?? DEFAULT_BATCH_SIZE,\n debug: config.debug ?? false,\n };\n\n this.start();\n\n if (config.captureErrors !== false) {\n this.installErrorHandler();\n }\n if (config.captureUnhandledRejections !== false) {\n this.installRejectionHandler();\n }\n }\n\n /** Set a persistent user ID for all subsequent events */\n setUser(userId: string): void {\n this.userId = userId;\n }\n\n /** Clear the user ID */\n clearUser(): void {\n this.userId = \"\";\n }\n\n /** Set a persistent job ID (session-level identifier) */\n setJobId(jobId: string): void {\n this.jobId = jobId;\n }\n\n /** Emit an event at a specific level */\n emit(name: string, level: LogLevel, opts?: EmitOptions): void {\n if (!this.active) return;\n\n const event: MonitorEvent = {\n timestamp: new Date().toISOString(),\n service: this.config.service,\n env: this.config.env,\n job_id: this.jobId,\n request_id: opts?.requestId ?? \"\",\n trace_id: opts?.traceId ?? \"\",\n user_id: opts?.userId ?? this.userId,\n name,\n level,\n data: opts?.data ?? {},\n };\n\n if (this.queue.length >= MAX_QUEUE_SIZE) {\n // Drop oldest events to prevent unbounded memory growth\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.config.debug) {\n console.debug(`[monitor] ${level} ${name}`, opts?.data);\n }\n\n if (this.queue.length >= this.config.batchSize) {\n this.flush();\n }\n }\n\n /** Emit a debug event */\n debug(name: string, opts?: EmitOptions): void {\n this.emit(name, \"debug\", opts);\n }\n\n /** Emit an info event */\n info(name: string, opts?: EmitOptions): void {\n this.emit(name, \"info\", opts);\n }\n\n /** Emit a warning event */\n warn(name: string, opts?: EmitOptions): void {\n this.emit(name, \"warn\", opts);\n }\n\n /** Emit an error event */\n error(name: string, opts?: EmitOptions): void {\n this.emit(name, \"error\", opts);\n }\n\n /** Emit a fatal event */\n fatal(name: string, opts?: EmitOptions): void {\n this.emit(name, \"fatal\", opts);\n }\n\n /** Flush all queued events to the ingest endpoint */\n flush(): void {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0);\n const payload = batch.map((e) => JSON.stringify(e)).join(\"\\n\");\n\n if (typeof fetch === \"undefined\") return;\n\n fetch(this.config.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-ndjson\",\n \"X-Api-Key\": this.config.apiKey,\n },\n body: payload,\n keepalive: true,\n }).catch((err) => {\n if (this.config.debug) {\n console.warn(\"[monitor] flush failed:\", err);\n }\n // Re-queue failed events if there's room\n if (this.queue.length + batch.length <= MAX_QUEUE_SIZE) {\n this.queue = batch.concat(this.queue);\n }\n });\n }\n\n /** Stop the monitor and flush remaining events */\n shutdown(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.flush();\n this.removeListeners();\n this.active = false;\n }\n\n private start(): void {\n if (this.active) return;\n this.active = true;\n\n this.timer = setInterval(() => this.flush(), this.config.flushInterval);\n\n if (typeof document !== \"undefined\") {\n document.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n }\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"pagehide\", this.handlePageHide);\n }\n }\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === \"hidden\") {\n this.flush();\n }\n };\n\n private handlePageHide = (): void => {\n this.flush();\n };\n\n private errorHandler = (event: ErrorEvent): void => {\n this.emit(\"client.error.uncaught\", \"error\", {\n data: {\n message: event.message,\n filename: event.filename,\n lineno: event.lineno,\n colno: event.colno,\n stack: event.error?.stack,\n },\n });\n };\n\n private rejectionHandler = (event: PromiseRejectionEvent): void => {\n const reason = event.reason;\n this.emit(\"client.error.unhandled_rejection\", \"error\", {\n data: {\n message: reason?.message ?? String(reason),\n stack: reason?.stack,\n },\n });\n };\n\n private installErrorHandler(): void {\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"error\", this.errorHandler);\n }\n }\n\n private installRejectionHandler(): void {\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"unhandledrejection\", this.rejectionHandler);\n }\n }\n\n private removeListeners(): void {\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"error\", this.errorHandler);\n window.removeEventListener(\"unhandledrejection\", this.rejectionHandler);\n window.removeEventListener(\"pagehide\", this.handlePageHide);\n }\n if (typeof document !== \"undefined\") {\n document.removeEventListener(\"visibilitychange\", this.handleVisibilityChange);\n }\n }\n}\n","import type { Monitor } from \"./client\";\nimport type { LogLevel } from \"./types\";\n\ninterface AxiosInstance {\n interceptors: {\n request: { use: (onFulfilled: (config: any) => any) => void };\n response: {\n use: (onFulfilled: (response: any) => any, onRejected: (error: any) => any) => void;\n };\n };\n}\n\nexport interface AxiosMonitorOptions {\n /** Only report events for responses with these status codes or above (default: 400) */\n minStatus?: number;\n /** Report successful requests too (default: false) */\n reportSuccess?: boolean;\n /** Paths to ignore (e.g. [\"/healthcheck\", \"/api/health\"]) */\n ignorePaths?: string[];\n}\n\n/**\n * Attaches Monitor interceptors to an Axios instance.\n * Automatically reports API failures with request_id correlation.\n *\n * Works with both standard axios error handling AND `validateStatus: () => true`\n * (where all HTTP responses go through the fulfilled handler).\n */\nexport function attachAxiosMonitor(\n axiosInstance: AxiosInstance,\n monitor: Monitor,\n opts?: AxiosMonitorOptions\n): void {\n const minStatus = opts?.minStatus ?? 400;\n const reportSuccess = opts?.reportSuccess ?? false;\n const ignorePaths = opts?.ignorePaths ?? [];\n\n // Stamp request start time\n axiosInstance.interceptors.request.use((config: any) => {\n config.metadata = { startTime: Date.now() };\n return config;\n });\n\n axiosInstance.interceptors.response.use(\n (response: any) => {\n const url: string = response.config?.url ?? \"\";\n if (ignorePaths.some((p) => url.includes(p))) return response;\n\n const statusCode: number = response.status ?? 0;\n const requestId: string = response.headers?.[\"x-request-id\"] ?? \"\";\n const durationMs = response.config?.metadata?.startTime\n ? Date.now() - response.config.metadata.startTime\n : undefined;\n\n // Handle error responses that come through fulfilled handler\n // (when validateStatus: () => true is used)\n if (statusCode >= minStatus) {\n const level: LogLevel = statusCode >= 500 ? \"error\" : \"warn\";\n const name =\n statusCode >= 500 ? \"api.request.server_error\" : \"api.request.client_error\";\n\n monitor.emit(name, level, {\n requestId,\n data: {\n method: (response.config?.method ?? \"\").toUpperCase(),\n url,\n status_code: statusCode,\n error: response.data?.error,\n error_message: response.data?.error_message,\n duration_ms: durationMs,\n },\n });\n\n return response;\n }\n\n // Report successful requests if enabled\n if (reportSuccess && statusCode > 0) {\n monitor.info(\"api.request.success\", {\n requestId,\n data: {\n method: (response.config?.method ?? \"\").toUpperCase(),\n url,\n status_code: statusCode,\n duration_ms: durationMs,\n },\n });\n }\n\n return response;\n },\n (error: any) => {\n const url: string = error.config?.url ?? \"\";\n if (ignorePaths.some((p) => url.includes(p))) {\n return Promise.reject(error);\n }\n\n const durationMs = error.config?.metadata?.startTime\n ? Date.now() - error.config.metadata.startTime\n : undefined;\n\n // Network errors (no response — timeout, DNS failure, CORS blocked)\n if (!error.response) {\n monitor.error(\"api.request.network_error\", {\n data: {\n method: (error.config?.method ?? \"\").toUpperCase(),\n url,\n error_code: error.code,\n error_message: error.message,\n duration_ms: durationMs,\n },\n });\n return Promise.reject(error);\n }\n\n // HTTP errors (when validateStatus is default — throws on non-2xx)\n const statusCode: number = error.response.status ?? 0;\n const requestId: string = error.response.headers?.[\"x-request-id\"] ?? \"\";\n\n if (statusCode >= minStatus) {\n const level: LogLevel = statusCode >= 500 ? \"error\" : \"warn\";\n const name =\n statusCode >= 500 ? \"api.request.server_error\" : \"api.request.client_error\";\n\n monitor.emit(name, level, {\n requestId,\n data: {\n method: (error.config?.method ?? \"\").toUpperCase(),\n url,\n status_code: statusCode,\n error: error.response.data?.error,\n error_message: error.response.data?.error_message,\n duration_ms: durationMs,\n },\n });\n }\n\n return Promise.reject(error);\n }\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var DEFAULT_FLUSH_INTERVAL = 2e3;
|
|
3
|
+
var DEFAULT_BATCH_SIZE = 20;
|
|
4
|
+
var MAX_QUEUE_SIZE = 500;
|
|
5
|
+
var Monitor = class {
|
|
6
|
+
config;
|
|
7
|
+
queue = [];
|
|
8
|
+
timer = null;
|
|
9
|
+
userId = "";
|
|
10
|
+
jobId = "";
|
|
11
|
+
active = false;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = {
|
|
14
|
+
service: config.service,
|
|
15
|
+
ingestUrl: config.ingestUrl,
|
|
16
|
+
apiKey: config.apiKey,
|
|
17
|
+
env: config.env ?? "production",
|
|
18
|
+
flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,
|
|
19
|
+
batchSize: config.batchSize ?? DEFAULT_BATCH_SIZE,
|
|
20
|
+
debug: config.debug ?? false
|
|
21
|
+
};
|
|
22
|
+
this.start();
|
|
23
|
+
if (config.captureErrors !== false) {
|
|
24
|
+
this.installErrorHandler();
|
|
25
|
+
}
|
|
26
|
+
if (config.captureUnhandledRejections !== false) {
|
|
27
|
+
this.installRejectionHandler();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Set a persistent user ID for all subsequent events */
|
|
31
|
+
setUser(userId) {
|
|
32
|
+
this.userId = userId;
|
|
33
|
+
}
|
|
34
|
+
/** Clear the user ID */
|
|
35
|
+
clearUser() {
|
|
36
|
+
this.userId = "";
|
|
37
|
+
}
|
|
38
|
+
/** Set a persistent job ID (session-level identifier) */
|
|
39
|
+
setJobId(jobId) {
|
|
40
|
+
this.jobId = jobId;
|
|
41
|
+
}
|
|
42
|
+
/** Emit an event at a specific level */
|
|
43
|
+
emit(name, level, opts) {
|
|
44
|
+
if (!this.active) return;
|
|
45
|
+
const event = {
|
|
46
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47
|
+
service: this.config.service,
|
|
48
|
+
env: this.config.env,
|
|
49
|
+
job_id: this.jobId,
|
|
50
|
+
request_id: opts?.requestId ?? "",
|
|
51
|
+
trace_id: opts?.traceId ?? "",
|
|
52
|
+
user_id: opts?.userId ?? this.userId,
|
|
53
|
+
name,
|
|
54
|
+
level,
|
|
55
|
+
data: opts?.data ?? {}
|
|
56
|
+
};
|
|
57
|
+
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
|
58
|
+
this.queue.shift();
|
|
59
|
+
}
|
|
60
|
+
this.queue.push(event);
|
|
61
|
+
if (this.config.debug) {
|
|
62
|
+
console.debug(`[monitor] ${level} ${name}`, opts?.data);
|
|
63
|
+
}
|
|
64
|
+
if (this.queue.length >= this.config.batchSize) {
|
|
65
|
+
this.flush();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Emit a debug event */
|
|
69
|
+
debug(name, opts) {
|
|
70
|
+
this.emit(name, "debug", opts);
|
|
71
|
+
}
|
|
72
|
+
/** Emit an info event */
|
|
73
|
+
info(name, opts) {
|
|
74
|
+
this.emit(name, "info", opts);
|
|
75
|
+
}
|
|
76
|
+
/** Emit a warning event */
|
|
77
|
+
warn(name, opts) {
|
|
78
|
+
this.emit(name, "warn", opts);
|
|
79
|
+
}
|
|
80
|
+
/** Emit an error event */
|
|
81
|
+
error(name, opts) {
|
|
82
|
+
this.emit(name, "error", opts);
|
|
83
|
+
}
|
|
84
|
+
/** Emit a fatal event */
|
|
85
|
+
fatal(name, opts) {
|
|
86
|
+
this.emit(name, "fatal", opts);
|
|
87
|
+
}
|
|
88
|
+
/** Flush all queued events to the ingest endpoint */
|
|
89
|
+
flush() {
|
|
90
|
+
if (this.queue.length === 0) return;
|
|
91
|
+
const batch = this.queue.splice(0);
|
|
92
|
+
const payload = batch.map((e) => JSON.stringify(e)).join("\n");
|
|
93
|
+
if (typeof fetch === "undefined") return;
|
|
94
|
+
fetch(this.config.ingestUrl, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/x-ndjson",
|
|
98
|
+
"X-Api-Key": this.config.apiKey
|
|
99
|
+
},
|
|
100
|
+
body: payload,
|
|
101
|
+
keepalive: true
|
|
102
|
+
}).catch((err) => {
|
|
103
|
+
if (this.config.debug) {
|
|
104
|
+
console.warn("[monitor] flush failed:", err);
|
|
105
|
+
}
|
|
106
|
+
if (this.queue.length + batch.length <= MAX_QUEUE_SIZE) {
|
|
107
|
+
this.queue = batch.concat(this.queue);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/** Stop the monitor and flush remaining events */
|
|
112
|
+
shutdown() {
|
|
113
|
+
if (this.timer) {
|
|
114
|
+
clearInterval(this.timer);
|
|
115
|
+
this.timer = null;
|
|
116
|
+
}
|
|
117
|
+
this.flush();
|
|
118
|
+
this.removeListeners();
|
|
119
|
+
this.active = false;
|
|
120
|
+
}
|
|
121
|
+
start() {
|
|
122
|
+
if (this.active) return;
|
|
123
|
+
this.active = true;
|
|
124
|
+
this.timer = setInterval(() => this.flush(), this.config.flushInterval);
|
|
125
|
+
if (typeof document !== "undefined") {
|
|
126
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
127
|
+
}
|
|
128
|
+
if (typeof window !== "undefined") {
|
|
129
|
+
window.addEventListener("pagehide", this.handlePageHide);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
handleVisibilityChange = () => {
|
|
133
|
+
if (document.visibilityState === "hidden") {
|
|
134
|
+
this.flush();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
handlePageHide = () => {
|
|
138
|
+
this.flush();
|
|
139
|
+
};
|
|
140
|
+
errorHandler = (event) => {
|
|
141
|
+
this.emit("client.error.uncaught", "error", {
|
|
142
|
+
data: {
|
|
143
|
+
message: event.message,
|
|
144
|
+
filename: event.filename,
|
|
145
|
+
lineno: event.lineno,
|
|
146
|
+
colno: event.colno,
|
|
147
|
+
stack: event.error?.stack
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
rejectionHandler = (event) => {
|
|
152
|
+
const reason = event.reason;
|
|
153
|
+
this.emit("client.error.unhandled_rejection", "error", {
|
|
154
|
+
data: {
|
|
155
|
+
message: reason?.message ?? String(reason),
|
|
156
|
+
stack: reason?.stack
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
installErrorHandler() {
|
|
161
|
+
if (typeof window !== "undefined") {
|
|
162
|
+
window.addEventListener("error", this.errorHandler);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
installRejectionHandler() {
|
|
166
|
+
if (typeof window !== "undefined") {
|
|
167
|
+
window.addEventListener("unhandledrejection", this.rejectionHandler);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
removeListeners() {
|
|
171
|
+
if (typeof window !== "undefined") {
|
|
172
|
+
window.removeEventListener("error", this.errorHandler);
|
|
173
|
+
window.removeEventListener("unhandledrejection", this.rejectionHandler);
|
|
174
|
+
window.removeEventListener("pagehide", this.handlePageHide);
|
|
175
|
+
}
|
|
176
|
+
if (typeof document !== "undefined") {
|
|
177
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// src/axios.ts
|
|
183
|
+
function attachAxiosMonitor(axiosInstance, monitor, opts) {
|
|
184
|
+
const minStatus = opts?.minStatus ?? 400;
|
|
185
|
+
const reportSuccess = opts?.reportSuccess ?? false;
|
|
186
|
+
const ignorePaths = opts?.ignorePaths ?? [];
|
|
187
|
+
axiosInstance.interceptors.request.use((config) => {
|
|
188
|
+
config.metadata = { startTime: Date.now() };
|
|
189
|
+
return config;
|
|
190
|
+
});
|
|
191
|
+
axiosInstance.interceptors.response.use(
|
|
192
|
+
(response) => {
|
|
193
|
+
const url = response.config?.url ?? "";
|
|
194
|
+
if (ignorePaths.some((p) => url.includes(p))) return response;
|
|
195
|
+
const statusCode = response.status ?? 0;
|
|
196
|
+
const requestId = response.headers?.["x-request-id"] ?? "";
|
|
197
|
+
const durationMs = response.config?.metadata?.startTime ? Date.now() - response.config.metadata.startTime : void 0;
|
|
198
|
+
if (statusCode >= minStatus) {
|
|
199
|
+
const level = statusCode >= 500 ? "error" : "warn";
|
|
200
|
+
const name = statusCode >= 500 ? "api.request.server_error" : "api.request.client_error";
|
|
201
|
+
monitor.emit(name, level, {
|
|
202
|
+
requestId,
|
|
203
|
+
data: {
|
|
204
|
+
method: (response.config?.method ?? "").toUpperCase(),
|
|
205
|
+
url,
|
|
206
|
+
status_code: statusCode,
|
|
207
|
+
error: response.data?.error,
|
|
208
|
+
error_message: response.data?.error_message,
|
|
209
|
+
duration_ms: durationMs
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
return response;
|
|
213
|
+
}
|
|
214
|
+
if (reportSuccess && statusCode > 0) {
|
|
215
|
+
monitor.info("api.request.success", {
|
|
216
|
+
requestId,
|
|
217
|
+
data: {
|
|
218
|
+
method: (response.config?.method ?? "").toUpperCase(),
|
|
219
|
+
url,
|
|
220
|
+
status_code: statusCode,
|
|
221
|
+
duration_ms: durationMs
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return response;
|
|
226
|
+
},
|
|
227
|
+
(error) => {
|
|
228
|
+
const url = error.config?.url ?? "";
|
|
229
|
+
if (ignorePaths.some((p) => url.includes(p))) {
|
|
230
|
+
return Promise.reject(error);
|
|
231
|
+
}
|
|
232
|
+
const durationMs = error.config?.metadata?.startTime ? Date.now() - error.config.metadata.startTime : void 0;
|
|
233
|
+
if (!error.response) {
|
|
234
|
+
monitor.error("api.request.network_error", {
|
|
235
|
+
data: {
|
|
236
|
+
method: (error.config?.method ?? "").toUpperCase(),
|
|
237
|
+
url,
|
|
238
|
+
error_code: error.code,
|
|
239
|
+
error_message: error.message,
|
|
240
|
+
duration_ms: durationMs
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
return Promise.reject(error);
|
|
244
|
+
}
|
|
245
|
+
const statusCode = error.response.status ?? 0;
|
|
246
|
+
const requestId = error.response.headers?.["x-request-id"] ?? "";
|
|
247
|
+
if (statusCode >= minStatus) {
|
|
248
|
+
const level = statusCode >= 500 ? "error" : "warn";
|
|
249
|
+
const name = statusCode >= 500 ? "api.request.server_error" : "api.request.client_error";
|
|
250
|
+
monitor.emit(name, level, {
|
|
251
|
+
requestId,
|
|
252
|
+
data: {
|
|
253
|
+
method: (error.config?.method ?? "").toUpperCase(),
|
|
254
|
+
url,
|
|
255
|
+
status_code: statusCode,
|
|
256
|
+
error: error.response.data?.error,
|
|
257
|
+
error_message: error.response.data?.error_message,
|
|
258
|
+
duration_ms: durationMs
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return Promise.reject(error);
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
export {
|
|
267
|
+
Monitor,
|
|
268
|
+
attachAxiosMonitor
|
|
269
|
+
};
|
|
270
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/axios.ts"],"sourcesContent":["import type { MonitorConfig, MonitorEvent, EmitOptions, LogLevel } from \"./types\";\n\nconst DEFAULT_FLUSH_INTERVAL = 2000;\nconst DEFAULT_BATCH_SIZE = 20;\nconst MAX_QUEUE_SIZE = 500;\n\nexport class Monitor {\n private config: Required<\n Pick<MonitorConfig, \"service\" | \"ingestUrl\" | \"apiKey\" | \"env\" | \"flushInterval\" | \"batchSize\" | \"debug\">\n >;\n private queue: MonitorEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private userId: string = \"\";\n private jobId: string = \"\";\n private active = false;\n\n constructor(config: MonitorConfig) {\n this.config = {\n service: config.service,\n ingestUrl: config.ingestUrl,\n apiKey: config.apiKey,\n env: config.env ?? \"production\",\n flushInterval: config.flushInterval ?? DEFAULT_FLUSH_INTERVAL,\n batchSize: config.batchSize ?? DEFAULT_BATCH_SIZE,\n debug: config.debug ?? false,\n };\n\n this.start();\n\n if (config.captureErrors !== false) {\n this.installErrorHandler();\n }\n if (config.captureUnhandledRejections !== false) {\n this.installRejectionHandler();\n }\n }\n\n /** Set a persistent user ID for all subsequent events */\n setUser(userId: string): void {\n this.userId = userId;\n }\n\n /** Clear the user ID */\n clearUser(): void {\n this.userId = \"\";\n }\n\n /** Set a persistent job ID (session-level identifier) */\n setJobId(jobId: string): void {\n this.jobId = jobId;\n }\n\n /** Emit an event at a specific level */\n emit(name: string, level: LogLevel, opts?: EmitOptions): void {\n if (!this.active) return;\n\n const event: MonitorEvent = {\n timestamp: new Date().toISOString(),\n service: this.config.service,\n env: this.config.env,\n job_id: this.jobId,\n request_id: opts?.requestId ?? \"\",\n trace_id: opts?.traceId ?? \"\",\n user_id: opts?.userId ?? this.userId,\n name,\n level,\n data: opts?.data ?? {},\n };\n\n if (this.queue.length >= MAX_QUEUE_SIZE) {\n // Drop oldest events to prevent unbounded memory growth\n this.queue.shift();\n }\n\n this.queue.push(event);\n\n if (this.config.debug) {\n console.debug(`[monitor] ${level} ${name}`, opts?.data);\n }\n\n if (this.queue.length >= this.config.batchSize) {\n this.flush();\n }\n }\n\n /** Emit a debug event */\n debug(name: string, opts?: EmitOptions): void {\n this.emit(name, \"debug\", opts);\n }\n\n /** Emit an info event */\n info(name: string, opts?: EmitOptions): void {\n this.emit(name, \"info\", opts);\n }\n\n /** Emit a warning event */\n warn(name: string, opts?: EmitOptions): void {\n this.emit(name, \"warn\", opts);\n }\n\n /** Emit an error event */\n error(name: string, opts?: EmitOptions): void {\n this.emit(name, \"error\", opts);\n }\n\n /** Emit a fatal event */\n fatal(name: string, opts?: EmitOptions): void {\n this.emit(name, \"fatal\", opts);\n }\n\n /** Flush all queued events to the ingest endpoint */\n flush(): void {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0);\n const payload = batch.map((e) => JSON.stringify(e)).join(\"\\n\");\n\n if (typeof fetch === \"undefined\") return;\n\n fetch(this.config.ingestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-ndjson\",\n \"X-Api-Key\": this.config.apiKey,\n },\n body: payload,\n keepalive: true,\n }).catch((err) => {\n if (this.config.debug) {\n console.warn(\"[monitor] flush failed:\", err);\n }\n // Re-queue failed events if there's room\n if (this.queue.length + batch.length <= MAX_QUEUE_SIZE) {\n this.queue = batch.concat(this.queue);\n }\n });\n }\n\n /** Stop the monitor and flush remaining events */\n shutdown(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.flush();\n this.removeListeners();\n this.active = false;\n }\n\n private start(): void {\n if (this.active) return;\n this.active = true;\n\n this.timer = setInterval(() => this.flush(), this.config.flushInterval);\n\n if (typeof document !== \"undefined\") {\n document.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n }\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"pagehide\", this.handlePageHide);\n }\n }\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === \"hidden\") {\n this.flush();\n }\n };\n\n private handlePageHide = (): void => {\n this.flush();\n };\n\n private errorHandler = (event: ErrorEvent): void => {\n this.emit(\"client.error.uncaught\", \"error\", {\n data: {\n message: event.message,\n filename: event.filename,\n lineno: event.lineno,\n colno: event.colno,\n stack: event.error?.stack,\n },\n });\n };\n\n private rejectionHandler = (event: PromiseRejectionEvent): void => {\n const reason = event.reason;\n this.emit(\"client.error.unhandled_rejection\", \"error\", {\n data: {\n message: reason?.message ?? String(reason),\n stack: reason?.stack,\n },\n });\n };\n\n private installErrorHandler(): void {\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"error\", this.errorHandler);\n }\n }\n\n private installRejectionHandler(): void {\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"unhandledrejection\", this.rejectionHandler);\n }\n }\n\n private removeListeners(): void {\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"error\", this.errorHandler);\n window.removeEventListener(\"unhandledrejection\", this.rejectionHandler);\n window.removeEventListener(\"pagehide\", this.handlePageHide);\n }\n if (typeof document !== \"undefined\") {\n document.removeEventListener(\"visibilitychange\", this.handleVisibilityChange);\n }\n }\n}\n","import type { Monitor } from \"./client\";\nimport type { LogLevel } from \"./types\";\n\ninterface AxiosInstance {\n interceptors: {\n request: { use: (onFulfilled: (config: any) => any) => void };\n response: {\n use: (onFulfilled: (response: any) => any, onRejected: (error: any) => any) => void;\n };\n };\n}\n\nexport interface AxiosMonitorOptions {\n /** Only report events for responses with these status codes or above (default: 400) */\n minStatus?: number;\n /** Report successful requests too (default: false) */\n reportSuccess?: boolean;\n /** Paths to ignore (e.g. [\"/healthcheck\", \"/api/health\"]) */\n ignorePaths?: string[];\n}\n\n/**\n * Attaches Monitor interceptors to an Axios instance.\n * Automatically reports API failures with request_id correlation.\n *\n * Works with both standard axios error handling AND `validateStatus: () => true`\n * (where all HTTP responses go through the fulfilled handler).\n */\nexport function attachAxiosMonitor(\n axiosInstance: AxiosInstance,\n monitor: Monitor,\n opts?: AxiosMonitorOptions\n): void {\n const minStatus = opts?.minStatus ?? 400;\n const reportSuccess = opts?.reportSuccess ?? false;\n const ignorePaths = opts?.ignorePaths ?? [];\n\n // Stamp request start time\n axiosInstance.interceptors.request.use((config: any) => {\n config.metadata = { startTime: Date.now() };\n return config;\n });\n\n axiosInstance.interceptors.response.use(\n (response: any) => {\n const url: string = response.config?.url ?? \"\";\n if (ignorePaths.some((p) => url.includes(p))) return response;\n\n const statusCode: number = response.status ?? 0;\n const requestId: string = response.headers?.[\"x-request-id\"] ?? \"\";\n const durationMs = response.config?.metadata?.startTime\n ? Date.now() - response.config.metadata.startTime\n : undefined;\n\n // Handle error responses that come through fulfilled handler\n // (when validateStatus: () => true is used)\n if (statusCode >= minStatus) {\n const level: LogLevel = statusCode >= 500 ? \"error\" : \"warn\";\n const name =\n statusCode >= 500 ? \"api.request.server_error\" : \"api.request.client_error\";\n\n monitor.emit(name, level, {\n requestId,\n data: {\n method: (response.config?.method ?? \"\").toUpperCase(),\n url,\n status_code: statusCode,\n error: response.data?.error,\n error_message: response.data?.error_message,\n duration_ms: durationMs,\n },\n });\n\n return response;\n }\n\n // Report successful requests if enabled\n if (reportSuccess && statusCode > 0) {\n monitor.info(\"api.request.success\", {\n requestId,\n data: {\n method: (response.config?.method ?? \"\").toUpperCase(),\n url,\n status_code: statusCode,\n duration_ms: durationMs,\n },\n });\n }\n\n return response;\n },\n (error: any) => {\n const url: string = error.config?.url ?? \"\";\n if (ignorePaths.some((p) => url.includes(p))) {\n return Promise.reject(error);\n }\n\n const durationMs = error.config?.metadata?.startTime\n ? Date.now() - error.config.metadata.startTime\n : undefined;\n\n // Network errors (no response — timeout, DNS failure, CORS blocked)\n if (!error.response) {\n monitor.error(\"api.request.network_error\", {\n data: {\n method: (error.config?.method ?? \"\").toUpperCase(),\n url,\n error_code: error.code,\n error_message: error.message,\n duration_ms: durationMs,\n },\n });\n return Promise.reject(error);\n }\n\n // HTTP errors (when validateStatus is default — throws on non-2xx)\n const statusCode: number = error.response.status ?? 0;\n const requestId: string = error.response.headers?.[\"x-request-id\"] ?? \"\";\n\n if (statusCode >= minStatus) {\n const level: LogLevel = statusCode >= 500 ? \"error\" : \"warn\";\n const name =\n statusCode >= 500 ? \"api.request.server_error\" : \"api.request.client_error\";\n\n monitor.emit(name, level, {\n requestId,\n data: {\n method: (error.config?.method ?? \"\").toUpperCase(),\n url,\n status_code: statusCode,\n error: error.response.data?.error,\n error_message: error.response.data?.error_message,\n duration_ms: durationMs,\n },\n });\n }\n\n return Promise.reject(error);\n }\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aidenappleby/monitor-js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight JavaScript/TypeScript client for Monitor event ingestion",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"check": "tsc --noEmit",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"monitor",
|
|
26
|
+
"observability",
|
|
27
|
+
"events",
|
|
28
|
+
"logging",
|
|
29
|
+
"error-tracking"
|
|
30
|
+
],
|
|
31
|
+
"author": "aidenappl",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^5.4.0",
|
|
39
|
+
"vitest": "^3.0.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|