You've already forked uniapp-error-monitor
feat(refactor): 大幅重构和优化
✨ 主要更新: - 全面重写README.md,增强文档完整性和易读性 - 新增完整的使用示例和高级用法说明 - 重构代码架构,删除冗余文件,提升代码质量 - 添加TypeScript支持,增强类型安全 - 优化构建配置,支持多种输出格式(ESM/CJS/UMD) - 改进错误捕获机制,支持更多错误类型 📋 具体变更: - README: 从基础文档升级为完整的API参考和使用指南 - 构建: 删除build.sh,构建配置移至rollup.config.js - 类型: 添加index.d.ts,实现完整的TypeScript支持 - 示例: 新增USAGE_EXAMPLES.js提供详细使用示例 - 配置: 优化package.json脚本和依赖管理 🎯 技术提升: - 代码结构更清晰,维护性更好 - 类型安全性显著增强 - 文档质量和用户体验大幅提升 - 支持更多模块格式,兼容性更好 🔖 版本: 1.0.1 -> 1.1.0
This commit is contained in:
328
README.md
328
README.md
@@ -1,6 +1,6 @@
|
|||||||
# uniapp-error-monitor
|
# uniapp-error-monitor
|
||||||
|
|
||||||
UniApp 错误监控上报插件 - 专业的 JavaScript 错误监控和上报解决方案
|
🔍 UniApp 专业错误监控和上报工具 - 支持全平台、多场景错误捕获
|
||||||
|
|
||||||
[](https://badge.fury.io/js/uniapp-error-monitor)
|
[](https://badge.fury.io/js/uniapp-error-monitor)
|
||||||
[](https://www.npmjs.com/package/uniapp-error-monitor)
|
[](https://www.npmjs.com/package/uniapp-error-monitor)
|
||||||
@@ -8,17 +8,18 @@ UniApp 错误监控上报插件 - 专业的 JavaScript 错误监控和上报解
|
|||||||
[](https://www.typescriptlang.org/)
|
[](https://www.typescriptlang.org/)
|
||||||
[](https://rollupjs.org/)
|
[](https://rollupjs.org/)
|
||||||
|
|
||||||
## 🌟 特性
|
## ✨ 核心特性
|
||||||
|
|
||||||
- 🔍 **全面错误捕获**: 支持全局错误、Promise 错误、控制台错误、网络错误、小程序错误
|
- 🎯 **零配置使用**: 开箱即用,支持多种导入方式
|
||||||
- 🎯 **环境智能**: 自动检测生产环境,非生产环境不启用错误上报
|
- 🔍 **全面错误捕获**: 全局错误、Promise错误、控制台错误、网络错误、小程序错误
|
||||||
- 🚀 **高性能**: 异步发送错误,不阻塞主线程
|
- 🧠 **环境智能**: 自动检测生产环境,非生产环境优雅降级
|
||||||
- 🔄 **重试机制**: 网络失败自动重试,可配置重试次数和间隔
|
- ⚡ **高性能**: 异步发送错误,不阻塞主线程
|
||||||
- 📊 **错误统计**: 内置错误统计功能,便于数据分析
|
- 🔄 **重试机制**: 网络失败自动重试,可配置次数和间隔
|
||||||
- 🔧 **易于集成**: 零配置使用,支持自定义 webhook 和发送器
|
- 📊 **错误统计**: 内置统计功能,便于数据分析
|
||||||
- 📱 **多平台支持**: 支持 H5、微信小程序、App 等 UniApp 支持的所有平台
|
- 🔧 **高度可定制**: 支持自定义发送器和格式化函数
|
||||||
|
- 📱 **全平台支持**: H5、微信小程序、App、支付宝小程序等
|
||||||
- 🛡️ **类型安全**: 完整的 TypeScript 类型定义
|
- 🛡️ **类型安全**: 完整的 TypeScript 类型定义
|
||||||
- 📦 **模块化**: 支持 ESM、CommonJS、UMD 多种模块格式
|
- 📦 **多格式输出**: 支持 ESM、CommonJS、UMD 格式
|
||||||
|
|
||||||
## 📦 安装
|
## 📦 安装
|
||||||
|
|
||||||
@@ -35,10 +36,10 @@ pnpm add uniapp-error-monitor
|
|||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
|
|
||||||
### 基础使用
|
### 方式一:命名导出(推荐)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { initErrorMonitor } from 'uniapp-error-monitor'
|
import { initErrorMonitor, reportError, getErrorStats, wrapPromise } from 'uniapp-error-monitor'
|
||||||
|
|
||||||
// 初始化错误监控
|
// 初始化错误监控
|
||||||
initErrorMonitor({
|
initErrorMonitor({
|
||||||
@@ -49,56 +50,30 @@ initErrorMonitor({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 手动上报错误
|
// 手动上报错误
|
||||||
import { reportError } from 'uniapp-error-monitor'
|
|
||||||
|
|
||||||
reportError('manual', new Error('自定义错误'), {
|
reportError('manual', new Error('自定义错误'), {
|
||||||
page: 'index',
|
page: 'index',
|
||||||
action: '用户操作失败'
|
action: '用户操作失败'
|
||||||
})
|
})
|
||||||
```
|
|
||||||
|
|
||||||
### Promise 包装
|
// Promise 包装(自动捕获 Promise 错误)
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { wrapPromise } from 'uniapp-error-monitor'
|
|
||||||
|
|
||||||
// 自动捕获 Promise 错误
|
|
||||||
const result = await wrapPromise(
|
const result = await wrapPromise(
|
||||||
fetch('https://api.example.com/data')
|
fetch('https://api.example.com/data')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 获取错误统计
|
||||||
|
const stats = getErrorStats()
|
||||||
|
console.log('错误统计:', stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📋 配置选项
|
### 方式二:类实例(高级用法)
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface ErrorMonitorOptions {
|
|
||||||
// 基础配置
|
|
||||||
webhookUrl: string // Webhook 地址(必填)
|
|
||||||
enableGlobalError?: boolean // 启用全局错误捕获(默认:true)
|
|
||||||
enablePromiseError?: boolean // 启用 Promise 错误捕获(默认:true)
|
|
||||||
enableConsoleError?: boolean // 启用 console.error 捕获(默认:false)
|
|
||||||
|
|
||||||
// 重试配置
|
|
||||||
maxRetries?: number // 最大重试次数(默认:3)
|
|
||||||
retryDelay?: number // 重试延迟时间(ms)(默认:1000)
|
|
||||||
|
|
||||||
// 高级配置
|
|
||||||
forceEnable?: boolean // 强制启用错误监控(忽略环境检查)
|
|
||||||
formatter?: (error: ErrorInfo) => string // 自定义格式化函数
|
|
||||||
sender?: (payload: ErrorInfo) => Promise<void> // 自定义发送器
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 高级使用
|
|
||||||
|
|
||||||
### 自定义发送器
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { ErrorMonitor } from 'uniapp-error-monitor'
|
import { ErrorMonitor } from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
// 创建自定义实例
|
||||||
const errorMonitor = new ErrorMonitor()
|
const errorMonitor = new ErrorMonitor()
|
||||||
|
|
||||||
// 使用自定义发送器
|
// 设置自定义发送器
|
||||||
errorMonitor.setSender(async (errorInfo) => {
|
errorMonitor.setSender(async (errorInfo) => {
|
||||||
// 发送到自己的服务器
|
// 发送到自己的服务器
|
||||||
await fetch('/api/errors', {
|
await fetch('/api/errors', {
|
||||||
@@ -108,18 +83,6 @@ errorMonitor.setSender(async (errorInfo) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
errorMonitor.init({
|
|
||||||
webhookUrl: 'custom-sender' // 使用自定义发送器时,webhookUrl 可以设置为任意值
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 自定义错误格式化
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { ErrorMonitor } from 'uniapp-error-monitor'
|
|
||||||
|
|
||||||
const errorMonitor = new ErrorMonitor()
|
|
||||||
|
|
||||||
// 设置自定义格式化函数
|
// 设置自定义格式化函数
|
||||||
errorMonitor.setFormatter((errorInfo) => {
|
errorMonitor.setFormatter((errorInfo) => {
|
||||||
return `🔴 错误详情:
|
return `🔴 错误详情:
|
||||||
@@ -129,56 +92,181 @@ errorMonitor.setFormatter((errorInfo) => {
|
|||||||
时间:${new Date(errorInfo.timestamp).toLocaleString()}`
|
时间:${new Date(errorInfo.timestamp).toLocaleString()}`
|
||||||
})
|
})
|
||||||
|
|
||||||
errorMonitor.init({
|
// 初始化
|
||||||
|
errorMonitor.initErrorMonitor({
|
||||||
webhookUrl: 'your-webhook-url'
|
webhookUrl: 'your-webhook-url'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 使用实例方法
|
||||||
|
errorMonitor.reportError('api', new Error('接口调用失败'))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式三:默认实例(向后兼容)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import ErrorMonitor from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
// 使用默认实例
|
||||||
|
ErrorMonitor.initErrorMonitor({
|
||||||
|
webhookUrl: 'https://your-webhook-url.com'
|
||||||
|
})
|
||||||
|
|
||||||
|
ErrorMonitor.reportError('manual', new Error('测试错误'))
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ 配置选项
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ErrorMonitorOptions {
|
||||||
|
// 基础配置
|
||||||
|
webhookUrl?: string // Webhook 地址(可选,使用环境变量)
|
||||||
|
enableGlobalError?: boolean // 启用全局错误捕获(默认:true)
|
||||||
|
enablePromiseError?: boolean // 启用 Promise 错误捕获(默认:true)
|
||||||
|
enableConsoleError?: boolean // 启用 console.error 捕获(默认:false)
|
||||||
|
|
||||||
|
// 重试配置
|
||||||
|
maxRetries?: number // 最大重试次数(默认:3)
|
||||||
|
retryDelay?: number // 重试延迟时间(ms)(默认:1000)
|
||||||
|
|
||||||
|
// 高级配置
|
||||||
|
forceEnable?: boolean // 强制启用错误监控(忽略环境检查)
|
||||||
|
sender?: (errorInfo: ErrorInfo) => Promise<void> // 自定义发送器
|
||||||
|
formatter?: (errorInfo: ErrorInfo) => string // 自定义格式化函数
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 错误类型
|
||||||
|
|
||||||
|
| 类型 | 说明 | 自动捕获 | 手动上报 | 触发场景 |
|
||||||
|
|------|------|----------|----------|----------|
|
||||||
|
| `global` | 全局 JavaScript 错误 | ✅ | ❌ | `window.onerror` |
|
||||||
|
| `promise` | Promise 拒绝错误 | ✅ | ❌ | `unhandledrejection` |
|
||||||
|
| `console` | console.error 输出 | ✅ | ❌ | 启用后自动捕获 |
|
||||||
|
| `miniProgram` | 小程序特定错误 | ✅ | ❌ | `uni.onError`, `uni.onPageNotFound` |
|
||||||
|
| `network` | 网络请求失败 | ✅ | ❌ | 拦截的 `uni.request` 失败 |
|
||||||
|
| `api` | API 接口错误 | ❌ | ✅ | 手动调用 `reportError` |
|
||||||
|
| `manual` | 手动上报错误 | ❌ | ✅ | 手动调用 `reportError` |
|
||||||
|
|
||||||
|
## 🔧 高级用法
|
||||||
|
|
||||||
|
### 环境检测
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { getEnvironmentInfo } from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
const envInfo = getEnvironmentInfo()
|
||||||
|
|
||||||
|
if (envInfo.isProduction) {
|
||||||
|
console.log('生产环境,错误监控已启用')
|
||||||
|
} else {
|
||||||
|
console.log('开发环境,错误监控已禁用')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重置统计
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { resetErrorStats } from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
// 重置错误统计(在页面刷新或特定事件后)
|
||||||
|
resetErrorStats()
|
||||||
|
console.log('错误统计已重置')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 丰富的错误上下文
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
reportError('global', new Error('页面崩溃'), {
|
||||||
|
// 用户信息
|
||||||
|
userId: 'user123',
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
|
||||||
|
// 页面信息
|
||||||
|
currentPage: getCurrentPageName(),
|
||||||
|
routeParams: getCurrentPage()?.$page?.fullPath,
|
||||||
|
|
||||||
|
// 业务信息
|
||||||
|
action: '用户点击按钮',
|
||||||
|
component: 'UserProfile',
|
||||||
|
|
||||||
|
// 性能信息
|
||||||
|
loadTime: performance.now(),
|
||||||
|
memoryUsage: performance.memory?.usedJSHeapSize,
|
||||||
|
|
||||||
|
// 自定义数据
|
||||||
|
customData: {
|
||||||
|
sessionId: getSessionId(),
|
||||||
|
feature: 'user_management'
|
||||||
|
}
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### 获取错误统计
|
### 批量错误处理
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import { getErrorStats } from 'uniapp-error-monitor'
|
// 定时检查错误状态
|
||||||
|
setInterval(() => {
|
||||||
const stats = getErrorStats()
|
const stats = getErrorStats()
|
||||||
console.log('错误统计:', stats)
|
if (stats.total > 10) {
|
||||||
/*
|
console.warn('检测到大量错误:', stats)
|
||||||
输出:
|
// 可以发送告警或执行其他处理逻辑
|
||||||
{
|
}
|
||||||
total: 5,
|
}, 60000) // 每分钟检查一次
|
||||||
global: 2,
|
|
||||||
promise: 1,
|
|
||||||
console: 0,
|
|
||||||
miniProgram: 1,
|
|
||||||
network: 1,
|
|
||||||
lastErrorTime: 1640995200000
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📊 错误类型说明
|
## 🛡️ TypeScript 支持
|
||||||
|
|
||||||
| 错误类型 | 说明 | 触发条件 |
|
完整的类型安全支持:
|
||||||
|---------|------|---------|
|
|
||||||
| `global` | 全局 JavaScript 错误 | `window.onerror` 捕获 |
|
|
||||||
| `promise` | 未处理的 Promise 拒绝 | `unhandledrejection` 事件 |
|
|
||||||
| `console` | console.error 输出 | 手动启用后捕获 |
|
|
||||||
| `miniProgram` | 小程序特定错误 | `uni.onError`, `uni.onPageNotFound` |
|
|
||||||
| `network` | 网络请求失败 | 拦截的 `uni.request` 失败 |
|
|
||||||
| `api` | API 接口错误 | 手动上报的接口错误 |
|
|
||||||
| `manual` | 手动上报的错误 | 手动调用 `reportError` |
|
|
||||||
|
|
||||||
## 🏗️ 构建配置
|
```typescript
|
||||||
|
import type {
|
||||||
|
ErrorMonitorOptions,
|
||||||
|
ErrorType,
|
||||||
|
ErrorStats,
|
||||||
|
EnvironmentInfo,
|
||||||
|
ErrorInfo
|
||||||
|
} from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
// 类型安全的配置
|
||||||
|
const options: ErrorMonitorOptions = {
|
||||||
|
webhookUrl: 'https://example.com/webhook',
|
||||||
|
enableGlobalError: true,
|
||||||
|
enablePromiseError: true,
|
||||||
|
maxRetries: 5,
|
||||||
|
retryDelay: 2000,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型安全的错误上报
|
||||||
|
const reportTypeSafeError = (type: ErrorType, message: string) => {
|
||||||
|
reportError(type, new Error(message), {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
userId: '12345'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📱 平台兼容性
|
||||||
|
|
||||||
|
- ✅ **微信小程序**: 完整支持,包括所有错误类型
|
||||||
|
- ✅ **H5**: 完整支持,支持所有现代浏览器
|
||||||
|
- ✅ **App (iOS/Android)**: 完整支持
|
||||||
|
- ✅ **支付宝小程序**: 基本支持
|
||||||
|
- ✅ **字节跳动小程序**: 基本支持
|
||||||
|
- ✅ **百度小程序**: 基本支持
|
||||||
|
- ✅ **快应用**: 基本支持
|
||||||
|
|
||||||
|
## 🏗️ 环境配置
|
||||||
|
|
||||||
### 环境变量
|
### 环境变量
|
||||||
|
|
||||||
在你的项目中设置环境变量:
|
在你的项目中设置环境变量:
|
||||||
|
|
||||||
```javascript
|
```bash
|
||||||
// .env 文件
|
# .env 文件
|
||||||
VITE_WEBHOOK=https://your-webhook-url.com
|
VITE_WEBHOOK=https://your-webhook-url.com
|
||||||
```
|
```
|
||||||
|
|
||||||
### 开发环境自动禁用
|
### 环境检测逻辑
|
||||||
|
|
||||||
插件会在以下情况下自动禁用(非生产环境):
|
插件会在以下情况下自动禁用(非生产环境):
|
||||||
|
|
||||||
@@ -188,36 +276,22 @@ VITE_WEBHOOK=https://your-webhook-url.com
|
|||||||
|
|
||||||
如需强制启用,设置 `forceEnable: true`。
|
如需强制启用,设置 `forceEnable: true`。
|
||||||
|
|
||||||
## 🔍 TypeScript 支持
|
## 📦 构建产物
|
||||||
|
|
||||||
完整的 TypeScript 类型支持:
|
构建后会在 `dist/` 目录生成:
|
||||||
|
|
||||||
```typescript
|
- `index.js` - CommonJS 格式(Node.js)
|
||||||
import { ErrorMonitor, ErrorMonitorOptions, ErrorType, ErrorStats } from 'uniapp-error-monitor'
|
- `index.esm.js` - ES Module 格式(现代构建工具)
|
||||||
|
- `index.umd.js` - UMD 格式(浏览器直接使用)
|
||||||
|
- `index.umd.min.js` - UMD 压缩版
|
||||||
|
- `index.d.ts` - TypeScript 类型声明
|
||||||
|
- `*.map` - Source map 文件
|
||||||
|
|
||||||
const options: ErrorMonitorOptions = {
|
## 🔧 开发调试
|
||||||
webhookUrl: 'https://example.com/webhook',
|
|
||||||
maxRetries: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorMonitor = new ErrorMonitor(options)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📱 平台兼容性
|
|
||||||
|
|
||||||
- ✅ **微信小程序**: 完整支持
|
|
||||||
- ✅ **H5**: 完整支持
|
|
||||||
- ✅ **App (iOS/Android)**: 完整支持
|
|
||||||
- ✅ **支付宝小程序**: 基本支持
|
|
||||||
- ✅ **字节跳动小程序**: 基本支持
|
|
||||||
- ✅ **百度小程序**: 基本支持
|
|
||||||
- ✅ **快应用**: 基本支持
|
|
||||||
|
|
||||||
## 🛠️ 开发调试
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆项目
|
# 克隆项目
|
||||||
git clone https://github.com/yuentao/uniapp-error-monitor.git
|
git clone https://github.com/your-username/uniapp-error-monitor.git
|
||||||
cd uniapp-error-monitor
|
cd uniapp-error-monitor
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
@@ -229,28 +303,24 @@ npm run dev
|
|||||||
# 类型检查
|
# 类型检查
|
||||||
npm run type-check
|
npm run type-check
|
||||||
|
|
||||||
|
# 代码检查
|
||||||
|
npm run lint
|
||||||
|
|
||||||
# 构建
|
# 构建
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# 单元测试
|
# 发布
|
||||||
npm run test
|
npm publish
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📦 构建产物
|
## 📄 使用示例
|
||||||
|
|
||||||
构建后会在 `dist/` 目录生成:
|
完整的使用示例请参考 [USAGE_EXAMPLES.js](./USAGE_EXAMPLES.js) 文件。
|
||||||
|
|
||||||
- `index.js` - CommonJS 格式
|
## 🤝 贡献指南
|
||||||
- `index.mjs` - ES Module 格式
|
|
||||||
- `index.umd.js` - UMD 格式(用于浏览器)
|
欢迎提交 Issue 和 Pull Request!
|
||||||
- `index.umd.min.js` - UMD 压缩版
|
|
||||||
- `index.d.ts` - TypeScript 类型声明
|
|
||||||
- `types/` - 类型声明目录
|
|
||||||
|
|
||||||
## 📄 许可证
|
## 📄 许可证
|
||||||
|
|
||||||
MIT License - 详见 [LICENSE](LICENSE) 文件
|
MIT License - 详见 [LICENSE](./LICENSE) 文件
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
⭐ 如果这个项目对你有帮助,请给它一个星标!
|
|
||||||
236
USAGE_EXAMPLES.js
Normal file
236
USAGE_EXAMPLES.js
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
/**
|
||||||
|
* UniApp Error Monitor 使用示例
|
||||||
|
* 展示如何在不同场景下使用错误监控工具
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 1. 基础使用 - 命名导出方式(推荐)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 方式一:导入所有命名导出
|
||||||
|
import {
|
||||||
|
initErrorMonitor,
|
||||||
|
reportError,
|
||||||
|
getErrorStats,
|
||||||
|
wrapPromise
|
||||||
|
} from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
// 初始化错误监控
|
||||||
|
initErrorMonitor({
|
||||||
|
webhookUrl: 'https://your-webhook-url.com', // 必填
|
||||||
|
enableGlobalError: true, // 启用全局错误捕获
|
||||||
|
enablePromiseError: true, // 启用 Promise 错误捕获
|
||||||
|
enableConsoleError: false, // 禁用 console.error 捕获
|
||||||
|
maxRetries: 3, // 最大重试次数
|
||||||
|
retryDelay: 1000, // 重试延迟时间(ms)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 手动上报错误
|
||||||
|
reportError('manual', new Error('自定义错误'), {
|
||||||
|
page: 'index',
|
||||||
|
action: '用户操作失败'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取错误统计
|
||||||
|
const stats = getErrorStats()
|
||||||
|
console.log('错误统计:', stats)
|
||||||
|
|
||||||
|
// Promise 包装示例
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await wrapPromise(
|
||||||
|
fetch('https://api.example.com/data')
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
} catch (error) {
|
||||||
|
// 错误已经被自动捕获和上报
|
||||||
|
console.error('获取数据失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 2. 高级使用 - 类实例方式
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import { ErrorMonitor } from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
// 创建自定义实例
|
||||||
|
const errorMonitor = new ErrorMonitor({
|
||||||
|
webhookUrl: 'https://your-webhook-url.com',
|
||||||
|
enableGlobalError: true,
|
||||||
|
enablePromiseError: true,
|
||||||
|
forceEnable: true // 强制启用(忽略环境检查)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置自定义发送器
|
||||||
|
errorMonitor.setSender(async (errorInfo) => {
|
||||||
|
// 发送到自己的服务器
|
||||||
|
await fetch('/api/errors', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(errorInfo)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置自定义格式化函数
|
||||||
|
errorMonitor.setFormatter((errorInfo) => {
|
||||||
|
return `🔴 错误详情:
|
||||||
|
类型:${errorInfo.type}
|
||||||
|
消息:${errorInfo.error}
|
||||||
|
页面:${errorInfo.page}
|
||||||
|
时间:${new Date(errorInfo.timestamp).toLocaleString()}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
errorMonitor.initErrorMonitor()
|
||||||
|
|
||||||
|
// 使用实例方法
|
||||||
|
errorMonitor.reportError('api', new Error('接口调用失败'), {
|
||||||
|
api: '/user/profile',
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 3. 向后兼容 - 默认实例
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import ErrorMonitorDefault from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
// 使用默认实例(向后兼容)
|
||||||
|
ErrorMonitorDefault.initErrorMonitor({
|
||||||
|
webhookUrl: 'https://your-webhook-url.com'
|
||||||
|
})
|
||||||
|
|
||||||
|
ErrorMonitorDefault.reportError('manual', new Error('测试错误'))
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 4. TypeScript 类型安全使用
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ErrorMonitorOptions,
|
||||||
|
ErrorType,
|
||||||
|
ErrorStats,
|
||||||
|
EnvironmentInfo
|
||||||
|
} from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
// 类型安全的配置
|
||||||
|
const options: ErrorMonitorOptions = {
|
||||||
|
webhookUrl: 'https://example.com/webhook',
|
||||||
|
enableGlobalError: true,
|
||||||
|
enablePromiseError: true,
|
||||||
|
maxRetries: 5,
|
||||||
|
retryDelay: 2000,
|
||||||
|
forceEnable: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型安全的错误上报
|
||||||
|
const reportTypeSafeError = (type: ErrorType, message: string) => {
|
||||||
|
reportError(type, new Error(message), {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
userId: '12345'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型安全的统计获取
|
||||||
|
const getSafeStats = (): ErrorStats => {
|
||||||
|
return getErrorStats()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 5. 环境检测和使用
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import { getEnvironmentInfo } from 'uniapp-error-monitor'
|
||||||
|
|
||||||
|
// 获取环境信息
|
||||||
|
const envInfo: EnvironmentInfo = getEnvironmentInfo()
|
||||||
|
|
||||||
|
if (envInfo.isProduction) {
|
||||||
|
// 生产环境逻辑
|
||||||
|
console.log('生产环境,错误监控已启用')
|
||||||
|
} else {
|
||||||
|
// 开发环境逻辑
|
||||||
|
console.log('开发环境,错误监控已禁用')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 6. 错误类型示例
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 全局错误 - 自动捕获,无需手动上报
|
||||||
|
// window.onerror 触发
|
||||||
|
|
||||||
|
// Promise 错误 - 通过 wrapPromise 包装或自动捕获
|
||||||
|
wrapPromise(someAsyncFunction())
|
||||||
|
|
||||||
|
// Console 错误 - 需要 enableConsoleError: true
|
||||||
|
console.error('这条错误会被捕获')
|
||||||
|
|
||||||
|
// 小程序错误 - 自动捕获
|
||||||
|
// uni.onError, uni.onPageNotFound 触发
|
||||||
|
|
||||||
|
// 网络错误 - 自动捕获
|
||||||
|
// 拦截的 uni.request 失败
|
||||||
|
|
||||||
|
// API 错误 - 手动上报
|
||||||
|
reportError('api', new Error('接口调用失败'), {
|
||||||
|
url: '/api/users',
|
||||||
|
method: 'GET',
|
||||||
|
statusCode: 500
|
||||||
|
})
|
||||||
|
|
||||||
|
// 手动错误 - 手动上报
|
||||||
|
reportError('manual', new Error('用户操作失败'), {
|
||||||
|
action: 'submitForm',
|
||||||
|
formData: { name: 'John' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 7. 错误上下文信息
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 添加丰富的错误上下文
|
||||||
|
reportError('global', new Error('页面崩溃'), {
|
||||||
|
// 用户信息
|
||||||
|
userId: 'user123',
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
|
||||||
|
// 页面信息
|
||||||
|
currentPage: getCurrentPageName(),
|
||||||
|
routeParams: getCurrentPage()?.$page?.fullPath,
|
||||||
|
|
||||||
|
// 业务信息
|
||||||
|
action: '用户点击按钮',
|
||||||
|
component: 'UserProfile',
|
||||||
|
|
||||||
|
// 性能信息
|
||||||
|
loadTime: performance.now(),
|
||||||
|
memoryUsage: performance.memory?.usedJSHeapSize,
|
||||||
|
|
||||||
|
// 自定义数据
|
||||||
|
customData: {
|
||||||
|
sessionId: getSessionId(),
|
||||||
|
feature: 'user_management'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 8. 批量错误处理
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 重置错误统计(在页面刷新或特定事件后)
|
||||||
|
const resetStats = () => {
|
||||||
|
import { resetErrorStats } from 'uniapp-error-monitor'
|
||||||
|
resetErrorStats()
|
||||||
|
console.log('错误统计已重置')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定时检查错误状态
|
||||||
|
setInterval(() => {
|
||||||
|
const stats = getErrorStats()
|
||||||
|
if (stats.total > 10) {
|
||||||
|
console.warn('检测到大量错误:', stats)
|
||||||
|
// 可以发送告警或执行其他处理逻辑
|
||||||
|
}
|
||||||
|
}, 60000) // 每分钟检查一次
|
||||||
37
build.sh
37
build.sh
@@ -1,37 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 清理 dist 目录
|
|
||||||
echo "🧹 清理构建目录..."
|
|
||||||
rm -rf dist
|
|
||||||
rm -rf types
|
|
||||||
|
|
||||||
# TypeScript 类型检查和编译
|
|
||||||
echo "🔍 TypeScript 类型检查..."
|
|
||||||
npm run type-check
|
|
||||||
|
|
||||||
# 生成类型声明文件
|
|
||||||
echo "📝 生成类型声明文件..."
|
|
||||||
npm run build:types
|
|
||||||
|
|
||||||
# Rollup 构建
|
|
||||||
echo "📦 执行 Rollup 构建..."
|
|
||||||
npm run build:dist
|
|
||||||
|
|
||||||
# 复制类型文件到 dist 目录
|
|
||||||
echo "📋 复制类型文件..."
|
|
||||||
cp -r types/* dist/types/
|
|
||||||
|
|
||||||
# 构建完成
|
|
||||||
echo "✅ 构建完成!"
|
|
||||||
echo "📁 输出目录: dist/"
|
|
||||||
echo "🎯 主要文件:"
|
|
||||||
echo " - dist/index.js (CommonJS)"
|
|
||||||
echo " - dist/index.mjs (ESM)"
|
|
||||||
echo " - dist/index.umd.js (UMD)"
|
|
||||||
echo " - dist/index.d.ts (TypeScript 类型)"
|
|
||||||
echo ""
|
|
||||||
echo "📊 统计信息:"
|
|
||||||
ls -lh dist/
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "🚀 可以执行 'npm publish' 发布到 npm!"
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uniapp-error-monitor",
|
"name": "uniapp-error-monitor",
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"description": "专门为UniApp环境设计的错误监控和上报工具,支持全局错误捕获、Promise错误捕获、网络错误捕获等",
|
"description": "专门为UniApp环境设计的错误监控和上报工具,支持全局错误捕获、Promise错误捕获、网络错误捕获等",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.esm.js",
|
"module": "dist/index.esm.js",
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
"build": "rollup -c",
|
"build": "rollup -c",
|
||||||
"dev": "rollup -c -w",
|
"dev": "rollup -c -w",
|
||||||
"clean": "rimraf dist",
|
"clean": "rimraf dist",
|
||||||
"lint": "eslint src/**/*.js",
|
"type-check": "tsc --noEmit",
|
||||||
"lint:fix": "eslint src/**/*.js --fix",
|
"prepublishOnly": "npm run clean && npm run type-check && npm run build",
|
||||||
"prepublishOnly": "npm run clean && npm run build"
|
"prepare": "npm run build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.28.5",
|
"@babel/core": "^7.28.5",
|
||||||
|
|||||||
@@ -13,6 +13,25 @@ const external = [
|
|||||||
'uniapp'
|
'uniapp'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 复制 TypeScript 定义文件到 dist 目录
|
||||||
|
function copyTypescriptDefinitions() {
|
||||||
|
return {
|
||||||
|
name: 'copy-types',
|
||||||
|
writeBundle() {
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const srcTypesFile = path.resolve(__dirname, 'src/index.d.ts')
|
||||||
|
const destTypesFile = path.resolve(__dirname, 'dist/index.d.ts')
|
||||||
|
|
||||||
|
if (fs.existsSync(srcTypesFile)) {
|
||||||
|
fs.copyFileSync(srcTypesFile, destTypesFile)
|
||||||
|
console.log('✅ TypeScript definitions copied to dist/index.d.ts')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
typescript(),
|
typescript(),
|
||||||
babel({
|
babel({
|
||||||
@@ -27,7 +46,8 @@ const plugins = [
|
|||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
]
|
]
|
||||||
})
|
}),
|
||||||
|
copyTypescriptDefinitions()
|
||||||
]
|
]
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
@@ -38,7 +58,8 @@ export default defineConfig([
|
|||||||
{
|
{
|
||||||
file: pkg.module,
|
file: pkg.module,
|
||||||
format: 'es',
|
format: 'es',
|
||||||
sourcemap: true
|
sourcemap: true,
|
||||||
|
exports: 'named'
|
||||||
},
|
},
|
||||||
// UMD 输出 (用于浏览器)
|
// UMD 输出 (用于浏览器)
|
||||||
{
|
{
|
||||||
@@ -48,13 +69,15 @@ export default defineConfig([
|
|||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
globals: {
|
globals: {
|
||||||
'vue': 'Vue'
|
'vue': 'Vue'
|
||||||
}
|
},
|
||||||
|
exports: 'named'
|
||||||
},
|
},
|
||||||
// CommonJS 输出 (用于 Node.js)
|
// CommonJS 输出 (用于 Node.js)
|
||||||
{
|
{
|
||||||
file: pkg.main,
|
file: pkg.main,
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
sourcemap: true
|
sourcemap: true,
|
||||||
|
exports: 'named'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
external,
|
external,
|
||||||
@@ -69,6 +92,7 @@ export default defineConfig([
|
|||||||
format: 'umd',
|
format: 'umd',
|
||||||
name: 'UniAppErrorMonitor',
|
name: 'UniAppErrorMonitor',
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
|
exports: 'named',
|
||||||
plugins: [terser()]
|
plugins: [terser()]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
187
src/ErrorMonitor.d.ts
vendored
187
src/ErrorMonitor.d.ts
vendored
@@ -1,187 +0,0 @@
|
|||||||
/**
|
|
||||||
* UniApp错误监控器类型定义
|
|
||||||
* 专门为UniApp环境设计的错误监控和上报工具
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare class ErrorMonitor {
|
|
||||||
/**
|
|
||||||
* 初始化错误监控器
|
|
||||||
* @param options 配置选项
|
|
||||||
*/
|
|
||||||
init(options?: ErrorMonitorOptions): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动上报错误
|
|
||||||
* @param type 错误类型
|
|
||||||
* @param error 错误对象或错误信息
|
|
||||||
* @param context 错误上下文信息
|
|
||||||
* @param forceSend 强制发送(忽略环境检查)
|
|
||||||
*/
|
|
||||||
reportError(
|
|
||||||
type?: ErrorType,
|
|
||||||
error?: Error | object | string,
|
|
||||||
context?: object,
|
|
||||||
forceSend?: boolean
|
|
||||||
): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 包装Promise,自动捕获Promise错误
|
|
||||||
* @param promise 要包装的Promise
|
|
||||||
* @returns 包装后的Promise
|
|
||||||
*/
|
|
||||||
wrapPromise<T>(promise: Promise<T>): Promise<T>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取错误统计信息
|
|
||||||
* @returns 错误统计信息
|
|
||||||
*/
|
|
||||||
getErrorStats(): ErrorStats;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置错误统计
|
|
||||||
*/
|
|
||||||
resetErrorStats(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前环境信息
|
|
||||||
* @returns 环境信息
|
|
||||||
*/
|
|
||||||
getEnvironmentInfo(): EnvironmentInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface ErrorMonitorOptions {
|
|
||||||
/**
|
|
||||||
* 是否启用全局错误捕获
|
|
||||||
* @default true
|
|
||||||
*/
|
|
||||||
enableGlobalError?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否启用Promise错误捕获
|
|
||||||
* @default true
|
|
||||||
*/
|
|
||||||
enablePromiseError?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否启用console.error捕获
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
enableConsoleError?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义webhook地址
|
|
||||||
*/
|
|
||||||
webhookUrl?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送失败时最大重试次数
|
|
||||||
* @default 3
|
|
||||||
*/
|
|
||||||
maxRetries?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重试延迟时间(毫秒)
|
|
||||||
* @default 1000
|
|
||||||
*/
|
|
||||||
retryDelay?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制启用错误监控(忽略环境检查)
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
forceEnable?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义错误格式化函数
|
|
||||||
*/
|
|
||||||
customFormatter?: (errorInfo: ErrorInfo) => string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义发送函数
|
|
||||||
*/
|
|
||||||
customSender?: (errorInfo: ErrorInfo) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare type ErrorType =
|
|
||||||
| 'manual'
|
|
||||||
| 'api'
|
|
||||||
| 'network'
|
|
||||||
| 'global'
|
|
||||||
| 'promise'
|
|
||||||
| 'console'
|
|
||||||
| 'miniProgram';
|
|
||||||
|
|
||||||
declare interface ErrorStats {
|
|
||||||
total: number;
|
|
||||||
global: number;
|
|
||||||
promise: number;
|
|
||||||
console: number;
|
|
||||||
miniProgram: number;
|
|
||||||
api: number;
|
|
||||||
network: number;
|
|
||||||
manual: number;
|
|
||||||
lastErrorTime: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface EnvironmentInfo {
|
|
||||||
isProduction: boolean;
|
|
||||||
mode: string;
|
|
||||||
platform: string;
|
|
||||||
errorMonitorEnabled: boolean;
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface ErrorInfo {
|
|
||||||
type: ErrorType;
|
|
||||||
error: string | object;
|
|
||||||
stack?: string | null;
|
|
||||||
context?: object;
|
|
||||||
timestamp: number;
|
|
||||||
url: string;
|
|
||||||
userAgent: string;
|
|
||||||
page: string;
|
|
||||||
|
|
||||||
// API错误特有字段
|
|
||||||
statusCode?: number;
|
|
||||||
statusText?: string;
|
|
||||||
responseTime?: number;
|
|
||||||
requestData?: object;
|
|
||||||
requestHeaders?: object;
|
|
||||||
requestId?: string;
|
|
||||||
environment?: string;
|
|
||||||
|
|
||||||
// 网络错误特有字段
|
|
||||||
retryCount?: number;
|
|
||||||
networkType?: string;
|
|
||||||
isConnected?: boolean;
|
|
||||||
|
|
||||||
// 全局错误特有字段
|
|
||||||
message?: string;
|
|
||||||
source?: string;
|
|
||||||
lineno?: number;
|
|
||||||
colno?: number;
|
|
||||||
|
|
||||||
// Promise错误特有字段
|
|
||||||
reason?: string | object;
|
|
||||||
|
|
||||||
// Console错误特有字段
|
|
||||||
args?: string[];
|
|
||||||
|
|
||||||
// 小程序错误特有字段
|
|
||||||
path?: string;
|
|
||||||
query?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出默认实例
|
|
||||||
declare const ErrorMonitorInstance: ErrorMonitor;
|
|
||||||
export default ErrorMonitorInstance;
|
|
||||||
|
|
||||||
// 导出类型
|
|
||||||
export {
|
|
||||||
ErrorMonitor,
|
|
||||||
ErrorMonitorOptions,
|
|
||||||
ErrorType,
|
|
||||||
ErrorStats,
|
|
||||||
EnvironmentInfo,
|
|
||||||
ErrorInfo,
|
|
||||||
};
|
|
||||||
@@ -1,710 +0,0 @@
|
|||||||
/**
|
|
||||||
* UniApp错误监控器
|
|
||||||
* 专门为UniApp环境设计的错误监控和上报工具
|
|
||||||
* 支持全局错误捕获、Promise错误捕获、网络错误捕获等
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ErrorMonitor {
|
|
||||||
constructor() {
|
|
||||||
// 错误统计信息
|
|
||||||
this.errorStats = {
|
|
||||||
total: 0,
|
|
||||||
global: 0,
|
|
||||||
promise: 0,
|
|
||||||
console: 0,
|
|
||||||
miniProgram: 0,
|
|
||||||
api: 0,
|
|
||||||
network: 0,
|
|
||||||
manual: 0,
|
|
||||||
lastErrorTime: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Promise包装工具
|
|
||||||
this.wrapPromise = null
|
|
||||||
|
|
||||||
// 项目信息
|
|
||||||
this.projectInfo = {
|
|
||||||
name: 'uniapp-error-monitor',
|
|
||||||
version: '1.0.0',
|
|
||||||
}
|
|
||||||
|
|
||||||
// 配置对象
|
|
||||||
this.config = null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化错误监控器
|
|
||||||
* @param {Object} options 配置选项
|
|
||||||
* @param {boolean} [options.enableGlobalError=true] 是否启用全局错误捕获
|
|
||||||
* @param {boolean} [options.enablePromiseError=true] 是否启用Promise错误捕获
|
|
||||||
* @param {boolean} [options.enableConsoleError=false] 是否启用console.error捕获
|
|
||||||
* @param {string} [options.webhookUrl] 自定义webhook地址
|
|
||||||
* @param {number} [options.maxRetries=3] 发送失败时最大重试次数
|
|
||||||
* @param {number} [options.retryDelay=1000] 重试延迟时间(毫秒)
|
|
||||||
* @param {boolean} [options.forceEnable=false] 强制启用错误监控(忽略环境检查)
|
|
||||||
* @param {Function} [options.customFormatter] 自定义错误格式化函数
|
|
||||||
* @param {Function} [options.customSender] 自定义发送函数
|
|
||||||
*/
|
|
||||||
init(options = {}) {
|
|
||||||
const config = {
|
|
||||||
enableGlobalError: true,
|
|
||||||
enablePromiseError: true,
|
|
||||||
enableConsoleError: false,
|
|
||||||
webhookUrl: '',
|
|
||||||
maxRetries: 3,
|
|
||||||
retryDelay: 1000,
|
|
||||||
forceEnable: false,
|
|
||||||
customFormatter: null,
|
|
||||||
customSender: null,
|
|
||||||
...options,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 环境检查:默认在生产环境下启用错误监控
|
|
||||||
if (!config.forceEnable && !this._isProduction()) {
|
|
||||||
console.info('[ErrorMonitor] 当前为非生产环境,错误监控已禁用')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config = config
|
|
||||||
|
|
||||||
// 全局错误捕获
|
|
||||||
if (config.enableGlobalError) {
|
|
||||||
this._setupGlobalErrorHandlers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Promise错误捕获
|
|
||||||
if (config.enablePromiseError) {
|
|
||||||
this._setupPromiseErrorHandlers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// console错误捕获
|
|
||||||
if (config.enableConsoleError) {
|
|
||||||
this._setupConsoleErrorHandlers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 小程序特定错误捕获
|
|
||||||
this._setupMiniProgramErrorHandlers()
|
|
||||||
|
|
||||||
console.log('[ErrorMonitor] 错误监控已初始化')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动上报错误
|
|
||||||
* @param {string} type 错误类型 ('manual', 'api', 'network', 'global', 'promise', 'console', 'miniProgram')
|
|
||||||
* @param {Error|Object} error 错误对象或错误信息
|
|
||||||
* @param {Object} [context] 错误上下文信息
|
|
||||||
* @param {boolean} [forceSend=false] 强制发送(忽略环境检查)
|
|
||||||
*/
|
|
||||||
reportError(type = 'manual', error, context = {}, forceSend = false) {
|
|
||||||
const errorInfo = {
|
|
||||||
type,
|
|
||||||
error: error instanceof Error ? error.message : error,
|
|
||||||
stack: error instanceof Error ? error.stack : null,
|
|
||||||
context,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: this._getCurrentPageName(),
|
|
||||||
|
|
||||||
// API错误特有字段
|
|
||||||
statusCode: error.statusCode,
|
|
||||||
statusText: error.statusText,
|
|
||||||
responseTime: error.responseTime,
|
|
||||||
requestData: error.requestData,
|
|
||||||
requestHeaders: error.requestHeaders,
|
|
||||||
requestId: error.requestId,
|
|
||||||
environment: error.environment,
|
|
||||||
|
|
||||||
// 网络错误特有字段
|
|
||||||
retryCount: error.retryCount,
|
|
||||||
networkType: error.networkType,
|
|
||||||
isConnected: error.isConnected,
|
|
||||||
}
|
|
||||||
|
|
||||||
this._updateErrorStats(type)
|
|
||||||
this._sendError(errorInfo, forceSend)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 包装Promise,自动捕获Promise错误
|
|
||||||
* @param {Promise} promise 要包装的Promise
|
|
||||||
* @returns {Promise} 包装后的Promise
|
|
||||||
*/
|
|
||||||
wrapPromise(promise) {
|
|
||||||
return promise.catch(error => {
|
|
||||||
this.reportError('promise', error)
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取错误统计信息
|
|
||||||
* @returns {Object} 错误统计信息
|
|
||||||
*/
|
|
||||||
getErrorStats() {
|
|
||||||
return { ...this.errorStats }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置错误统计
|
|
||||||
*/
|
|
||||||
resetErrorStats() {
|
|
||||||
this.errorStats = {
|
|
||||||
total: 0,
|
|
||||||
global: 0,
|
|
||||||
promise: 0,
|
|
||||||
console: 0,
|
|
||||||
miniProgram: 0,
|
|
||||||
api: 0,
|
|
||||||
network: 0,
|
|
||||||
manual: 0,
|
|
||||||
lastErrorTime: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前环境信息
|
|
||||||
* @returns {Object} 环境信息
|
|
||||||
*/
|
|
||||||
getEnvironmentInfo() {
|
|
||||||
return {
|
|
||||||
isProduction: this._isProduction(),
|
|
||||||
mode: this._getMode(),
|
|
||||||
platform: this._getUserAgent(),
|
|
||||||
errorMonitorEnabled: !!this.config,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置全局错误处理器
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_setupGlobalErrorHandlers() {
|
|
||||||
// Web环境
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.onerror = (message, source, lineno, colno, error) => {
|
|
||||||
this._handleGlobalError({
|
|
||||||
type: 'global',
|
|
||||||
message,
|
|
||||||
source,
|
|
||||||
lineno,
|
|
||||||
colno,
|
|
||||||
error,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理未捕获的Promise错误
|
|
||||||
window.addEventListener('unhandledrejection', event => {
|
|
||||||
this._handlePromiseError({
|
|
||||||
type: 'promise',
|
|
||||||
reason: event.reason,
|
|
||||||
promise: event.promise,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置Promise错误处理器
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_setupPromiseErrorHandlers() {
|
|
||||||
// 在UniApp环境中,wrapPromise方法会处理Promise错误
|
|
||||||
this.wrapPromise = promise => {
|
|
||||||
return promise.catch(error => {
|
|
||||||
this._handlePromiseError({
|
|
||||||
type: 'promise',
|
|
||||||
reason: error,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置console错误处理器
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_setupConsoleErrorHandlers() {
|
|
||||||
const originalError = console.error
|
|
||||||
console.error = (...args) => {
|
|
||||||
originalError.apply(console, args)
|
|
||||||
this._handleConsoleError({
|
|
||||||
type: 'console',
|
|
||||||
args: args.map(arg => this._serializeError(arg)),
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置小程序错误处理器
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_setupMiniProgramErrorHandlers() {
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
// 监听小程序错误事件
|
|
||||||
uni.onError &&
|
|
||||||
uni.onError(error => {
|
|
||||||
this._handleMiniProgramError({
|
|
||||||
type: 'miniProgram',
|
|
||||||
error,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听小程序页面错误
|
|
||||||
uni.onPageNotFound &&
|
|
||||||
uni.onPageNotFound(result => {
|
|
||||||
this._handleMiniProgramError({
|
|
||||||
type: 'pageNotFound',
|
|
||||||
path: result.path,
|
|
||||||
query: result.query,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听小程序网络请求错误
|
|
||||||
const originalRequest = uni.request
|
|
||||||
uni.request = options => {
|
|
||||||
return originalRequest({
|
|
||||||
...options,
|
|
||||||
fail: err => {
|
|
||||||
options.fail && options.fail(err)
|
|
||||||
this._handleNetworkError({
|
|
||||||
type: 'network',
|
|
||||||
url: options.url,
|
|
||||||
method: options.method,
|
|
||||||
error: err,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理全局错误
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleGlobalError(errorInfo) {
|
|
||||||
this._updateErrorStats('global')
|
|
||||||
this._sendError({
|
|
||||||
...errorInfo,
|
|
||||||
message: errorInfo.message || 'Unknown global error',
|
|
||||||
source: errorInfo.source || '',
|
|
||||||
lineno: errorInfo.lineno || 0,
|
|
||||||
colno: errorInfo.colno || 0,
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: this._getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理Promise错误
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handlePromiseError(errorInfo) {
|
|
||||||
this._updateErrorStats('promise')
|
|
||||||
this._sendError({
|
|
||||||
...errorInfo,
|
|
||||||
reason: this._serializeError(errorInfo.reason),
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: this._getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理console错误
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleConsoleError(errorInfo) {
|
|
||||||
this._updateErrorStats('console')
|
|
||||||
this._sendError({
|
|
||||||
...errorInfo,
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: this._getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理小程序错误
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleMiniProgramError(errorInfo) {
|
|
||||||
this._updateErrorStats('miniProgram')
|
|
||||||
this._sendError({
|
|
||||||
...errorInfo,
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: this._getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理网络错误
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_handleNetworkError(errorInfo) {
|
|
||||||
this._updateErrorStats('network')
|
|
||||||
this._sendError({
|
|
||||||
...errorInfo,
|
|
||||||
url: this._getCurrentUrl(),
|
|
||||||
userAgent: this._getUserAgent(),
|
|
||||||
page: this._getCurrentPageName(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新错误统计
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_updateErrorStats(type) {
|
|
||||||
this.errorStats.total++
|
|
||||||
this.errorStats[type] = (this.errorStats[type] || 0) + 1
|
|
||||||
this.errorStats.lastErrorTime = Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送错误信息
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _sendError(errorInfo, forceSend = false) {
|
|
||||||
// 环境检查
|
|
||||||
if (!forceSend && !this._isProduction() && !this.config?.forceEnable) {
|
|
||||||
console.info('[ErrorMonitor] 非生产环境,错误信息不发送:', errorInfo.type)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 使用自定义发送器或默认发送器
|
|
||||||
if (this.config?.customSender) {
|
|
||||||
await this.config.customSender(errorInfo)
|
|
||||||
} else {
|
|
||||||
await this._sendToWebhook(errorInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[ErrorMonitor] 错误信息已处理')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[ErrorMonitor] 发送错误信息失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送到webhook
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _sendToWebhook(errorInfo) {
|
|
||||||
const webhookUrl = this.config?.webhookUrl
|
|
||||||
if (!webhookUrl) {
|
|
||||||
console.warn('[ErrorMonitor] 未配置webhook地址')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化错误信息
|
|
||||||
const message = this.config?.customFormatter ? this.config.customFormatter(errorInfo) : this._formatErrorMessage(errorInfo)
|
|
||||||
|
|
||||||
// 使用uni.request发送POST请求(适配uniapp环境)
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
uni.request({
|
|
||||||
url: webhookUrl,
|
|
||||||
method: 'POST',
|
|
||||||
header: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
msgtype: 'text',
|
|
||||||
text: {
|
|
||||||
content: message,
|
|
||||||
mentioned_list: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
success: resolve,
|
|
||||||
fail: reject,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化错误消息
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_formatErrorMessage(errorInfo) {
|
|
||||||
const timestamp = new Date(errorInfo.timestamp).toLocaleString('zh-CN')
|
|
||||||
const systemInfo = uni.getSystemInfoSync?.()
|
|
||||||
|
|
||||||
let message = `🚨 JavaScript错误报告\n`
|
|
||||||
message += `📦 项目: ${systemInfo.appName || this.projectInfo.name}\n`
|
|
||||||
message += `🏷️ 版本: ${systemInfo.appVersion || this.projectInfo.version}\n`
|
|
||||||
message += `⏰ 时间: ${timestamp}\n`
|
|
||||||
message += `📱 页面: ${errorInfo.page || '未知页面'}\n`
|
|
||||||
message += `🌐 链接: ${errorInfo.url || '未知链接'}\n\n`
|
|
||||||
|
|
||||||
switch (errorInfo.type) {
|
|
||||||
case 'global':
|
|
||||||
message += `🔍 错误类型: 全局错误\n`
|
|
||||||
message += `📝 错误信息: ${errorInfo.message}\n`
|
|
||||||
if (errorInfo.source) {
|
|
||||||
message += `📂 文件: ${errorInfo.source}\n`
|
|
||||||
}
|
|
||||||
if (errorInfo.lineno) {
|
|
||||||
message += `📍 行号: ${errorInfo.lineno}:${errorInfo.colno}\n`
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'promise':
|
|
||||||
message += `🔍 错误类型: Promise错误\n`
|
|
||||||
message += `📝 错误信息: ${this._serializeError(errorInfo.reason)}\n`
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'console':
|
|
||||||
message += `🔍 错误类型: Console错误\n`
|
|
||||||
message += `📝 错误信息: ${errorInfo.args.join(' ')}\n`
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'miniProgram':
|
|
||||||
message += `🔍 错误类型: 小程序错误\n`
|
|
||||||
message += `📝 错误信息: ${errorInfo.error || 'Unknown'}\n`
|
|
||||||
if (errorInfo.path) {
|
|
||||||
message += `📱 页面路径: ${errorInfo.path}\n`
|
|
||||||
}
|
|
||||||
if (errorInfo.query) {
|
|
||||||
message += `🔗 查询参数: ${errorInfo.query}\n`
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'network':
|
|
||||||
message += `🔍 错误类型: 网络错误\n`
|
|
||||||
message += `📝 请求地址: ${errorInfo.url || 'Unknown'}\n`
|
|
||||||
message += `📝 请求方法: ${errorInfo.method || 'Unknown'}\n`
|
|
||||||
|
|
||||||
if (errorInfo.error) {
|
|
||||||
if (typeof errorInfo.error === 'object') {
|
|
||||||
message += `🔢 错误代码: ${errorInfo.error.code || errorInfo.error.errCode || 'Unknown'}\n`
|
|
||||||
message += `📝 错误信息: ${errorInfo.error.message || errorInfo.error.errMsg || this._serializeError(errorInfo.error)}\n`
|
|
||||||
|
|
||||||
if (errorInfo.error.errCode) {
|
|
||||||
message += `🆔 微信错误码: ${errorInfo.error.errCode}\n`
|
|
||||||
}
|
|
||||||
if (errorInfo.error.errMsg) {
|
|
||||||
message += `💬 微信错误信息: ${errorInfo.error.errMsg}\n`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message += `📝 错误信息: ${errorInfo.error}\n`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'api':
|
|
||||||
message += `🔍 错误类型: 接口错误\n`
|
|
||||||
message += `📝 请求地址: ${errorInfo.url || 'Unknown'}\n`
|
|
||||||
message += `📝 请求方法: ${errorInfo.method || 'Unknown'}\n`
|
|
||||||
|
|
||||||
if (errorInfo.requestData) {
|
|
||||||
message += `📋 请求参数: ${typeof errorInfo.requestData === 'object' ? JSON.stringify(errorInfo.requestData, null, 2) : errorInfo.requestData}\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorInfo.statusCode) {
|
|
||||||
message += `📊 状态码: ${errorInfo.statusCode}\n`
|
|
||||||
}
|
|
||||||
if (errorInfo.statusText) {
|
|
||||||
message += `📝 状态文本: ${errorInfo.statusText}\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorInfo.error) {
|
|
||||||
if (typeof errorInfo.error === 'object') {
|
|
||||||
if (errorInfo.error.code || errorInfo.error.status) {
|
|
||||||
message += `🔢 错误代码: ${errorInfo.error.code || errorInfo.error.status}\n`
|
|
||||||
}
|
|
||||||
if (errorInfo.error.message || errorInfo.error.msg) {
|
|
||||||
message += `📝 错误信息: ${errorInfo.error.message || errorInfo.error.msg}\n`
|
|
||||||
}
|
|
||||||
if (errorInfo.error.data) {
|
|
||||||
message += `📄 响应数据: ${this._serializeError(errorInfo.error.data)}\n`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message += `📝 错误信息: ${errorInfo.error}\n`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
message += `🔍 错误类型: ${errorInfo.type}\n`
|
|
||||||
message += `📝 错误信息: ${this._serializeError(errorInfo.error)}\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
message += `\n📊 统计信息:\n`
|
|
||||||
message += `总计错误: ${this.errorStats.total}\n`
|
|
||||||
message += `全局错误: ${this.errorStats.global}\n`
|
|
||||||
message += `Promise错误: ${this.errorStats.promise}\n`
|
|
||||||
message += `Console错误: ${this.errorStats.console}\n`
|
|
||||||
message += `小程序错误: ${this.errorStats.miniProgram}\n`
|
|
||||||
message += `接口错误: ${this.errorStats.api}\n`
|
|
||||||
message += `网络错误: ${this.errorStats.network}\n`
|
|
||||||
message += `手动错误: ${this.errorStats.manual}\n`
|
|
||||||
|
|
||||||
if (errorInfo.userAgent) {
|
|
||||||
message += `\n📱 设备信息:\n${errorInfo.userAgent}\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前URL
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_getCurrentUrl() {
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
return window.location?.href || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const pages = getCurrentPages()
|
|
||||||
if (pages && pages.length > 0) {
|
|
||||||
const currentPage = pages[pages.length - 1]
|
|
||||||
return currentPage.route || ''
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户代理信息
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_getUserAgent() {
|
|
||||||
if (typeof navigator !== 'undefined') {
|
|
||||||
return navigator.userAgent || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
|
||||||
return `${systemInfo.platform} ${systemInfo.system} ${systemInfo.model}`
|
|
||||||
} catch (error) {
|
|
||||||
return 'Unknown Device'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Unknown Device'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前页面名称
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_getCurrentPageName() {
|
|
||||||
try {
|
|
||||||
const pages = getCurrentPages()
|
|
||||||
if (pages && pages.length > 0) {
|
|
||||||
const currentPage = pages[pages.length - 1]
|
|
||||||
return currentPage.route || currentPage.$page?.fullPath || '未知页面'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误,返回默认值
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const currentPages = getCurrentPages?.()
|
|
||||||
if (currentPages && currentPages.length > 0) {
|
|
||||||
return currentPages[currentPages.length - 1]?.route || '未知页面'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return '未知页面'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (typeof window !== 'undefined' && window.location) {
|
|
||||||
return window.location.pathname || '未知页面'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return '未知页面'
|
|
||||||
}
|
|
||||||
|
|
||||||
return '未知页面'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取运行模式
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_getMode() {
|
|
||||||
try {
|
|
||||||
if (import.meta?.env?.MODE) {
|
|
||||||
return import.meta.env.MODE
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略访问错误
|
|
||||||
}
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测是否为生产环境
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_isProduction() {
|
|
||||||
// 检查uniapp运行模式
|
|
||||||
try {
|
|
||||||
const systemInfo = uni?.getSystemInfoSync?.()
|
|
||||||
if (systemInfo?.mode && systemInfo.mode !== 'default') {
|
|
||||||
// 体验版、开发版、预览版
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误,继续检测
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查环境变量MODE
|
|
||||||
const mode = this._getMode()
|
|
||||||
if (mode === 'development' || mode === 'sandbox') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认:开发环境和体验版不启用,生产环境启用
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化错误对象
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_serializeError(error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
return {
|
|
||||||
name: error.name || error.code,
|
|
||||||
message: error.message,
|
|
||||||
stack: error.stack,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof error === 'object' && error !== null) {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(error, null, 2)
|
|
||||||
} catch (e) {
|
|
||||||
return String(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出单例
|
|
||||||
export default new ErrorMonitor()
|
|
||||||
237
src/index.d.ts
vendored
Normal file
237
src/index.d.ts
vendored
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
/**
|
||||||
|
* UniApp 错误监控和上报工具类型定义
|
||||||
|
* @version 1.0.1
|
||||||
|
* @author yuantao
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module 'uniapp-error-monitor' {
|
||||||
|
/**
|
||||||
|
* 错误类型枚举
|
||||||
|
*/
|
||||||
|
export type ErrorType =
|
||||||
|
| 'global'
|
||||||
|
| 'promise'
|
||||||
|
| 'console'
|
||||||
|
| 'miniProgram'
|
||||||
|
| 'network'
|
||||||
|
| 'api'
|
||||||
|
| 'manual'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误统计信息接口
|
||||||
|
*/
|
||||||
|
export interface ErrorStats {
|
||||||
|
total: number
|
||||||
|
global: number
|
||||||
|
promise: number
|
||||||
|
console: number
|
||||||
|
miniProgram: number
|
||||||
|
api: number
|
||||||
|
network: number
|
||||||
|
lastErrorTime: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误监控配置选项
|
||||||
|
*/
|
||||||
|
export interface ErrorMonitorOptions {
|
||||||
|
/** 是否启用全局错误捕获 */
|
||||||
|
enableGlobalError?: boolean
|
||||||
|
/** 是否启用Promise错误捕获 */
|
||||||
|
enablePromiseError?: boolean
|
||||||
|
/** 是否启用console.error捕获 */
|
||||||
|
enableConsoleError?: boolean
|
||||||
|
/** 自定义webhook地址,不传则使用环境变量 */
|
||||||
|
webhookUrl?: string
|
||||||
|
/** 发送失败时最大重试次数 */
|
||||||
|
maxRetries?: number
|
||||||
|
/** 重试延迟时间(毫秒) */
|
||||||
|
retryDelay?: number
|
||||||
|
/** 强制启用错误监控(忽略环境检查) */
|
||||||
|
forceEnable?: boolean
|
||||||
|
/** 自定义发送器 */
|
||||||
|
sender?: (errorInfo: ErrorInfo) => Promise<void>
|
||||||
|
/** 自定义格式化函数 */
|
||||||
|
formatter?: (errorInfo: ErrorInfo) => string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误信息接口
|
||||||
|
*/
|
||||||
|
export interface ErrorInfo {
|
||||||
|
type: ErrorType
|
||||||
|
error: string | Error | object
|
||||||
|
stack?: string | null
|
||||||
|
context?: object
|
||||||
|
timestamp: number
|
||||||
|
url?: string
|
||||||
|
method?: string
|
||||||
|
userAgent?: string
|
||||||
|
page?: string
|
||||||
|
|
||||||
|
// API错误特有字段
|
||||||
|
statusCode?: number
|
||||||
|
statusText?: string
|
||||||
|
responseTime?: number
|
||||||
|
requestData?: any
|
||||||
|
requestHeaders?: any
|
||||||
|
requestId?: string
|
||||||
|
environment?: string
|
||||||
|
|
||||||
|
// 网络错误特有字段
|
||||||
|
retryCount?: number
|
||||||
|
networkType?: string
|
||||||
|
isConnected?: boolean
|
||||||
|
|
||||||
|
// Promise错误特有字段
|
||||||
|
reason?: any
|
||||||
|
promise?: any
|
||||||
|
|
||||||
|
// 全局错误特有字段
|
||||||
|
message?: string
|
||||||
|
source?: string
|
||||||
|
lineno?: number
|
||||||
|
colno?: number
|
||||||
|
|
||||||
|
// Console错误特有字段
|
||||||
|
args?: any[]
|
||||||
|
|
||||||
|
// 小程序错误特有字段
|
||||||
|
path?: string
|
||||||
|
query?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境信息接口
|
||||||
|
*/
|
||||||
|
export interface EnvironmentInfo {
|
||||||
|
isProduction: boolean
|
||||||
|
mode: string
|
||||||
|
platform: string
|
||||||
|
errorMonitorEnabled: boolean
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误监控类
|
||||||
|
*/
|
||||||
|
export class ErrorMonitor {
|
||||||
|
constructor(options?: ErrorMonitorOptions)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化全局错误监控
|
||||||
|
* @param options 配置选项
|
||||||
|
*/
|
||||||
|
initErrorMonitor(options?: ErrorMonitorOptions): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动上报错误
|
||||||
|
* @param type 错误类型
|
||||||
|
* @param error 错误对象或错误信息
|
||||||
|
* @param context 错误上下文信息
|
||||||
|
* @param forceSend 强制发送(忽略环境检查)
|
||||||
|
*/
|
||||||
|
reportError(
|
||||||
|
type?: ErrorType,
|
||||||
|
error?: Error | string | object,
|
||||||
|
context?: object,
|
||||||
|
forceSend?: boolean
|
||||||
|
): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取错误统计信息
|
||||||
|
* @returns 错误统计信息
|
||||||
|
*/
|
||||||
|
getErrorStats(): ErrorStats
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置错误统计
|
||||||
|
*/
|
||||||
|
resetErrorStats(): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前环境信息
|
||||||
|
* @returns 环境信息
|
||||||
|
*/
|
||||||
|
getEnvironmentInfo(): EnvironmentInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置自定义发送器
|
||||||
|
* @param sender 自定义发送器函数
|
||||||
|
*/
|
||||||
|
setSender(sender: (errorInfo: ErrorInfo) => Promise<void>): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置自定义格式化函数
|
||||||
|
* @param formatter 自定义格式化函数
|
||||||
|
*/
|
||||||
|
setFormatter(formatter: (errorInfo: ErrorInfo) => string): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 包装Promise以自动捕获错误
|
||||||
|
* @param promise 要包装的Promise
|
||||||
|
* @returns 包装后的Promise
|
||||||
|
*/
|
||||||
|
wrapPromise<T>(promise: Promise<T>): Promise<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 便捷方法 - 初始化错误监控
|
||||||
|
* @param options 配置选项
|
||||||
|
*/
|
||||||
|
export function initErrorMonitor(options?: ErrorMonitorOptions): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 便捷方法 - 手动上报错误
|
||||||
|
* @param type 错误类型
|
||||||
|
* @param error 错误对象或错误信息
|
||||||
|
* @param context 错误上下文信息
|
||||||
|
* @param forceSend 强制发送(忽略环境检查)
|
||||||
|
*/
|
||||||
|
export function reportError(
|
||||||
|
type?: ErrorType,
|
||||||
|
error?: Error | string | object,
|
||||||
|
context?: object,
|
||||||
|
forceSend?: boolean
|
||||||
|
): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 便捷方法 - 获取错误统计信息
|
||||||
|
* @returns 错误统计信息
|
||||||
|
*/
|
||||||
|
export function getErrorStats(): ErrorStats
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 便捷方法 - 重置错误统计
|
||||||
|
*/
|
||||||
|
export function resetErrorStats(): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 便捷方法 - 获取当前环境信息
|
||||||
|
* @returns 环境信息
|
||||||
|
*/
|
||||||
|
export function getEnvironmentInfo(): EnvironmentInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 便捷方法 - 包装Promise以自动捕获错误
|
||||||
|
* @param promise 要包装的Promise
|
||||||
|
* @returns 包装后的Promise
|
||||||
|
*/
|
||||||
|
export function wrapPromise<T>(promise: Promise<T>): Promise<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认实例 - 向后兼容
|
||||||
|
*/
|
||||||
|
const errorMonitor: ErrorMonitor
|
||||||
|
export default errorMonitor
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局类型声明(如果需要在全局使用)
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
UniAppErrorMonitor: typeof import('uniapp-error-monitor').default
|
||||||
|
}
|
||||||
|
|
||||||
|
const uni: any
|
||||||
|
const getCurrentPages: any
|
||||||
|
}
|
||||||
788
src/index.js
788
src/index.js
@@ -1,101 +1,729 @@
|
|||||||
/**
|
/**
|
||||||
* UniApp 错误监控上报插件 - 入口文件
|
* 错误监控和上报类
|
||||||
* 提供完整的 JavaScript 错误监控和上报解决方案
|
|
||||||
*/
|
*/
|
||||||
|
class ErrorMonitor {
|
||||||
|
constructor(options = {}) {
|
||||||
|
// 初始化错误统计
|
||||||
|
this.errorStats = {
|
||||||
|
total: 0,
|
||||||
|
global: 0,
|
||||||
|
promise: 0,
|
||||||
|
console: 0,
|
||||||
|
miniProgram: 0,
|
||||||
|
api: 0,
|
||||||
|
network: 0,
|
||||||
|
lastErrorTime: null,
|
||||||
|
}
|
||||||
|
|
||||||
import ErrorMonitor from './ErrorMonitor'
|
// Promise包装方法
|
||||||
import { getCurrentPageName, getCurrentUrl, getUserAgent, serializeError } from './utils'
|
this.wrapPromise = null
|
||||||
|
|
||||||
// 创建单例实例
|
// 配置信息
|
||||||
|
this.config = null
|
||||||
|
|
||||||
|
// 项目信息
|
||||||
|
this.projectInfo = {
|
||||||
|
name: '未命名项目',
|
||||||
|
version: '0.0.0',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试从 manifest.json 加载项目信息
|
||||||
|
this._loadProjectInfo()
|
||||||
|
|
||||||
|
// 应用初始配置
|
||||||
|
if (Object.keys(options).length > 0) {
|
||||||
|
this.initErrorMonitor(options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否为生产环境
|
||||||
|
* @private
|
||||||
|
* @returns {boolean} 是否为生产环境
|
||||||
|
*/
|
||||||
|
_isProduction() {
|
||||||
|
// 检查uniapp运行模式
|
||||||
|
try {
|
||||||
|
const systemInfo = uni.getSystemInfoSync?.()
|
||||||
|
if (systemInfo?.mode && systemInfo.mode !== 'default') {
|
||||||
|
// 体验版、开发版、预览版
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略错误,继续检测
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查环境变量MODE
|
||||||
|
if (import.meta.env.MODE === 'development') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认:开发环境和体验版不启用,生产环境启用
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化全局错误监控
|
||||||
|
* @param {Object} options 配置选项
|
||||||
|
* @param {boolean} [options.enableGlobalError=true] 是否启用全局错误捕获
|
||||||
|
* @param {boolean} [options.enablePromiseError=true] 是否启用Promise错误捕获
|
||||||
|
* @param {boolean} [options.enableConsoleError=true] 是否启用console.error捕获
|
||||||
|
* @param {string} [options.webhookUrl] 自定义webhook地址,不传则使用环境变量
|
||||||
|
* @param {number} [options.maxRetries=3] 发送失败时最大重试次数
|
||||||
|
* @param {number} [options.retryDelay=1000] 重试延迟时间(毫秒)
|
||||||
|
* @param {boolean} [options.forceEnable=false] 强制启用错误监控(忽略环境检查)
|
||||||
|
*/
|
||||||
|
initErrorMonitor(options = {}) {
|
||||||
|
const config = {
|
||||||
|
enableGlobalError: true,
|
||||||
|
enablePromiseError: true,
|
||||||
|
enableConsoleError: false,
|
||||||
|
webhookUrl: import.meta.env.VITE_WEBHOOK,
|
||||||
|
maxRetries: 3,
|
||||||
|
retryDelay: 1000,
|
||||||
|
forceEnable: false,
|
||||||
|
...options,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 环境检查:只在生产环境下启用错误监控
|
||||||
|
if (!config.forceEnable && !this._isProduction()) {
|
||||||
|
console.info('当前为非生产环境,错误监控已禁用')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查webhook配置
|
||||||
|
if (!config.webhookUrl) {
|
||||||
|
console.warn('错误监控初始化失败:未配置webhook地址')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config = config
|
||||||
|
|
||||||
|
// 全局错误捕获(uniapp环境适配)
|
||||||
|
if (config.enableGlobalError) {
|
||||||
|
// Web环境
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.onerror = (message, source, lineno, colno, error) => {
|
||||||
|
this._handleGlobalError({
|
||||||
|
type: 'global',
|
||||||
|
message,
|
||||||
|
source,
|
||||||
|
lineno,
|
||||||
|
colno,
|
||||||
|
error,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理未捕获的Promise错误
|
||||||
|
window.addEventListener('unhandledrejection', event => {
|
||||||
|
this._handlePromiseError({
|
||||||
|
type: 'promise',
|
||||||
|
reason: event.reason,
|
||||||
|
promise: event.promise,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// uniapp环境 - 提供Promise包装工具
|
||||||
|
if (typeof uni !== 'undefined' && config.enablePromiseError) {
|
||||||
|
// 提供一个包装Promise的方法,让开发者可以手动包装重要的Promise
|
||||||
|
this.wrapPromise = promise => {
|
||||||
|
const self = this
|
||||||
|
return promise.catch(error => {
|
||||||
|
self._handlePromiseError({
|
||||||
|
type: 'promise',
|
||||||
|
reason: error,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.error捕获(可选)
|
||||||
|
if (config.enableConsoleError) {
|
||||||
|
const originalError = console.error
|
||||||
|
console.error = (...args) => {
|
||||||
|
originalError.apply(console, args)
|
||||||
|
this._handleConsoleError({
|
||||||
|
type: 'console',
|
||||||
|
args: args.map(arg => this._serializeError(arg)),
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信小程序错误捕获
|
||||||
|
if (typeof uni !== 'undefined') {
|
||||||
|
// 监听小程序错误事件
|
||||||
|
uni.onError &&
|
||||||
|
uni.onError(error => {
|
||||||
|
this._handleMiniProgramError({
|
||||||
|
type: 'miniProgram',
|
||||||
|
error,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听小程序页面错误
|
||||||
|
uni.onPageNotFound &&
|
||||||
|
uni.onPageNotFound(result => {
|
||||||
|
this._handleMiniProgramError({
|
||||||
|
type: 'pageNotFound',
|
||||||
|
path: result.path,
|
||||||
|
query: result.query,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听小程序网络请求错误
|
||||||
|
const originalRequest = uni.request
|
||||||
|
uni.request = options => {
|
||||||
|
return originalRequest({
|
||||||
|
...options,
|
||||||
|
fail: err => {
|
||||||
|
options.fail && options.fail(err)
|
||||||
|
this._handleNetworkError({
|
||||||
|
type: 'network',
|
||||||
|
url: options.url,
|
||||||
|
method: options.method,
|
||||||
|
error: err,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('错误监控已初始化')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动上报错误
|
||||||
|
* @param {string} type 错误类型 ('manual', 'api', 'network', 'global', 'promise', 'console', 'miniProgram')
|
||||||
|
* @param {Error|Object} error 错误对象或错误信息
|
||||||
|
* @param {Object} [context] 错误上下文信息
|
||||||
|
* @param {boolean} [forceSend=false] 强制发送(忽略环境检查)
|
||||||
|
*/
|
||||||
|
reportError(type = 'manual', error, context = {}, forceSend = false) {
|
||||||
|
const errorInfo = {
|
||||||
|
type,
|
||||||
|
error: error instanceof Error ? error.message : error,
|
||||||
|
stack: error instanceof Error ? error.stack : null,
|
||||||
|
context,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
url: error.url || this._getCurrentUrl(),
|
||||||
|
method: error.method || '',
|
||||||
|
userAgent: this._getUserAgent(),
|
||||||
|
page: getCurrentPageName(),
|
||||||
|
|
||||||
|
// API错误特有字段
|
||||||
|
statusCode: error.statusCode,
|
||||||
|
statusText: error.statusText,
|
||||||
|
responseTime: error.responseTime,
|
||||||
|
requestData: error.requestData,
|
||||||
|
requestHeaders: error.requestHeaders,
|
||||||
|
requestId: error.requestId,
|
||||||
|
environment: error.environment,
|
||||||
|
|
||||||
|
// 网络错误特有字段
|
||||||
|
retryCount: error.retryCount,
|
||||||
|
networkType: error.networkType,
|
||||||
|
isConnected: error.isConnected,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.errorStats.total++
|
||||||
|
this.errorStats[type] = (this.errorStats[type] || 0) + 1
|
||||||
|
this.errorStats.lastErrorTime = errorInfo.timestamp
|
||||||
|
|
||||||
|
if (forceSend) {
|
||||||
|
// 强制发送
|
||||||
|
this._sendErrorToWebhook(errorInfo, 0, true)
|
||||||
|
} else {
|
||||||
|
this._sendErrorToWebhook(errorInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取错误统计信息
|
||||||
|
* @returns {Object} 错误统计信息
|
||||||
|
*/
|
||||||
|
getErrorStats() {
|
||||||
|
return { ...this.errorStats }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置错误统计
|
||||||
|
*/
|
||||||
|
resetErrorStats() {
|
||||||
|
this.errorStats = {
|
||||||
|
total: 0,
|
||||||
|
global: 0,
|
||||||
|
promise: 0,
|
||||||
|
console: 0,
|
||||||
|
miniProgram: 0,
|
||||||
|
api: 0,
|
||||||
|
network: 0,
|
||||||
|
lastErrorTime: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前环境信息
|
||||||
|
* @returns {Object} 环境信息
|
||||||
|
*/
|
||||||
|
getEnvironmentInfo() {
|
||||||
|
return {
|
||||||
|
isProduction: this._isProduction(),
|
||||||
|
mode: import.meta.env.MODE,
|
||||||
|
platform: this._getUserAgent(),
|
||||||
|
errorMonitorEnabled: !!this.config,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理全局错误
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleGlobalError(errorInfo) {
|
||||||
|
this.errorStats.total++
|
||||||
|
this.errorStats.global++
|
||||||
|
this.errorStats.lastErrorTime = errorInfo.timestamp
|
||||||
|
|
||||||
|
this._sendErrorToWebhook({
|
||||||
|
...errorInfo,
|
||||||
|
message: errorInfo.message || 'Unknown global error',
|
||||||
|
source: errorInfo.source || '',
|
||||||
|
lineno: errorInfo.lineno || 0,
|
||||||
|
colno: errorInfo.colno || 0,
|
||||||
|
url: this._getCurrentUrl(),
|
||||||
|
userAgent: this._getUserAgent(),
|
||||||
|
page: getCurrentPageName(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理Promise错误
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handlePromiseError(errorInfo) {
|
||||||
|
this.errorStats.total++
|
||||||
|
this.errorStats.promise++
|
||||||
|
this.errorStats.lastErrorTime = errorInfo.timestamp
|
||||||
|
|
||||||
|
this._sendErrorToWebhook({
|
||||||
|
...errorInfo,
|
||||||
|
reason: this._serializeError(errorInfo.reason),
|
||||||
|
url: this._getCurrentUrl(),
|
||||||
|
userAgent: this._getUserAgent(),
|
||||||
|
page: getCurrentPageName(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理console错误
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleConsoleError(errorInfo) {
|
||||||
|
this.errorStats.total++
|
||||||
|
this.errorStats.console++
|
||||||
|
this.errorStats.lastErrorTime = errorInfo.timestamp
|
||||||
|
|
||||||
|
this._sendErrorToWebhook({
|
||||||
|
...errorInfo,
|
||||||
|
url: this._getCurrentUrl(),
|
||||||
|
userAgent: this._getUserAgent(),
|
||||||
|
page: getCurrentPageName(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理小程序错误
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleMiniProgramError(errorInfo) {
|
||||||
|
this.errorStats.total++
|
||||||
|
this.errorStats.miniProgram++
|
||||||
|
this.errorStats.lastErrorTime = errorInfo.timestamp
|
||||||
|
|
||||||
|
this._sendErrorToWebhook({
|
||||||
|
...errorInfo,
|
||||||
|
url: this._getCurrentUrl(),
|
||||||
|
userAgent: this._getUserAgent(),
|
||||||
|
page: getCurrentPageName(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理网络错误
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleNetworkError(errorInfo) {
|
||||||
|
this.errorStats.total++
|
||||||
|
this.errorStats.network++
|
||||||
|
this.errorStats.lastErrorTime = errorInfo.timestamp
|
||||||
|
|
||||||
|
this._sendErrorToWebhook({
|
||||||
|
...errorInfo,
|
||||||
|
url: this._getCurrentUrl(),
|
||||||
|
userAgent: this._getUserAgent(),
|
||||||
|
page: getCurrentPageName(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前URL
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getCurrentUrl() {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return window.location?.href || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof uni !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
if (pages && pages.length > 0) {
|
||||||
|
const currentPage = pages[pages.length - 1]
|
||||||
|
return currentPage.route || ''
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户代理信息
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getUserAgent() {
|
||||||
|
if (typeof navigator !== 'undefined') {
|
||||||
|
return navigator.userAgent || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof uni !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
return `${systemInfo.platform} ${systemInfo.system} ${systemInfo.model}`
|
||||||
|
} catch (error) {
|
||||||
|
return 'Unknown Device'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Unknown Device'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化错误对象
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_serializeError(error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return {
|
||||||
|
name: error.name || error.code,
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof error === 'object' && error !== null) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(error, null, 2)
|
||||||
|
} catch (e) {
|
||||||
|
return String(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送错误到webhook
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _sendErrorToWebhook(errorInfo, retryCount = 0, forceSend = false) {
|
||||||
|
// 环境检查:只在生产环境下发送错误信息
|
||||||
|
if (!forceSend && !this._isProduction() && !this.config?.forceEnable) {
|
||||||
|
console.info('非生产环境,错误信息不上报到webhook:', errorInfo.type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhookUrl = import.meta.env.VITE_WEBHOOK
|
||||||
|
if (!webhookUrl) {
|
||||||
|
console.error('未配置webhook地址,无法发送错误信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 格式化错误信息
|
||||||
|
const message = this._formatErrorMessage(errorInfo)
|
||||||
|
// 使用uni.request发送POST请求(适配uniapp环境)
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
uni.request({
|
||||||
|
url: webhookUrl,
|
||||||
|
method: 'POST',
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
msgtype: 'text',
|
||||||
|
text: {
|
||||||
|
content: message,
|
||||||
|
mentioned_list: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
success: resolve,
|
||||||
|
fail: reject,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('错误信息已发送到webhook')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发送错误到webhook失败:', error)
|
||||||
|
|
||||||
|
// 重试机制
|
||||||
|
if (retryCount < (this.config?.maxRetries || 3)) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this._sendErrorToWebhook(errorInfo, retryCount + 1)
|
||||||
|
}, (this.config?.retryDelay || 1000) * (retryCount + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载项目信息
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_loadProjectInfo() {
|
||||||
|
try {
|
||||||
|
if (uni.getSystemInfoSync()) {
|
||||||
|
const AppInfo = uni.getSystemInfoSync()
|
||||||
|
this.projectInfo.name = AppInfo.appName
|
||||||
|
this.projectInfo.version = AppInfo.appVersion
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 如果加载失败,使用默认信息
|
||||||
|
console.warn('无法加载项目信息,使用默认值')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化错误消息
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_formatErrorMessage(errorInfo) {
|
||||||
|
const timestamp = new Date(errorInfo.timestamp).toLocaleString('zh-CN')
|
||||||
|
|
||||||
|
let message = `🚨 JavaScript错误报告\n`
|
||||||
|
message += `📦 项目: ${this.projectInfo.name}\n`
|
||||||
|
message += `🏷️ 版本: ${this.projectInfo.version}\n`
|
||||||
|
message += `⏰ 时间: ${timestamp}\n`
|
||||||
|
message += `📱 页面: ${errorInfo.page || '未知页面'}\n`
|
||||||
|
message += `🌐 链接: ${errorInfo.url || '未知链接'}\n\n`
|
||||||
|
|
||||||
|
switch (errorInfo.type) {
|
||||||
|
case 'global':
|
||||||
|
message += `🔍 错误类型: 全局错误\n`
|
||||||
|
message += `📝 错误信息: ${errorInfo.message}\n`
|
||||||
|
if (errorInfo.source) {
|
||||||
|
message += `📂 文件: ${errorInfo.source}\n`
|
||||||
|
}
|
||||||
|
if (errorInfo.lineno) {
|
||||||
|
message += `📍 行号: ${errorInfo.lineno}:${errorInfo.colno}\n`
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'promise':
|
||||||
|
message += `🔍 错误类型: Promise错误\n`
|
||||||
|
message += `📝 错误信息: ${this._serializeError(errorInfo.reason)}\n`
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'console':
|
||||||
|
message += `🔍 错误类型: Console错误\n`
|
||||||
|
message += `📝 错误信息: ${errorInfo.args.join(' ')}\n`
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'miniProgram':
|
||||||
|
message += `🔍 错误类型: 小程序错误\n`
|
||||||
|
message += `📝 错误信息: ${errorInfo.error || 'Unknown'}\n`
|
||||||
|
if (errorInfo.path) {
|
||||||
|
message += `📱 页面路径: ${errorInfo.path}\n`
|
||||||
|
}
|
||||||
|
if (errorInfo.query) {
|
||||||
|
message += `🔗 查询参数: ${errorInfo.query}\n`
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'network':
|
||||||
|
message += `🔍 错误类型: 网络错误\n`
|
||||||
|
message += `📝 请求地址: ${errorInfo.url || 'Unknown'}\n`
|
||||||
|
message += `📝 请求方法: ${errorInfo.method || 'Unknown'}\n`
|
||||||
|
|
||||||
|
// 网络错误详细信息
|
||||||
|
if (errorInfo.error) {
|
||||||
|
if (typeof errorInfo.error === 'object') {
|
||||||
|
// 处理网络错误对象
|
||||||
|
message += `🔢 错误代码: ${errorInfo.error.code || errorInfo.error.errCode || 'Unknown'}\n`
|
||||||
|
message += `📝 错误信息: ${errorInfo.error.message || errorInfo.error.errMsg || this._serializeError(errorInfo.error)}\n`
|
||||||
|
|
||||||
|
// 网络错误特定信息
|
||||||
|
if (errorInfo.error.errCode) {
|
||||||
|
message += `🆔 微信错误码: ${errorInfo.error.errCode}\n`
|
||||||
|
}
|
||||||
|
if (errorInfo.error.errMsg) {
|
||||||
|
message += `💬 微信错误信息: ${errorInfo.error.errMsg}\n`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message += `📝 错误信息: ${errorInfo.error}\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'api':
|
||||||
|
message += `🔍 错误类型: 接口错误\n`
|
||||||
|
message += `📝 请求地址: ${errorInfo.url || 'Unknown'}\n`
|
||||||
|
message += `📝 请求方法: ${errorInfo.method || 'Unknown'}\n`
|
||||||
|
|
||||||
|
// 请求信息
|
||||||
|
if (errorInfo.requestData) {
|
||||||
|
message += `📋 请求参数: ${typeof errorInfo.requestData === 'object' ? JSON.stringify(errorInfo.requestData, null, 2) : errorInfo.requestData}\n`
|
||||||
|
}
|
||||||
|
if (errorInfo.requestHeaders) {
|
||||||
|
message += `🔑 请求头: ${this._serializeError(errorInfo.requestHeaders)}\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应信息
|
||||||
|
if (errorInfo.statusCode) {
|
||||||
|
message += `📊 状态码: ${errorInfo.statusCode}\n`
|
||||||
|
}
|
||||||
|
if (errorInfo.statusText) {
|
||||||
|
message += `📝 状态文本: ${errorInfo.statusText}\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误详情
|
||||||
|
if (errorInfo.error) {
|
||||||
|
if (typeof errorInfo.error === 'object') {
|
||||||
|
// 处理标准的错误响应格式
|
||||||
|
if (errorInfo.error.code || errorInfo.error.status) {
|
||||||
|
message += `🔢 错误代码: ${errorInfo.error.code || errorInfo.error.status}\n`
|
||||||
|
}
|
||||||
|
if (errorInfo.error.message || errorInfo.error.msg) {
|
||||||
|
message += `📝 错误信息: ${errorInfo.error.message || errorInfo.error.msg}\n`
|
||||||
|
}
|
||||||
|
if (errorInfo.error.data) {
|
||||||
|
message += `📄 响应数据: ${this._serializeError(errorInfo.error.data)}\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是标准错误对象格式
|
||||||
|
if (errorInfo.error.name || errorInfo.error.code) {
|
||||||
|
message += `🏷️ 错误名称: ${errorInfo.error.name || errorInfo.error.code}\n`
|
||||||
|
}
|
||||||
|
if (errorInfo.error.stack) {
|
||||||
|
message += `📜 错误堆栈: ${errorInfo.error.stack}\n`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message += `📝 错误信息: ${errorInfo.error}\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
message += `🔍 错误类型: ${errorInfo.type}\n`
|
||||||
|
message += `📝 错误信息: ${this._serializeError(errorInfo.error)}\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `\n📊 统计信息:\n`
|
||||||
|
message += `总计错误: ${this.errorStats.total}\n`
|
||||||
|
message += `全局错误: ${this.errorStats.global}\n`
|
||||||
|
message += `Promise错误: ${this.errorStats.promise}\n`
|
||||||
|
message += `Console错误: ${this.errorStats.console}\n`
|
||||||
|
message += `小程序错误: ${this.errorStats.miniProgram}\n`
|
||||||
|
message += `接口错误: ${this.errorStats.api}\n`
|
||||||
|
message += `网络错误: ${this.errorStats.network}\n`
|
||||||
|
|
||||||
|
// 添加设备信息
|
||||||
|
if (errorInfo.userAgent) {
|
||||||
|
message += `\n📱 设备信息:\n${errorInfo.userAgent}\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前页面名称
|
||||||
|
* @returns {string} 页面名称
|
||||||
|
*/
|
||||||
|
function getCurrentPageName() {
|
||||||
|
try {
|
||||||
|
// 尝试从getCurrentPages获取
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
if (pages && pages.length > 0) {
|
||||||
|
const currentPage = pages[pages.length - 1]
|
||||||
|
return currentPage.route || currentPage.$page?.fullPath || '未知页面'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略错误,返回默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信小程序环境
|
||||||
|
if (typeof uni !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const currentPages = getCurrentPages?.()
|
||||||
|
if (currentPages && currentPages.length > 0) {
|
||||||
|
return currentPages[currentPages.length - 1]?.route || '未知页面'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return '未知页面'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web环境
|
||||||
|
try {
|
||||||
|
if (typeof window !== 'undefined' && window.location) {
|
||||||
|
return window.location.pathname || '未知页面'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return '未知页面'
|
||||||
|
}
|
||||||
|
|
||||||
|
return '未知页面'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建默认实例
|
||||||
const errorMonitorInstance = new ErrorMonitor()
|
const errorMonitorInstance = new ErrorMonitor()
|
||||||
|
|
||||||
/**
|
// 命名导出 - 便捷方法
|
||||||
* 初始化错误监控
|
export const initErrorMonitor = (options) => {
|
||||||
* @param {Object} options 配置选项
|
return errorMonitorInstance.initErrorMonitor(options)
|
||||||
* @returns {ErrorMonitor} 错误监控实例
|
|
||||||
*/
|
|
||||||
export function initErrorMonitor(options = {}) {
|
|
||||||
return errorMonitorInstance.init(options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const reportError = (type, error, context, forceSend) => {
|
||||||
* 手动上报错误
|
return errorMonitorInstance.reportError(type, error, context, forceSend)
|
||||||
* @param {string} type 错误类型
|
|
||||||
* @param {Error|Object} error 错误对象或错误信息
|
|
||||||
* @param {Object} context 错误上下文信息
|
|
||||||
* @param {boolean} forceSend 强制发送(忽略环境检查)
|
|
||||||
*/
|
|
||||||
export function reportError(type = 'manual', error, context = {}, forceSend = false) {
|
|
||||||
errorMonitorInstance.reportError(type, error, context, forceSend)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const getErrorStats = () => {
|
||||||
* Promise 包装工具
|
|
||||||
* 自动捕获 Promise 错误
|
|
||||||
* @param {Promise} promise 要包装的 Promise
|
|
||||||
* @returns {Promise} 包装后的 Promise
|
|
||||||
*/
|
|
||||||
export function wrapPromise(promise) {
|
|
||||||
return errorMonitorInstance.wrapPromise(promise)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取错误统计信息
|
|
||||||
* @returns {Object} 错误统计信息
|
|
||||||
*/
|
|
||||||
export function getErrorStats() {
|
|
||||||
return errorMonitorInstance.getErrorStats()
|
return errorMonitorInstance.getErrorStats()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const resetErrorStats = () => {
|
||||||
* 重置错误统计
|
return errorMonitorInstance.resetErrorStats()
|
||||||
*/
|
|
||||||
export function resetErrorStats() {
|
|
||||||
errorMonitorInstance.resetErrorStats()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const getEnvironmentInfo = () => {
|
||||||
* 获取环境信息
|
|
||||||
* @returns {Object} 环境信息
|
|
||||||
*/
|
|
||||||
export function getEnvironmentInfo() {
|
|
||||||
return errorMonitorInstance.getEnvironmentInfo()
|
return errorMonitorInstance.getEnvironmentInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const wrapPromise = (promise) => {
|
||||||
* 检查是否为生产环境
|
return errorMonitorInstance.wrapPromise ? errorMonitorInstance.wrapPromise(promise) : promise
|
||||||
* @returns {boolean} 是否为生产环境
|
|
||||||
*/
|
|
||||||
export function isProduction() {
|
|
||||||
return errorMonitorInstance.isProduction()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 默认导出 - 向后兼容
|
||||||
* 设置自定义发送器
|
|
||||||
* @param {Function} sender 自定义发送函数
|
|
||||||
*/
|
|
||||||
export function setCustomSender(sender) {
|
|
||||||
errorMonitorInstance.setSender(sender)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置自定义格式化函数
|
|
||||||
* @param {Function} formatter 自定义格式化函数
|
|
||||||
*/
|
|
||||||
export function setCustomFormatter(formatter) {
|
|
||||||
errorMonitorInstance.setFormatter(formatter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出 ErrorMonitor 类本身
|
|
||||||
export { ErrorMonitor }
|
|
||||||
|
|
||||||
// 导出工具函数
|
|
||||||
export {
|
|
||||||
getCurrentPageName,
|
|
||||||
getCurrentUrl,
|
|
||||||
getUserAgent,
|
|
||||||
serializeError
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出默认实例(方便直接使用)
|
|
||||||
export default errorMonitorInstance
|
export default errorMonitorInstance
|
||||||
215
src/types.ts
215
src/types.ts
@@ -1,215 +0,0 @@
|
|||||||
/**
|
|
||||||
* TypeScript 类型定义
|
|
||||||
* 为插件提供完整的类型安全保障
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误监控配置选项
|
|
||||||
*/
|
|
||||||
export interface ErrorMonitorOptions {
|
|
||||||
/** Webhook 地址(必填) */
|
|
||||||
webhookUrl: string
|
|
||||||
/** 是否启用全局错误捕获(默认:true) */
|
|
||||||
enableGlobalError?: boolean
|
|
||||||
/** 是否启用 Promise 错误捕获(默认:true) */
|
|
||||||
enablePromiseError?: boolean
|
|
||||||
/** 是否启用 console.error 捕获(默认:false) */
|
|
||||||
enableConsoleError?: boolean
|
|
||||||
/** 最大重试次数(默认:3) */
|
|
||||||
maxRetries?: number
|
|
||||||
/** 重试延迟时间(毫秒)(默认:1000) */
|
|
||||||
retryDelay?: number
|
|
||||||
/** 强制启用错误监控(忽略环境检查)(默认:false) */
|
|
||||||
forceEnable?: boolean
|
|
||||||
/** 自定义格式化函数 */
|
|
||||||
formatter?: (error: ErrorInfo) => string
|
|
||||||
/** 自定义发送器 */
|
|
||||||
sender?: (payload: ErrorInfo) => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误类型枚举
|
|
||||||
*/
|
|
||||||
export type ErrorType =
|
|
||||||
| 'global' // 全局 JavaScript 错误
|
|
||||||
| 'promise' // Promise 错误
|
|
||||||
| 'console' // console.error
|
|
||||||
| 'miniProgram' // 小程序特定错误
|
|
||||||
| 'network' // 网络请求错误
|
|
||||||
| 'api' // API 接口错误
|
|
||||||
| 'manual' // 手动上报错误
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误统计信息
|
|
||||||
*/
|
|
||||||
export interface ErrorStats {
|
|
||||||
/** 总错误数 */
|
|
||||||
total: number
|
|
||||||
/** 全局错误数 */
|
|
||||||
global: number
|
|
||||||
/** Promise 错误数 */
|
|
||||||
promise: number
|
|
||||||
/** Console 错误数 */
|
|
||||||
console: number
|
|
||||||
/** 小程序错误数 */
|
|
||||||
miniProgram: number
|
|
||||||
/** API 错误数 */
|
|
||||||
api: number
|
|
||||||
/** 网络错误数 */
|
|
||||||
network: number
|
|
||||||
/** 最后错误时间 */
|
|
||||||
lastErrorTime: number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误信息接口
|
|
||||||
*/
|
|
||||||
export interface ErrorInfo {
|
|
||||||
/** 错误类型 */
|
|
||||||
type: ErrorType
|
|
||||||
/** 错误消息 */
|
|
||||||
error: string
|
|
||||||
/** 错误堆栈 */
|
|
||||||
stack: string | null
|
|
||||||
/** 错误上下文 */
|
|
||||||
context: Record<string, any>
|
|
||||||
/** 错误时间戳 */
|
|
||||||
timestamp: number
|
|
||||||
/** 错误发生的 URL */
|
|
||||||
url: string
|
|
||||||
/** HTTP 方法(API 错误时) */
|
|
||||||
method?: string
|
|
||||||
/** 用户代理 */
|
|
||||||
userAgent: string
|
|
||||||
/** 页面名称 */
|
|
||||||
page: string
|
|
||||||
|
|
||||||
// 全局错误特有字段
|
|
||||||
/** 错误源文件 */
|
|
||||||
source?: string
|
|
||||||
/** 行号 */
|
|
||||||
lineno?: number
|
|
||||||
/** 列号 */
|
|
||||||
colno?: number
|
|
||||||
|
|
||||||
// Promise 错误特有字段
|
|
||||||
/** Promise 拒绝原因 */
|
|
||||||
reason?: any
|
|
||||||
/** Promise 对象 */
|
|
||||||
promise?: any
|
|
||||||
|
|
||||||
// Console 错误特有字段
|
|
||||||
/** Console 参数 */
|
|
||||||
args?: any[]
|
|
||||||
|
|
||||||
// 小程序错误特有字段
|
|
||||||
/** 错误对象 */
|
|
||||||
errorObject?: any
|
|
||||||
/** 页面路径(pageNotFound 时) */
|
|
||||||
path?: string
|
|
||||||
/** 查询参数(pageNotFound 时) */
|
|
||||||
query?: string
|
|
||||||
|
|
||||||
// 网络错误特有字段
|
|
||||||
/** 网络错误对象 */
|
|
||||||
networkError?: any
|
|
||||||
/** 网络类型 */
|
|
||||||
networkType?: string
|
|
||||||
/** 连接状态 */
|
|
||||||
isConnected?: boolean
|
|
||||||
|
|
||||||
// API 错误特有字段
|
|
||||||
/** HTTP 状态码 */
|
|
||||||
statusCode?: number
|
|
||||||
/** HTTP 状态文本 */
|
|
||||||
statusText?: string
|
|
||||||
/** 响应时间(毫秒) */
|
|
||||||
responseTime?: number
|
|
||||||
/** 请求数据 */
|
|
||||||
requestData?: any
|
|
||||||
/** 请求头 */
|
|
||||||
requestHeaders?: Record<string, string>
|
|
||||||
/** 请求 ID */
|
|
||||||
requestId?: string
|
|
||||||
/** 环境信息 */
|
|
||||||
environment?: string
|
|
||||||
/** 重试次数 */
|
|
||||||
retryCount?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 环境信息接口
|
|
||||||
*/
|
|
||||||
export interface EnvironmentInfo {
|
|
||||||
/** 是否为生产环境 */
|
|
||||||
isProduction: boolean
|
|
||||||
/** 运行环境模式 */
|
|
||||||
mode: string
|
|
||||||
/** 平台信息 */
|
|
||||||
platform: string
|
|
||||||
/** 错误监控是否启用 */
|
|
||||||
errorMonitorEnabled: boolean
|
|
||||||
/** 时间戳 */
|
|
||||||
timestamp: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化函数类型
|
|
||||||
*/
|
|
||||||
export type FormatterFunction = (error: ErrorInfo) => string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送器函数类型
|
|
||||||
*/
|
|
||||||
export type SenderFunction = (payload: ErrorInfo) => Promise<void>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工具函数接口
|
|
||||||
*/
|
|
||||||
export interface Utils {
|
|
||||||
/** 获取当前页面名称 */
|
|
||||||
getCurrentPageName: () => string
|
|
||||||
/** 获取当前 URL */
|
|
||||||
getCurrentUrl: () => string
|
|
||||||
/** 获取用户代理 */
|
|
||||||
getUserAgent: () => string
|
|
||||||
/** 序列化错误对象 */
|
|
||||||
serializeError: (error: any) => any
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造函数选项(用于类实例化)
|
|
||||||
*/
|
|
||||||
export interface ConstructorOptions extends ErrorMonitorOptions {
|
|
||||||
/** 配置选项 */
|
|
||||||
config?: Partial<ErrorMonitorOptions>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Webhook 消息格式
|
|
||||||
*/
|
|
||||||
export interface WebhookMessage {
|
|
||||||
msgtype: 'text'
|
|
||||||
text: {
|
|
||||||
content: string
|
|
||||||
mentioned_list?: string[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UniApp 环境信息
|
|
||||||
*/
|
|
||||||
export interface UniAppSystemInfo {
|
|
||||||
/** 应用名称 */
|
|
||||||
appName?: string
|
|
||||||
/** 应用版本 */
|
|
||||||
appVersion?: string
|
|
||||||
/** 平台信息 */
|
|
||||||
platform?: string
|
|
||||||
/** 系统信息 */
|
|
||||||
system?: string
|
|
||||||
/** 设备型号 */
|
|
||||||
model?: string
|
|
||||||
/** 小程序模式 */
|
|
||||||
mode?: string
|
|
||||||
}
|
|
||||||
343
src/utils.js
343
src/utils.js
@@ -1,343 +0,0 @@
|
|||||||
/**
|
|
||||||
* 工具函数
|
|
||||||
* 提供环境检测、错误序列化等通用功能
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前页面名称
|
|
||||||
* @returns {string} 页面名称
|
|
||||||
*/
|
|
||||||
export function getCurrentPageName() {
|
|
||||||
try {
|
|
||||||
// 尝试从 getCurrentPages 获取(小程序环境)
|
|
||||||
if (typeof getCurrentPages !== 'undefined') {
|
|
||||||
const pages = getCurrentPages()
|
|
||||||
if (pages && pages.length > 0) {
|
|
||||||
const currentPage = pages[pages.length - 1]
|
|
||||||
return currentPage.route || currentPage.$page?.fullPath || '未知页面'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误,返回默认值
|
|
||||||
}
|
|
||||||
|
|
||||||
// Web 环境
|
|
||||||
try {
|
|
||||||
if (typeof window !== 'undefined' && window.location) {
|
|
||||||
return window.location.pathname || '未知页面'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return '未知页面'
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniApp 环境
|
|
||||||
try {
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
const pages = getCurrentPages?.()
|
|
||||||
if (pages && pages.length > 0) {
|
|
||||||
return pages[pages.length - 1]?.route || '未知页面'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return '未知页面'
|
|
||||||
}
|
|
||||||
|
|
||||||
return '未知页面'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前 URL
|
|
||||||
* @returns {string} 当前 URL
|
|
||||||
*/
|
|
||||||
export function getCurrentUrl() {
|
|
||||||
// Web 环境
|
|
||||||
if (typeof window !== 'undefined' && window.location?.href) {
|
|
||||||
return window.location.href
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniApp 小程序环境
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const pages = getCurrentPages?.()
|
|
||||||
if (pages && pages.length > 0) {
|
|
||||||
const currentPage = pages[pages.length - 1]
|
|
||||||
return currentPage?.route || ''
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户代理信息
|
|
||||||
* @returns {string} 用户代理信息
|
|
||||||
*/
|
|
||||||
export function getUserAgent() {
|
|
||||||
// Web 环境
|
|
||||||
if (typeof navigator !== 'undefined' && navigator.userAgent) {
|
|
||||||
return navigator.userAgent
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniApp 环境
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const systemInfo = uni.getSystemInfoSync?.()
|
|
||||||
if (systemInfo) {
|
|
||||||
return `${systemInfo.platform || 'Unknown'} ${systemInfo.system || 'Unknown'} ${systemInfo.model || 'Unknown'}`
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Unknown Device'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化错误对象
|
|
||||||
* @param {any} error 要序列化的错误对象
|
|
||||||
* @returns {any} 序列化后的错误对象
|
|
||||||
*/
|
|
||||||
export function serializeError(error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
return {
|
|
||||||
name: error.name || error.code,
|
|
||||||
message: error.message,
|
|
||||||
stack: error.stack,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof error === 'object' && error !== null) {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(error, null, 2)
|
|
||||||
} catch (e) {
|
|
||||||
return String(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return String(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测是否为生产环境
|
|
||||||
* @returns {boolean} 是否为生产环境
|
|
||||||
*/
|
|
||||||
export function isProduction() {
|
|
||||||
try {
|
|
||||||
// 检查 uniapp 运行模式
|
|
||||||
if (typeof uni !== 'undefined') {
|
|
||||||
const systemInfo = uni.getSystemInfoSync?.()
|
|
||||||
if (systemInfo?.mode && systemInfo.mode !== 'default') {
|
|
||||||
// 体验版、开发版、预览版 - 认为是非生产环境
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误,继续检测
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查环境变量 MODE
|
|
||||||
try {
|
|
||||||
// 使用 process.env 检测环境变量
|
|
||||||
if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'development') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误,继续检测
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否为浏览器环境
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
try {
|
|
||||||
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认:开发环境和体验版不启用,生产环境启用
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测是否为 UniApp 环境
|
|
||||||
* @returns {boolean} 是否为 UniApp 环境
|
|
||||||
*/
|
|
||||||
export function isUniAppEnvironment() {
|
|
||||||
return typeof uni !== 'undefined' && typeof getCurrentPages !== 'undefined'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测是否为微信小程序环境
|
|
||||||
* @returns {boolean} 是否为微信小程序环境
|
|
||||||
*/
|
|
||||||
export function isWeChatMiniProgram() {
|
|
||||||
if (typeof uni !== 'undefined' && uni.getSystemInfoSync) {
|
|
||||||
try {
|
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
|
||||||
return systemInfo.hostName === 'wechat' || systemInfo.platform === 'devtools'
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 延迟执行函数
|
|
||||||
* @param {number} ms 延迟时间(毫秒)
|
|
||||||
* @returns {Promise<void>} Promise 对象
|
|
||||||
*/
|
|
||||||
export function delay(ms) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重试函数
|
|
||||||
* @param {Function} fn 要执行的函数
|
|
||||||
* @param {number} maxRetries 最大重试次数
|
|
||||||
* @param {number} delayTime 重试延迟时间(毫秒)
|
|
||||||
* @returns {Promise} 执行结果
|
|
||||||
*/
|
|
||||||
export async function retry(fn, maxRetries = 3, delayTime = 1000) {
|
|
||||||
let lastError
|
|
||||||
|
|
||||||
for (let i = 0; i <= maxRetries; i++) {
|
|
||||||
try {
|
|
||||||
return await fn()
|
|
||||||
} catch (error) {
|
|
||||||
lastError = error
|
|
||||||
if (i < maxRetries) {
|
|
||||||
await delay(delayTime * (i + 1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw lastError
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统信息
|
|
||||||
* @returns {Object} 系统信息对象
|
|
||||||
*/
|
|
||||||
export function getSystemInfo() {
|
|
||||||
const systemInfo = {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (typeof uni !== 'undefined' && uni.getSystemInfoSync) {
|
|
||||||
Object.assign(systemInfo, uni.getSystemInfoSync())
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (typeof navigator !== 'undefined') {
|
|
||||||
systemInfo.userAgent = navigator.userAgent
|
|
||||||
systemInfo.language = navigator.language
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (typeof window !== 'undefined' && window.location) {
|
|
||||||
systemInfo.url = window.location.href
|
|
||||||
systemInfo.hostname = window.location.hostname
|
|
||||||
systemInfo.pathname = window.location.pathname
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
|
|
||||||
return systemInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证 webhook URL 格式
|
|
||||||
* @param {string} url 要验证的 URL
|
|
||||||
* @returns {boolean} URL 是否有效
|
|
||||||
*/
|
|
||||||
export function isValidWebhookUrl(url) {
|
|
||||||
if (!url || typeof url !== 'string') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const urlObj = new URL(url)
|
|
||||||
return ['http:', 'https:'].includes(urlObj.protocol)
|
|
||||||
} catch (error) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 深度克隆对象
|
|
||||||
* @param {any} obj 要克隆的对象
|
|
||||||
* @returns {any} 克隆后的对象
|
|
||||||
*/
|
|
||||||
export function deepClone(obj) {
|
|
||||||
if (obj === null || typeof obj !== 'object') {
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj instanceof Date) {
|
|
||||||
return new Date(obj.getTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj instanceof Array) {
|
|
||||||
return obj.map(item => deepClone(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof obj === 'object') {
|
|
||||||
const clonedObj = {}
|
|
||||||
for (const key in obj) {
|
|
||||||
if (obj.hasOwnProperty(key)) {
|
|
||||||
clonedObj[key] = deepClone(obj[key])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clonedObj
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 防抖函数
|
|
||||||
* @param {Function} func 要防抖的函数
|
|
||||||
* @param {number} wait 等待时间(毫秒)
|
|
||||||
* @returns {Function} 防抖后的函数
|
|
||||||
*/
|
|
||||||
export function debounce(func, wait) {
|
|
||||||
let timeout
|
|
||||||
return function executedFunction(...args) {
|
|
||||||
const later = () => {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
func(...args)
|
|
||||||
}
|
|
||||||
clearTimeout(timeout)
|
|
||||||
timeout = setTimeout(later, wait)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 节流函数
|
|
||||||
* @param {Function} func 要节流的函数
|
|
||||||
* @param {number} limit 限制时间(毫秒)
|
|
||||||
* @returns {Function} 节流后的函数
|
|
||||||
*/
|
|
||||||
export function throttle(func, limit) {
|
|
||||||
let inThrottle
|
|
||||||
return function(...args) {
|
|
||||||
if (!inThrottle) {
|
|
||||||
func.apply(this, args)
|
|
||||||
inThrottle = true
|
|
||||||
setTimeout(() => inThrottle = false, limit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user