vue_zhongyou 1.0.1 → 1.0.3
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/icon//346/225/254/350/257/267/346/234/237/345/276/205.png +0 -0
- package/package.json +1 -1
- package//345/212/237/350/203/275/344/273/243/347/240/201//345/255/230/345/220/216/347/253/257/347/211/210/347/233/221/346/216/247/errorLogPage.vue +422 -0
- package//345/212/237/350/203/275/344/273/243/347/240/201//345/255/230/345/220/216/347/253/257/347/211/210/347/233/221/346/216/247/errorMonitor.js +375 -0
- package//345/212/237/350/203/275/344/273/243/347/240/201//345/255/230/345/220/216/347/253/257/347/211/210/347/233/221/346/216/247/pcErrorLogPage.vue +585 -0
- package//345/212/237/350/203/275/344/273/243/347/240/201//345/255/230/345/220/216/347/253/257/347/211/210/347/233/221/346/216/247/request.js +99 -0
- package//345/212/237/350/203/275/344/273/243/347/240/201//345/255/230/345/220/216/347/253/257/347/211/210/347/233/221/346/216/247/testError.vue +500 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误监控工具
|
|
3
|
+
* 用于收集接口错误和前端页面错误,发送到后端服务器
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import axios from 'axios'
|
|
7
|
+
|
|
8
|
+
class ErrorMonitor {
|
|
9
|
+
constructor() {
|
|
10
|
+
// 错误日志发送到后端的接口地址,可以通过环境变量配置
|
|
11
|
+
this.apiUrl = import.meta.env.VITE_ERROR_LOG_API || '/api/error-logs'
|
|
12
|
+
// 是否启用错误监控
|
|
13
|
+
this.enabled = true
|
|
14
|
+
// 发送失败的日志队列(用于重试)
|
|
15
|
+
this.failedLogs = []
|
|
16
|
+
// 最大重试次数
|
|
17
|
+
this.maxRetries = 3
|
|
18
|
+
// 创建独立的 axios 实例用于发送错误日志,避免循环依赖
|
|
19
|
+
this.httpClient = axios.create({
|
|
20
|
+
baseURL: import.meta.env.VITE_API_BASE_URL || '',
|
|
21
|
+
timeout: 5000,
|
|
22
|
+
headers: {
|
|
23
|
+
'Content-Type': 'application/json;charset=UTF-8'
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 记录接口错误
|
|
30
|
+
* @param {Object} errorInfo - 错误信息
|
|
31
|
+
*/
|
|
32
|
+
logApiError(errorInfo) {
|
|
33
|
+
const log = {
|
|
34
|
+
id: this.generateId(),
|
|
35
|
+
type: 'api', // 错误类型:接口错误
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
time: new Date().toLocaleString('zh-CN'),
|
|
38
|
+
url: errorInfo.url || '',
|
|
39
|
+
method: errorInfo.method || 'GET',
|
|
40
|
+
params: errorInfo.params || {},
|
|
41
|
+
data: errorInfo.data || {},
|
|
42
|
+
status: errorInfo.status || 0,
|
|
43
|
+
statusText: errorInfo.statusText || '',
|
|
44
|
+
message: errorInfo.message || '',
|
|
45
|
+
stack: errorInfo.stack || '',
|
|
46
|
+
userAgent: navigator.userAgent,
|
|
47
|
+
pageUrl: window.location.href,
|
|
48
|
+
...errorInfo
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.addLog(log)
|
|
52
|
+
this.consoleError('接口错误', log)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 记录前端页面错误
|
|
57
|
+
* @param {Object} errorInfo - 错误信息
|
|
58
|
+
*/
|
|
59
|
+
logPageError(errorInfo) {
|
|
60
|
+
const log = {
|
|
61
|
+
id: this.generateId(),
|
|
62
|
+
type: 'page', // 错误类型:页面错误
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
time: new Date().toLocaleString('zh-CN'),
|
|
65
|
+
message: errorInfo.message || '',
|
|
66
|
+
stack: errorInfo.stack || '',
|
|
67
|
+
fileName: errorInfo.fileName || '',
|
|
68
|
+
lineNumber: errorInfo.lineNumber || 0,
|
|
69
|
+
columnNumber: errorInfo.columnNumber || 0,
|
|
70
|
+
componentName: errorInfo.componentName || '',
|
|
71
|
+
props: errorInfo.props || {},
|
|
72
|
+
userAgent: navigator.userAgent,
|
|
73
|
+
pageUrl: window.location.href,
|
|
74
|
+
...errorInfo
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.addLog(log)
|
|
78
|
+
this.consoleError('页面错误', log)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 记录Promise未捕获错误
|
|
83
|
+
* @param {Object} errorInfo - 错误信息
|
|
84
|
+
*/
|
|
85
|
+
logPromiseError(errorInfo) {
|
|
86
|
+
const log = {
|
|
87
|
+
id: this.generateId(),
|
|
88
|
+
type: 'promise', // 错误类型:Promise错误
|
|
89
|
+
timestamp: new Date().toISOString(),
|
|
90
|
+
time: new Date().toLocaleString('zh-CN'),
|
|
91
|
+
message: errorInfo.message || '',
|
|
92
|
+
stack: errorInfo.stack || '',
|
|
93
|
+
reason: errorInfo.reason || '',
|
|
94
|
+
userAgent: navigator.userAgent,
|
|
95
|
+
pageUrl: window.location.href,
|
|
96
|
+
...errorInfo
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.addLog(log)
|
|
100
|
+
this.consoleError('Promise错误', log)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 添加日志(直接发送到后端,不存储在前端)
|
|
105
|
+
* @param {Object} log - 日志对象
|
|
106
|
+
*/
|
|
107
|
+
addLog(log) {
|
|
108
|
+
// 如果错误监控未启用,直接返回
|
|
109
|
+
if (!this.enabled) {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 控制台输出错误(开发环境)
|
|
114
|
+
this.consoleError(log.type === 'api' ? '接口错误' : log.type === 'page' ? '页面错误' : 'Promise错误', log)
|
|
115
|
+
|
|
116
|
+
// 直接发送到服务器
|
|
117
|
+
this.sendToServer(log).catch(error => {
|
|
118
|
+
// 如果发送失败,加入失败队列等待重试
|
|
119
|
+
console.error('发送错误日志到服务器失败:', error)
|
|
120
|
+
this.failedLogs.push({
|
|
121
|
+
log,
|
|
122
|
+
retries: 0
|
|
123
|
+
})
|
|
124
|
+
// 尝试重试
|
|
125
|
+
this.retryFailedLogs()
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 控制台输出错误(开发环境)
|
|
131
|
+
*/
|
|
132
|
+
consoleError(title, log) {
|
|
133
|
+
if (import.meta.env.DEV) {
|
|
134
|
+
console.group(`[错误监控] ${title}`)
|
|
135
|
+
console.error(log)
|
|
136
|
+
console.groupEnd()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 生成唯一ID
|
|
142
|
+
*/
|
|
143
|
+
generateId() {
|
|
144
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 从后端获取所有日志(需要后端接口支持)
|
|
149
|
+
*/
|
|
150
|
+
async getLogs(type = null, params = {}) {
|
|
151
|
+
try {
|
|
152
|
+
const response = await this.httpClient.get(this.apiUrl, {
|
|
153
|
+
params: {
|
|
154
|
+
type,
|
|
155
|
+
...params
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
return response?.data?.data || response?.data || response || []
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('获取错误日志失败:', error)
|
|
161
|
+
return []
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 获取接口错误日志
|
|
167
|
+
*/
|
|
168
|
+
async getApiLogs(params = {}) {
|
|
169
|
+
return this.getLogs('api', params)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 获取页面错误日志
|
|
174
|
+
*/
|
|
175
|
+
async getPageLogs(params = {}) {
|
|
176
|
+
return this.getLogs('page', params)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 获取Promise错误日志
|
|
181
|
+
*/
|
|
182
|
+
async getPromiseLogs(params = {}) {
|
|
183
|
+
return this.getLogs('promise', params)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 导出日志为JSON(从后端获取)
|
|
188
|
+
*/
|
|
189
|
+
async exportLogs() {
|
|
190
|
+
try {
|
|
191
|
+
const logs = await this.getLogs()
|
|
192
|
+
const dataStr = JSON.stringify(logs, null, 2)
|
|
193
|
+
const dataBlob = new Blob([dataStr], { type: 'application/json' })
|
|
194
|
+
const url = URL.createObjectURL(dataBlob)
|
|
195
|
+
const link = document.createElement('a')
|
|
196
|
+
link.href = url
|
|
197
|
+
link.download = `error-logs-${new Date().toISOString().split('T')[0]}.json`
|
|
198
|
+
document.body.appendChild(link)
|
|
199
|
+
link.click()
|
|
200
|
+
document.body.removeChild(link)
|
|
201
|
+
URL.revokeObjectURL(url)
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error('导出日志失败:', error)
|
|
204
|
+
throw error
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 导出日志为文本(从后端获取)
|
|
210
|
+
*/
|
|
211
|
+
async exportLogsAsText() {
|
|
212
|
+
try {
|
|
213
|
+
const logs = await this.getLogs()
|
|
214
|
+
let text = '错误日志导出\n'
|
|
215
|
+
text += `导出时间: ${new Date().toLocaleString('zh-CN')}\n`
|
|
216
|
+
text += `总计: ${logs.length} 条\n\n`
|
|
217
|
+
text += '='.repeat(80) + '\n\n'
|
|
218
|
+
|
|
219
|
+
logs.reverse().forEach((log, index) => {
|
|
220
|
+
text += `[${index + 1}] ${log.type.toUpperCase()} 错误\n`
|
|
221
|
+
text += `时间: ${log.time}\n`
|
|
222
|
+
text += `页面: ${log.pageUrl}\n`
|
|
223
|
+
|
|
224
|
+
if (log.type === 'api') {
|
|
225
|
+
text += `接口: ${log.method} ${log.url}\n`
|
|
226
|
+
text += `状态: ${log.status} ${log.statusText}\n`
|
|
227
|
+
if (log.params && Object.keys(log.params).length > 0) {
|
|
228
|
+
text += `参数: ${JSON.stringify(log.params, null, 2)}\n`
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
if (log.fileName) {
|
|
232
|
+
text += `文件: ${log.fileName}:${log.lineNumber}:${log.columnNumber}\n`
|
|
233
|
+
}
|
|
234
|
+
if (log.componentName) {
|
|
235
|
+
text += `组件: ${log.componentName}\n`
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
text += `错误信息: ${log.message}\n`
|
|
240
|
+
if (log.stack) {
|
|
241
|
+
text += `堆栈信息:\n${log.stack}\n`
|
|
242
|
+
}
|
|
243
|
+
text += '\n' + '-'.repeat(80) + '\n\n'
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const dataBlob = new Blob([text], { type: 'text/plain;charset=utf-8' })
|
|
247
|
+
const url = URL.createObjectURL(dataBlob)
|
|
248
|
+
const link = document.createElement('a')
|
|
249
|
+
link.href = url
|
|
250
|
+
link.download = `error-logs-${new Date().toISOString().split('T')[0]}.txt`
|
|
251
|
+
document.body.appendChild(link)
|
|
252
|
+
link.click()
|
|
253
|
+
document.body.removeChild(link)
|
|
254
|
+
URL.revokeObjectURL(url)
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error('导出日志失败:', error)
|
|
257
|
+
throw error
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 发送日志到服务器
|
|
263
|
+
* @param {Object} log - 日志对象
|
|
264
|
+
*/
|
|
265
|
+
async sendToServer(log) {
|
|
266
|
+
try {
|
|
267
|
+
// 使用独立的 httpClient 发送错误日志到后端,避免循环依赖
|
|
268
|
+
await this.httpClient.post(this.apiUrl, log)
|
|
269
|
+
return true
|
|
270
|
+
} catch (error) {
|
|
271
|
+
// 如果发送失败,不记录错误(避免循环记录)
|
|
272
|
+
// 但会在 addLog 中加入失败队列
|
|
273
|
+
throw error
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 重试发送失败的日志
|
|
279
|
+
*/
|
|
280
|
+
async retryFailedLogs() {
|
|
281
|
+
if (this.failedLogs.length === 0) {
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 延迟重试,避免频繁请求
|
|
286
|
+
if (this.retryTimer) {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.retryTimer = setTimeout(async () => {
|
|
291
|
+
this.retryTimer = null
|
|
292
|
+
const logsToRetry = [...this.failedLogs]
|
|
293
|
+
this.failedLogs = []
|
|
294
|
+
|
|
295
|
+
for (const item of logsToRetry) {
|
|
296
|
+
if (item.retries >= this.maxRetries) {
|
|
297
|
+
// 超过最大重试次数,放弃
|
|
298
|
+
console.error('错误日志发送失败,已超过最大重试次数:', item.log)
|
|
299
|
+
continue
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
await this.sendToServer(item.log)
|
|
304
|
+
// 发送成功,不再处理
|
|
305
|
+
} catch (error) {
|
|
306
|
+
// 发送失败,增加重试次数并重新加入队列
|
|
307
|
+
item.retries++
|
|
308
|
+
this.failedLogs.push(item)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 如果还有失败的日志,继续重试
|
|
313
|
+
if (this.failedLogs.length > 0) {
|
|
314
|
+
setTimeout(() => this.retryFailedLogs(), 10000) // 10秒后重试
|
|
315
|
+
}
|
|
316
|
+
}, 2000) // 2秒后开始重试
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* 获取错误统计信息(从后端获取)
|
|
321
|
+
*/
|
|
322
|
+
async getStatistics() {
|
|
323
|
+
try {
|
|
324
|
+
const response = await this.httpClient.get(`${this.apiUrl}/statistics`)
|
|
325
|
+
return response?.data?.data || response?.data || response || {
|
|
326
|
+
total: 0,
|
|
327
|
+
api: 0,
|
|
328
|
+
page: 0,
|
|
329
|
+
promise: 0,
|
|
330
|
+
today: 0,
|
|
331
|
+
thisWeek: 0,
|
|
332
|
+
thisMonth: 0
|
|
333
|
+
}
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error('获取错误统计失败:', error)
|
|
336
|
+
// 返回默认统计信息
|
|
337
|
+
return {
|
|
338
|
+
total: 0,
|
|
339
|
+
api: 0,
|
|
340
|
+
page: 0,
|
|
341
|
+
promise: 0,
|
|
342
|
+
today: 0,
|
|
343
|
+
thisWeek: 0,
|
|
344
|
+
thisMonth: 0
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 启用/禁用错误监控
|
|
351
|
+
* @param {Boolean} enabled - 是否启用
|
|
352
|
+
*/
|
|
353
|
+
setEnabled(enabled) {
|
|
354
|
+
this.enabled = enabled
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 清空日志(请求后端接口)
|
|
359
|
+
*/
|
|
360
|
+
async clearLogs() {
|
|
361
|
+
try {
|
|
362
|
+
await this.httpClient.delete(this.apiUrl)
|
|
363
|
+
return true
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error('清空错误日志失败:', error)
|
|
366
|
+
throw error
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 创建单例
|
|
372
|
+
const errorMonitor = new ErrorMonitor()
|
|
373
|
+
|
|
374
|
+
export default errorMonitor
|
|
375
|
+
|