@guomain/monitor-core 0.1.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/README.md +27 -0
- package/index.d.ts +18 -0
- package/index.d.ts.map +1 -0
- package/index.js +246 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @gm Monitor SDK
|
|
2
|
+
|
|
3
|
+
前端错误监控:运行时错误、Promise、资源加载、接口异常、Vue/React、手动上报、catch 自动采集(需 Vite 插件)。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @guomain/monitor-web
|
|
9
|
+
pnpm add -D @guomain/monitor-plugins @guomain/monitor-types
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 快速开始
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createWebMonitor } from '@guomain/monitor-web'
|
|
16
|
+
|
|
17
|
+
const monitor = createWebMonitor({
|
|
18
|
+
appId: 'your-app',
|
|
19
|
+
dsn: '/api/monitor',
|
|
20
|
+
vue: { app }
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
globalThis.__GM_MONITOR__ = monitor
|
|
24
|
+
monitor.start()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
完整说明见仓库内 `SDK.md` 与 `PUBLISH.md`。
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MonitorConfig, MonitorEvent, MonitorEventMeta, MonitorInstance, MonitorLimits } from '@guomain/monitor-types';
|
|
2
|
+
/**
|
|
3
|
+
* 默认采集与发送阈值。
|
|
4
|
+
*/
|
|
5
|
+
export declare const defaultLimits: MonitorLimits;
|
|
6
|
+
/**
|
|
7
|
+
* 创建通用监控实例。
|
|
8
|
+
* @param config SDK 初始化配置,包含应用标识、上报地址、自定义发送函数、上下文和阈值。
|
|
9
|
+
*/
|
|
10
|
+
export declare function createMonitor(config: MonitorConfig): MonitorInstance;
|
|
11
|
+
/**
|
|
12
|
+
* 把任意错误值归一化为 SDK 事件。
|
|
13
|
+
* @param type 事件类型,用于标识错误来源。
|
|
14
|
+
* @param error 原始错误,可以是 Error、字符串或其他未知值。
|
|
15
|
+
* @param meta 附加 tags 和 extra。
|
|
16
|
+
*/
|
|
17
|
+
export declare function toErrorEvent(type: MonitorEvent['type'], error: unknown, meta?: MonitorEventMeta): MonitorEvent;
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAEb,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,aAAa,EAGd,MAAM,wBAAwB,CAAA;AAE/B;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,aAE3B,CAAA;AAID;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CA4LpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,EAC1B,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE,gBAAgB,GACtB,YAAY,CA8Bd"}
|
package/index.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 默认采集与发送阈值。
|
|
3
|
+
*/
|
|
4
|
+
export const defaultLimits = {
|
|
5
|
+
maxQueueSize: 50
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* 创建通用监控实例。
|
|
9
|
+
* @param config SDK 初始化配置,包含应用标识、上报地址、自定义发送函数、上下文和阈值。
|
|
10
|
+
*/
|
|
11
|
+
export function createMonitor(config) {
|
|
12
|
+
if (!config.appId) {
|
|
13
|
+
throw new Error('appId is required');
|
|
14
|
+
}
|
|
15
|
+
if (!(config.dsn || config.transport)) {
|
|
16
|
+
throw new Error('dsn or transport is required');
|
|
17
|
+
}
|
|
18
|
+
const mergedLimits = { ...defaultLimits, ...config.limits };
|
|
19
|
+
const plugins = [];
|
|
20
|
+
const pluginTeardowns = [];
|
|
21
|
+
const queue = [];
|
|
22
|
+
const queuedKeys = new Set();
|
|
23
|
+
let draining = false;
|
|
24
|
+
let idleDrainHandle;
|
|
25
|
+
let started = false;
|
|
26
|
+
async function defaultTransport(payloads) {
|
|
27
|
+
if (!config.dsn) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
31
|
+
const sent = navigator.sendBeacon(config.dsn, JSON.stringify(payloads));
|
|
32
|
+
if (sent) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// sendBeacon 不可用或返回 false 时回退到 fetch
|
|
37
|
+
await fetch(config.dsn, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
body: JSON.stringify(payloads),
|
|
40
|
+
headers: { 'Content-Type': 'application/json' }
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const setupPlugin = (plugin) => {
|
|
44
|
+
try {
|
|
45
|
+
const teardown = plugin.setup(monitor);
|
|
46
|
+
if (typeof teardown === 'function') {
|
|
47
|
+
pluginTeardowns.push(teardown);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
reportSdkError(monitor, error, {
|
|
52
|
+
stage: 'plugin',
|
|
53
|
+
pluginName: plugin.name
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const monitor = {
|
|
58
|
+
config,
|
|
59
|
+
limits: mergedLimits,
|
|
60
|
+
start() {
|
|
61
|
+
if (started) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
started = true;
|
|
65
|
+
plugins.forEach(plugin => {
|
|
66
|
+
setupPlugin(plugin);
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
stop() {
|
|
70
|
+
if (!started) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
started = false;
|
|
74
|
+
const tasks = pluginTeardowns.splice(0).reverse();
|
|
75
|
+
tasks.forEach(task => {
|
|
76
|
+
task();
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
use(plugin) {
|
|
80
|
+
plugins.push(plugin);
|
|
81
|
+
if (started) {
|
|
82
|
+
setupPlugin(plugin);
|
|
83
|
+
}
|
|
84
|
+
return monitor;
|
|
85
|
+
},
|
|
86
|
+
capture(event) {
|
|
87
|
+
if (!started) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const key = stringifyQueueEvent(event);
|
|
91
|
+
if (queuedKeys.has(key)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (queue.length >= mergedLimits.maxQueueSize) {
|
|
95
|
+
const removed = queue.shift();
|
|
96
|
+
if (removed) {
|
|
97
|
+
queuedKeys.delete(removed.key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
queuedKeys.add(key);
|
|
101
|
+
queue.push({ event, key });
|
|
102
|
+
scheduleIdleDrain();
|
|
103
|
+
},
|
|
104
|
+
async flush() {
|
|
105
|
+
await drainQueue();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
async function drainQueue() {
|
|
109
|
+
if (draining) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
cancelIdleDrain();
|
|
113
|
+
draining = true;
|
|
114
|
+
try {
|
|
115
|
+
const batch = queue.splice(0);
|
|
116
|
+
if (!batch.length) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
batch.forEach(item => {
|
|
120
|
+
queuedKeys.delete(item.key);
|
|
121
|
+
});
|
|
122
|
+
let context;
|
|
123
|
+
try {
|
|
124
|
+
if (config.context) {
|
|
125
|
+
context = typeof config.context === 'function' ? await config.context() : config.context;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
reportSdkError(monitor, error, { stage: 'context' });
|
|
130
|
+
}
|
|
131
|
+
const payloads = batch.map(({ event }) => ({
|
|
132
|
+
...event,
|
|
133
|
+
timestamp: event.timestamp ?? Date.now(),
|
|
134
|
+
appId: config.appId,
|
|
135
|
+
release: config.release,
|
|
136
|
+
context
|
|
137
|
+
}));
|
|
138
|
+
try {
|
|
139
|
+
await (config.transport ?? defaultTransport)(payloads);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
// transport 失败直接 console.error,不入队重入(避免 sdk-error 走同一故障通道导致风暴)
|
|
143
|
+
if (typeof console !== 'undefined' && console.error) {
|
|
144
|
+
console.error('[gm-monitor] Transport failed:', error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
draining = false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function scheduleIdleDrain() {
|
|
153
|
+
if (idleDrainHandle !== undefined) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (typeof requestIdleCallback === 'function') {
|
|
157
|
+
idleDrainHandle = requestIdleCallback(() => {
|
|
158
|
+
idleDrainHandle = undefined;
|
|
159
|
+
void drainQueue();
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
idleDrainHandle = setTimeout(() => {
|
|
164
|
+
idleDrainHandle = undefined;
|
|
165
|
+
void drainQueue();
|
|
166
|
+
}, 16);
|
|
167
|
+
}
|
|
168
|
+
function cancelIdleDrain() {
|
|
169
|
+
if (idleDrainHandle === undefined) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (typeof cancelIdleCallback === 'function') {
|
|
173
|
+
cancelIdleCallback(idleDrainHandle);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
clearTimeout(idleDrainHandle);
|
|
177
|
+
}
|
|
178
|
+
idleDrainHandle = undefined;
|
|
179
|
+
}
|
|
180
|
+
return monitor;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 把任意错误值归一化为 SDK 事件。
|
|
184
|
+
* @param type 事件类型,用于标识错误来源。
|
|
185
|
+
* @param error 原始错误,可以是 Error、字符串或其他未知值。
|
|
186
|
+
* @param meta 附加 tags 和 extra。
|
|
187
|
+
*/
|
|
188
|
+
export function toErrorEvent(type, error, meta) {
|
|
189
|
+
if (error instanceof Error) {
|
|
190
|
+
return {
|
|
191
|
+
type,
|
|
192
|
+
message: error.message,
|
|
193
|
+
stack: error.stack,
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
...meta
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
let message;
|
|
199
|
+
if (typeof error === 'string') {
|
|
200
|
+
message = error;
|
|
201
|
+
}
|
|
202
|
+
else if (error === undefined) {
|
|
203
|
+
message = 'undefined';
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
try {
|
|
207
|
+
message = JSON.stringify(error);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
message = String(error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
type,
|
|
215
|
+
message,
|
|
216
|
+
timestamp: Date.now(),
|
|
217
|
+
...meta
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function stringifyQueueEvent(event) {
|
|
221
|
+
return JSON.stringify({
|
|
222
|
+
type: event.type,
|
|
223
|
+
message: event.message,
|
|
224
|
+
url: event.url,
|
|
225
|
+
stack: event.stack,
|
|
226
|
+
filename: event.filename,
|
|
227
|
+
lineno: event.lineno,
|
|
228
|
+
colno: event.colno,
|
|
229
|
+
tags: event.tags,
|
|
230
|
+
extra: event.extra
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 上报 SDK 自身异常。同名 sdk-error 由 dedup 天然合并,不会风暴。
|
|
235
|
+
*/
|
|
236
|
+
function reportSdkError(monitor, error, extra) {
|
|
237
|
+
try {
|
|
238
|
+
monitor.capture(toErrorEvent('sdk-error', error, { extra }));
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// SDK 上报通道自身崩溃时静默吞掉,避免递归。
|
|
242
|
+
if (typeof console !== 'undefined' && console.error) {
|
|
243
|
+
console.error('[gm-monitor] SDK internal error:', error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+http://192.168.200.220/GomainFE/stamp.git",
|
|
7
|
+
"directory": "monitor/packages/monitor-core"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public",
|
|
11
|
+
"registry": "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
13
|
+
"name": "@guomain/monitor-core",
|
|
14
|
+
"description": "Runtime-agnostic core for @gm monitor SDK",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"main": "./index.js",
|
|
18
|
+
"module": "./index.js",
|
|
19
|
+
"types": "./index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./index.d.ts",
|
|
23
|
+
"import": "./index.js",
|
|
24
|
+
"default": "./index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@guomain/monitor-types": "^0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"**/*"
|
|
32
|
+
]
|
|
33
|
+
}
|