@guomain/monitor-plugins 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 +4 -0
- package/index.d.ts.map +1 -0
- package/index.js +3 -0
- package/package.json +61 -0
- package/react/index.d.ts +9 -0
- package/react/index.d.ts.map +1 -0
- package/react/index.js +56 -0
- package/vite/index.d.ts +39 -0
- package/vite/index.d.ts.map +1 -0
- package/vite/index.js +248 -0
- package/vue/index.d.ts +8 -0
- package/vue/index.d.ts.map +1 -0
- package/vue/index.js +16 -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
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAA;AACvB,cAAc,QAAQ,CAAA;AACtB,cAAc,OAAO,CAAA"}
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
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-plugins"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public",
|
|
11
|
+
"registry": "https://registry.npmjs.org/"
|
|
12
|
+
},
|
|
13
|
+
"name": "@guomain/monitor-plugins",
|
|
14
|
+
"description": "Build-time plugins for @gm monitor SDK (Vite catch injection, Vue/React helpers)",
|
|
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
|
+
"./vite": {
|
|
27
|
+
"types": "./vite/index.d.ts",
|
|
28
|
+
"import": "./vite/index.js",
|
|
29
|
+
"default": "./vite/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./vue": {
|
|
32
|
+
"types": "./vue/index.d.ts",
|
|
33
|
+
"import": "./vue/index.js",
|
|
34
|
+
"default": "./vue/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./react": {
|
|
37
|
+
"types": "./react/index.d.ts",
|
|
38
|
+
"import": "./react/index.js",
|
|
39
|
+
"default": "./react/index.js"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@guomain/monitor-core": "^0.1.0",
|
|
44
|
+
"@guomain/monitor-types": "^0.1.0",
|
|
45
|
+
"@babel/generator": "^7.28.3",
|
|
46
|
+
"@babel/parser": "^7.28.4",
|
|
47
|
+
"@babel/traverse": "^7.28.4",
|
|
48
|
+
"@babel/types": "^7.28.4"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"vite": ">=3.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"vite": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"**/*"
|
|
60
|
+
]
|
|
61
|
+
}
|
package/react/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MonitorInstance, ReactElementLike, ReactRenderRoot } from '@guomain/monitor-types';
|
|
2
|
+
/**
|
|
3
|
+
* 使用监控 ErrorBoundary 包装 React 渲染入口。
|
|
4
|
+
* @param monitor 当前监控实例。
|
|
5
|
+
* @param root React 渲染根节点。
|
|
6
|
+
* @param element 业务应用节点。
|
|
7
|
+
*/
|
|
8
|
+
export declare function renderReactWithMonitor(monitor: MonitorInstance, root: ReactRenderRoot, element: ReactElementLike): void;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAIhG;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,gBAAgB,QAG1B"}
|
package/react/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { toErrorEvent } from '@guomain/monitor-core';
|
|
2
|
+
const boundaryTypes = new WeakMap();
|
|
3
|
+
/**
|
|
4
|
+
* 使用监控 ErrorBoundary 包装 React 渲染入口。
|
|
5
|
+
* @param monitor 当前监控实例。
|
|
6
|
+
* @param root React 渲染根节点。
|
|
7
|
+
* @param element 业务应用节点。
|
|
8
|
+
*/
|
|
9
|
+
export function renderReactWithMonitor(monitor, root, element) {
|
|
10
|
+
root.render(createReactBoundaryElement(getReactBoundaryType(monitor), element));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 获取当前监控实例对应的 React ErrorBoundary 类型。
|
|
14
|
+
* @param monitor 当前监控实例。
|
|
15
|
+
*/
|
|
16
|
+
function getReactBoundaryType(monitor) {
|
|
17
|
+
const cachedType = boundaryTypes.get(monitor);
|
|
18
|
+
if (cachedType) {
|
|
19
|
+
return cachedType;
|
|
20
|
+
}
|
|
21
|
+
class MonitorReactBoundary {
|
|
22
|
+
static getDerivedStateFromError() {
|
|
23
|
+
return { hasError: true };
|
|
24
|
+
}
|
|
25
|
+
constructor(props) {
|
|
26
|
+
this.state = { hasError: false };
|
|
27
|
+
this.props = props;
|
|
28
|
+
}
|
|
29
|
+
componentDidCatch(error, info) {
|
|
30
|
+
monitor.capture(toErrorEvent('react-error', error, { extra: { info } }));
|
|
31
|
+
}
|
|
32
|
+
render() {
|
|
33
|
+
return this.state.hasError ? null : this.props.children;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty(MonitorReactBoundary.prototype, 'isReactComponent', {
|
|
37
|
+
value: {}
|
|
38
|
+
});
|
|
39
|
+
boundaryTypes.set(monitor, MonitorReactBoundary);
|
|
40
|
+
return MonitorReactBoundary;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 创建 React element 形态的边界节点。
|
|
44
|
+
* @param type React ErrorBoundary 类型。
|
|
45
|
+
* @param children 需要被边界包裹的业务节点。
|
|
46
|
+
*/
|
|
47
|
+
function createReactBoundaryElement(type, children) {
|
|
48
|
+
return {
|
|
49
|
+
$$typeof: Symbol.for('react.element'),
|
|
50
|
+
type,
|
|
51
|
+
key: null,
|
|
52
|
+
ref: null,
|
|
53
|
+
props: { children },
|
|
54
|
+
_owner: null
|
|
55
|
+
};
|
|
56
|
+
}
|
package/vite/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface MonitorCatchPluginOptions {
|
|
2
|
+
/** 是否启用 catch 注入,默认 false。 */
|
|
3
|
+
catch?: boolean;
|
|
4
|
+
/** 需要注入 catch 采集逻辑的文件路径。 */
|
|
5
|
+
include?: string[];
|
|
6
|
+
/** 需要排除的文件路径。 */
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
/** 运行时挂载在 globalThis 上的监控实例变量名。 */
|
|
9
|
+
monitorVar?: string;
|
|
10
|
+
}
|
|
11
|
+
interface VitePluginLike {
|
|
12
|
+
name: string;
|
|
13
|
+
enforce?: 'pre' | 'post';
|
|
14
|
+
transform: (code: string, id: string) => {
|
|
15
|
+
code: string;
|
|
16
|
+
map: null;
|
|
17
|
+
} | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 创建 Vite 插件,基于 Babel AST 为 catch 代码注入监控采集。
|
|
21
|
+
* @param options 插件配置,catch 为 true 时才会按 include、exclude 和 monitorVar 注入。
|
|
22
|
+
*/
|
|
23
|
+
export declare function monitorCatchPlugin(options: MonitorCatchPluginOptions): VitePluginLike;
|
|
24
|
+
/**
|
|
25
|
+
* 基于 Babel AST 为 catch 语句块注入 monitor.capture()。
|
|
26
|
+
*
|
|
27
|
+
* 注入逻辑:
|
|
28
|
+
* 1. 解析源码为 AST
|
|
29
|
+
* 2. 遍历所有 CatchClause 节点
|
|
30
|
+
* 3. 检查 catch 体内是否已包含上报调用,有则跳过
|
|
31
|
+
* 4. 在 catch 体首行插入 capture(),先于原有代码执行
|
|
32
|
+
*
|
|
33
|
+
* @param code 原始源码。
|
|
34
|
+
* @param monitorVar 运行时监控实例变量名。
|
|
35
|
+
* @param file 当前文件路径,用于上报排查。
|
|
36
|
+
*/
|
|
37
|
+
export declare function injectCatchCapture(code: string, monitorVar?: string, file?: string): string;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,yBAAyB;IACxC,8BAA8B;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,4BAA4B;IAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,iBAAiB;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;IACxB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;CAC5E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,cAAc,CAmCrF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,SAAmB,EAAE,IAAI,SAAK,GAAG,MAAM,CAkEjG"}
|
package/vite/index.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import _traverse from '@babel/traverse';
|
|
3
|
+
import _generate from '@babel/generator';
|
|
4
|
+
import * as t from '@babel/types';
|
|
5
|
+
// @babel/traverse 和 @babel/generator 都是 CJS 包,解构导入
|
|
6
|
+
const traverse = _traverse.default ?? _traverse;
|
|
7
|
+
const generate = _generate.default ?? _generate;
|
|
8
|
+
/**
|
|
9
|
+
* 创建 Vite 插件,基于 Babel AST 为 catch 代码注入监控采集。
|
|
10
|
+
* @param options 插件配置,catch 为 true 时才会按 include、exclude 和 monitorVar 注入。
|
|
11
|
+
*/
|
|
12
|
+
export function monitorCatchPlugin(options) {
|
|
13
|
+
const shouldInjectCatch = options.catch === true;
|
|
14
|
+
const monitorVar = options.monitorVar ?? '__GM_MONITOR__';
|
|
15
|
+
assertMonitorVar(monitorVar);
|
|
16
|
+
const include = (options.include ?? []).map(globToRegExp);
|
|
17
|
+
const exclude = (options.exclude ?? []).map(globToRegExp);
|
|
18
|
+
if (shouldInjectCatch && include.length === 0 && typeof console !== 'undefined') {
|
|
19
|
+
console.warn('[gm-monitor-catch] catch 注入已启用但未配置 include,不会注入任何文件');
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
name: 'gm-monitor-catch',
|
|
23
|
+
enforce: 'post',
|
|
24
|
+
transform(code, id) {
|
|
25
|
+
if (!shouldInjectCatch) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const normalizedId = normalizePath(id);
|
|
29
|
+
if (isSdkInternalPath(normalizedId)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (!include.some(item => item.test(normalizedId))) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (exclude.some(item => item.test(normalizedId))) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const nextCode = injectCatchCapture(code, monitorVar, normalizedId);
|
|
39
|
+
return nextCode === code ? null : { code: nextCode, map: null };
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 基于 Babel AST 为 catch 语句块注入 monitor.capture()。
|
|
45
|
+
*
|
|
46
|
+
* 注入逻辑:
|
|
47
|
+
* 1. 解析源码为 AST
|
|
48
|
+
* 2. 遍历所有 CatchClause 节点
|
|
49
|
+
* 3. 检查 catch 体内是否已包含上报调用,有则跳过
|
|
50
|
+
* 4. 在 catch 体首行插入 capture(),先于原有代码执行
|
|
51
|
+
*
|
|
52
|
+
* @param code 原始源码。
|
|
53
|
+
* @param monitorVar 运行时监控实例变量名。
|
|
54
|
+
* @param file 当前文件路径,用于上报排查。
|
|
55
|
+
*/
|
|
56
|
+
export function injectCatchCapture(code, monitorVar = '__GM_MONITOR__', file = '') {
|
|
57
|
+
assertMonitorVar(monitorVar);
|
|
58
|
+
let ast;
|
|
59
|
+
try {
|
|
60
|
+
ast = parse(code, {
|
|
61
|
+
sourceType: 'unambiguous',
|
|
62
|
+
plugins: ['typescript', 'jsx', 'decorators-legacy'],
|
|
63
|
+
errorRecovery: true
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return code;
|
|
68
|
+
}
|
|
69
|
+
let modified = false;
|
|
70
|
+
const visitor = {
|
|
71
|
+
CatchClause(path) {
|
|
72
|
+
const paramName = path.node.param
|
|
73
|
+
? t.isIdentifier(path.node.param)
|
|
74
|
+
? path.node.param.name
|
|
75
|
+
: t.isArrayPattern(path.node.param) || t.isObjectPattern(path.node.param)
|
|
76
|
+
? '__gmMonitorError'
|
|
77
|
+
: null
|
|
78
|
+
: null;
|
|
79
|
+
const errorRef = paramName ?? '__gmMonitorError';
|
|
80
|
+
// 检查是否已有上报调用
|
|
81
|
+
if (hasMonitorCapture(path, monitorVar)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// 如果没有参数,添加一个占位参数
|
|
85
|
+
if (!path.node.param) {
|
|
86
|
+
path.node.param = t.identifier('__gmMonitorError');
|
|
87
|
+
}
|
|
88
|
+
// 构造上报表达式
|
|
89
|
+
const captureCall = buildCaptureStatement(monitorVar, errorRef, file);
|
|
90
|
+
const body = path.node.body;
|
|
91
|
+
if (body.body.length > 0) {
|
|
92
|
+
body.body.unshift(captureCall);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
body.body.push(captureCall);
|
|
96
|
+
}
|
|
97
|
+
modified = true;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
traverse(ast, visitor);
|
|
101
|
+
if (!modified) {
|
|
102
|
+
return code;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const result = generate(ast, {
|
|
106
|
+
retainLines: true,
|
|
107
|
+
compact: false
|
|
108
|
+
});
|
|
109
|
+
return result.code;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return code;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 检查 catch 体内是否已包含指定监控变量的 capture 调用。
|
|
117
|
+
* 匹配模式:globalThis.{monitorVar}?.capture?.({ type: 'caught-error', ... })
|
|
118
|
+
*/
|
|
119
|
+
function hasMonitorCapture(path, monitorVar) {
|
|
120
|
+
let found = false;
|
|
121
|
+
path.node.body.body.forEach((stmt) => {
|
|
122
|
+
// 检查是否是表达式语句
|
|
123
|
+
if (t.isExpressionStatement(stmt)) {
|
|
124
|
+
if (matchCaptureCall(stmt.expression, monitorVar)) {
|
|
125
|
+
found = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
return found;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 递归匹配表达式是否是 capture 调用链。
|
|
133
|
+
* 目标:globalThis.{monitorVar}?.capture?.({ type: 'caught-error', ... })
|
|
134
|
+
*/
|
|
135
|
+
function matchCaptureCall(expr, monitorVar) {
|
|
136
|
+
// 最外层:可选调用 (...)
|
|
137
|
+
if (t.isOptionalCallExpression(expr) || t.isCallExpression(expr)) {
|
|
138
|
+
const args = expr.arguments;
|
|
139
|
+
if (args.length === 0)
|
|
140
|
+
return false;
|
|
141
|
+
const firstArg = args[0];
|
|
142
|
+
// 检查是否是对象参数且包含 type: 'caught-error'
|
|
143
|
+
if (t.isObjectExpression(firstArg)) {
|
|
144
|
+
const hasTypeKey = firstArg.properties.some(p => {
|
|
145
|
+
if (t.isObjectProperty(p) && t.isIdentifier(p.key, { name: 'type' })) {
|
|
146
|
+
return t.isStringLiteral(p.value) && p.value.value === 'caught-error';
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
});
|
|
150
|
+
// 确认调用链
|
|
151
|
+
if (hasTypeKey) {
|
|
152
|
+
return matchMemberChain(expr.callee, monitorVar);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 匹配成员访问链:globalThis.{monitorVar}?.capture 或 {monitorVar}.capture
|
|
160
|
+
*/
|
|
161
|
+
function matchMemberChain(callee, monitorVar) {
|
|
162
|
+
// capture 属性访问
|
|
163
|
+
if (t.isOptionalMemberExpression(callee) &&
|
|
164
|
+
t.isIdentifier(callee.property, { name: 'capture' })) {
|
|
165
|
+
return matchMonitorVarRef(callee.object, monitorVar);
|
|
166
|
+
}
|
|
167
|
+
// 直接 capture(无可选链)
|
|
168
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property, { name: 'capture' })) {
|
|
169
|
+
return matchMonitorVarRef(callee.object, monitorVar);
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 匹配监控变量引用:globalThis.{monitorVar} 或直接标识符
|
|
175
|
+
*/
|
|
176
|
+
function matchMonitorVarRef(expr, monitorVar) {
|
|
177
|
+
// globalThis.{monitorVar}
|
|
178
|
+
if (t.isOptionalMemberExpression(expr) &&
|
|
179
|
+
t.isIdentifier(expr.object, { name: 'globalThis' }) &&
|
|
180
|
+
t.isIdentifier(expr.property, { name: monitorVar })) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
if (t.isMemberExpression(expr) &&
|
|
184
|
+
t.isIdentifier(expr.object, { name: 'globalThis' }) &&
|
|
185
|
+
t.isIdentifier(expr.property, { name: monitorVar })) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
// 直接 {monitorVar}
|
|
189
|
+
if (t.isIdentifier(expr, { name: monitorVar })) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* 构造上报语句:
|
|
196
|
+
* globalThis.__GM_MONITOR__?.capture?.({ type: 'caught-error', message: ..., stack: ..., timestamp: ..., extra: { file: '...' } })
|
|
197
|
+
*/
|
|
198
|
+
function buildCaptureStatement(monitorVar, errorRef, file) {
|
|
199
|
+
const captureFn = t.optionalCallExpression(t.optionalMemberExpression(t.memberExpression(t.identifier('globalThis'), t.identifier(monitorVar)), t.identifier('capture'), false, true), [
|
|
200
|
+
t.objectExpression([
|
|
201
|
+
t.objectProperty(t.identifier('type'), t.stringLiteral('caught-error')),
|
|
202
|
+
t.objectProperty(t.identifier('message'), t.conditionalExpression(t.binaryExpression('instanceof', t.identifier(errorRef), t.identifier('Error')), t.memberExpression(t.identifier(errorRef), t.identifier('message')), t.callExpression(t.identifier('String'), [t.identifier(errorRef)]))),
|
|
203
|
+
t.objectProperty(t.identifier('stack'), t.conditionalExpression(t.binaryExpression('instanceof', t.identifier(errorRef), t.identifier('Error')), t.memberExpression(t.identifier(errorRef), t.identifier('stack')), t.identifier('undefined'))),
|
|
204
|
+
t.objectProperty(t.identifier('timestamp'), t.callExpression(t.memberExpression(t.identifier('Date'), t.identifier('now')), [])),
|
|
205
|
+
t.objectProperty(t.identifier('extra'), t.objectExpression([t.objectProperty(t.identifier('file'), t.stringLiteral(file))]))
|
|
206
|
+
])
|
|
207
|
+
], true);
|
|
208
|
+
return t.expressionStatement(captureFn);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 校验监控实例变量名。
|
|
212
|
+
* @param monitorVar 用户配置的全局变量名。
|
|
213
|
+
*/
|
|
214
|
+
function assertMonitorVar(monitorVar) {
|
|
215
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(monitorVar)) {
|
|
216
|
+
throw new Error(`Invalid monitorVar: ${monitorVar}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 把简单 glob 转成正则。
|
|
221
|
+
* @param glob include/exclude 配置项。
|
|
222
|
+
*/
|
|
223
|
+
function globToRegExp(glob) {
|
|
224
|
+
const normalized = normalizePath(glob);
|
|
225
|
+
const placeholder = '__GM_MONITOR_GLOB_STAR__';
|
|
226
|
+
const escaped = normalized
|
|
227
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
228
|
+
.replace(/\*\*/g, placeholder)
|
|
229
|
+
.replace(/\*/g, '[^/]*')
|
|
230
|
+
.replace(/\?/g, '[^/]');
|
|
231
|
+
return new RegExp(escaped.replace(new RegExp(placeholder, 'g'), '.*'));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 统一路径分隔符。
|
|
235
|
+
* @param path 文件路径。
|
|
236
|
+
*/
|
|
237
|
+
function normalizePath(path) {
|
|
238
|
+
return path.replace(/\\/g, '/');
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* 判断是否是 SDK 内部路径
|
|
242
|
+
*/
|
|
243
|
+
function isSdkInternalPath(path) {
|
|
244
|
+
return (path.includes('/node_modules/@guomain/monitor-') ||
|
|
245
|
+
path.includes('/node_modules/@gm/monitor-') ||
|
|
246
|
+
path.includes('/monitor/packages/') ||
|
|
247
|
+
/(^|\/)packages\/monitor-(core|web|types|plugins)\//.test(path));
|
|
248
|
+
}
|
package/vue/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MonitorInstance, VueMonitorOptions } from '@guomain/monitor-types';
|
|
2
|
+
/**
|
|
3
|
+
* 接入 Vue 全局错误处理。
|
|
4
|
+
* @param options Vue 监控配置,包含 app 实例。
|
|
5
|
+
* @param monitor 当前监控实例。
|
|
6
|
+
*/
|
|
7
|
+
export declare function installVueMonitor(options: VueMonitorOptions, monitor: MonitorInstance): () => void;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vue/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAEhF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,eAAe,cAUrF"}
|
package/vue/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { toErrorEvent } from '@guomain/monitor-core';
|
|
2
|
+
/**
|
|
3
|
+
* 接入 Vue 全局错误处理。
|
|
4
|
+
* @param options Vue 监控配置,包含 app 实例。
|
|
5
|
+
* @param monitor 当前监控实例。
|
|
6
|
+
*/
|
|
7
|
+
export function installVueMonitor(options, monitor) {
|
|
8
|
+
const previousHandler = options.app.config.errorHandler;
|
|
9
|
+
options.app.config.errorHandler = (err, instance, info) => {
|
|
10
|
+
monitor.capture(toErrorEvent('vue-error', err, { extra: { info } }));
|
|
11
|
+
previousHandler?.(err, instance, info);
|
|
12
|
+
};
|
|
13
|
+
return () => {
|
|
14
|
+
options.app.config.errorHandler = previousHandler;
|
|
15
|
+
};
|
|
16
|
+
}
|