feat: 添加 Vue3 BugCollect 崩溃收集模块
新增模块:src/bugcollect/ - bugcollect.ts: 主入口,错误捕获 API - crash-capture.ts: Web 端崩溃捕获(error + unhandledrejection) - types.ts: 数据模型 - fingerprint.ts: 错误指纹计算(SHA-256) - log-queue.ts: 内存队列 + 定时上传(localStorage 持久化) - funnel-tracker.ts: 漏斗追踪 功能: - captureError/captureCrash 捕获错误 - addBreadcrumb 添加面包屑 - event 记录自定义事件 - warn/info 日志级别记录 - 自动初始化(window error 事件捕获) Co-Authored-By: Claude <noreply@anthropic.com>
这个提交包含在:
父节点
92bf6ff794
当前提交
f60fdcdd6f
244
src/bugcollect/bugcollect.ts
普通文件
244
src/bugcollect/bugcollect.ts
普通文件
@ -0,0 +1,244 @@
|
|||||||
|
/**
|
||||||
|
* BugCollect 崩溃收集 SDK
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { BugCollectOptions, BugCollectAPI, IssueEvent, LogEvent, Breadcrumb, DeviceInfo, LogLevel } from './types'
|
||||||
|
import { computeFingerprint } from './fingerprint'
|
||||||
|
import { startCrashCapture, stopCrashCapture } from './crash-capture'
|
||||||
|
import { LogQueue } from './log-queue'
|
||||||
|
import { trackFunnelEvent } from './funnel-tracker'
|
||||||
|
|
||||||
|
const SDK_NAME = 'bugcollect.vue3'
|
||||||
|
const SDK_VERSION = '1.0.0'
|
||||||
|
const MAX_BREADCRUMBS = 50
|
||||||
|
|
||||||
|
const LOG_LEVELS: Record<LogLevel, number> = {
|
||||||
|
debug: 0,
|
||||||
|
info: 1,
|
||||||
|
warn: 2,
|
||||||
|
error: 3,
|
||||||
|
none: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
let _options: BugCollectOptions | null = null
|
||||||
|
let _queue: LogQueue | null = null
|
||||||
|
let _breadcrumbs: Breadcrumb[] = []
|
||||||
|
let _pendingErrors: IssueEvent[] = []
|
||||||
|
let _initialized = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 BugCollect
|
||||||
|
*/
|
||||||
|
export function initBugCollect(options: BugCollectOptions): void {
|
||||||
|
if (_initialized) return
|
||||||
|
_options = {
|
||||||
|
release: '1.0.0',
|
||||||
|
environment: 'production',
|
||||||
|
logLevel: 'warn',
|
||||||
|
...options,
|
||||||
|
}
|
||||||
|
_queue = new LogQueue(_options.appKey, _options.baseUrl)
|
||||||
|
_initialized = true
|
||||||
|
|
||||||
|
// 启动崩溃捕获
|
||||||
|
startCrashCapture((error, tags) => {
|
||||||
|
captureError(error, tags)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 刷新待处理的错误
|
||||||
|
flushPendingErrors()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获错误
|
||||||
|
*/
|
||||||
|
export function captureError(error: Error, tags?: Record<string, unknown>, fingerprint?: string): void {
|
||||||
|
const event = buildIssueEvent('error', error, tags, fingerprint)
|
||||||
|
|
||||||
|
if (!_initialized) {
|
||||||
|
_pendingErrors.push(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
flushPendingErrors()
|
||||||
|
_queue?.push(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获致命错误
|
||||||
|
*/
|
||||||
|
export function captureCrash(error: Error, tags?: Record<string, unknown>): void {
|
||||||
|
if (!_initialized) return
|
||||||
|
_queue?.push(buildIssueEvent('fatal', error, tags))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录事件
|
||||||
|
*/
|
||||||
|
export function event(name: string, properties?: Record<string, unknown>): void {
|
||||||
|
if (!_initialized || !_options) return
|
||||||
|
|
||||||
|
const logEvent: LogEvent = {
|
||||||
|
type: 'event',
|
||||||
|
eventId: generateUuid(),
|
||||||
|
name,
|
||||||
|
properties,
|
||||||
|
appKey: _options.appKey,
|
||||||
|
userId: _options.userId,
|
||||||
|
platform: 'web',
|
||||||
|
release: _options.release ?? '1.0.0',
|
||||||
|
environment: _options.environment ?? 'production',
|
||||||
|
device: buildDeviceInfo(),
|
||||||
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
||||||
|
}
|
||||||
|
|
||||||
|
_queue?.push(logEvent)
|
||||||
|
trackFunnelEvent(name, properties)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加面包屑
|
||||||
|
*/
|
||||||
|
export function addBreadcrumb(category: string, message: string, level = 'info', data?: Record<string, unknown>): void {
|
||||||
|
if (_breadcrumbs.length >= MAX_BREADCRUMBS) {
|
||||||
|
_breadcrumbs.shift()
|
||||||
|
}
|
||||||
|
_breadcrumbs.push({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
category,
|
||||||
|
message,
|
||||||
|
level,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录警告
|
||||||
|
*/
|
||||||
|
export function warn(message: string, tags?: Record<string, unknown>): void {
|
||||||
|
if (!_initialized || !_options) return
|
||||||
|
if (LOG_LEVELS[_options.logLevel ?? 'warn'] > LOG_LEVELS.warn) return
|
||||||
|
|
||||||
|
const issueEvent: IssueEvent = {
|
||||||
|
type: 'issue',
|
||||||
|
eventId: generateUuid(),
|
||||||
|
level: 'warning',
|
||||||
|
platform: 'web',
|
||||||
|
fingerprint: '', // will be computed
|
||||||
|
appKey: _options.appKey,
|
||||||
|
exception: { type: 'Warning', value: message },
|
||||||
|
breadcrumbs: [..._breadcrumbs],
|
||||||
|
release: _options.release ?? '1.0.0',
|
||||||
|
environment: _options.environment ?? 'production',
|
||||||
|
userId: _options.userId,
|
||||||
|
device: buildDeviceInfo(),
|
||||||
|
tags,
|
||||||
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fingerprint async
|
||||||
|
computeFingerprint('Warning', message, '').then(fp => {
|
||||||
|
issueEvent.fingerprint = fp
|
||||||
|
_queue?.push(issueEvent)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录信息
|
||||||
|
*/
|
||||||
|
export function info(message: string, tags?: Record<string, unknown>): void {
|
||||||
|
if (!_initialized || !_options) return
|
||||||
|
if (LOG_LEVELS[_options.logLevel ?? 'warn'] > LOG_LEVELS.info) return
|
||||||
|
event('__log_info', { ...tags, message })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新队列
|
||||||
|
*/
|
||||||
|
export async function flush(): Promise<void> {
|
||||||
|
await _queue?.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
export function destroy(): void {
|
||||||
|
stopCrashCapture()
|
||||||
|
_queue?.destroy()
|
||||||
|
_queue = null
|
||||||
|
_breadcrumbs = []
|
||||||
|
_initialized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 IssueEvent
|
||||||
|
*/
|
||||||
|
function buildIssueEvent(level: IssueEvent['level'], error: Error, tags?: Record<string, unknown>, fingerprint?: string): IssueEvent {
|
||||||
|
const stacktrace = error.stack ?? ''
|
||||||
|
|
||||||
|
const event: IssueEvent = {
|
||||||
|
type: 'issue',
|
||||||
|
eventId: generateUuid(),
|
||||||
|
level,
|
||||||
|
platform: 'web',
|
||||||
|
fingerprint: fingerprint ?? '', // will be computed async
|
||||||
|
appKey: _options?.appKey ?? '',
|
||||||
|
exception: {
|
||||||
|
type: error.name ?? 'Error',
|
||||||
|
value: error.message ?? '',
|
||||||
|
stacktrace,
|
||||||
|
},
|
||||||
|
breadcrumbs: [..._breadcrumbs],
|
||||||
|
release: _options?.release ?? '1.0.0',
|
||||||
|
environment: _options?.environment ?? 'production',
|
||||||
|
userId: _options?.userId,
|
||||||
|
device: buildDeviceInfo(),
|
||||||
|
tags,
|
||||||
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fingerprint async
|
||||||
|
if (!fingerprint) {
|
||||||
|
computeFingerprint(error.name ?? 'Error', error.message ?? '', stacktrace).then(fp => {
|
||||||
|
event.fingerprint = fp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建设备信息
|
||||||
|
*/
|
||||||
|
function buildDeviceInfo(): DeviceInfo {
|
||||||
|
return {
|
||||||
|
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
|
||||||
|
locale: typeof navigator !== 'undefined' ? navigator.language : undefined,
|
||||||
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
screenWidth: typeof screen !== 'undefined' ? screen.width : undefined,
|
||||||
|
screenHeight: typeof screen !== 'undefined' ? screen.height : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新待处理的错误
|
||||||
|
*/
|
||||||
|
function flushPendingErrors(): void {
|
||||||
|
if (_pendingErrors.length === 0) return
|
||||||
|
const errors = [..._pendingErrors]
|
||||||
|
_pendingErrors = []
|
||||||
|
for (const error of errors) {
|
||||||
|
_queue?.push(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 UUID
|
||||||
|
*/
|
||||||
|
function generateUuid(): string {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||||
|
const r = (Math.random() * 16) | 0
|
||||||
|
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
||||||
|
return v.toString(16)
|
||||||
|
})
|
||||||
|
}
|
||||||
40
src/bugcollect/crash-capture.ts
普通文件
40
src/bugcollect/crash-capture.ts
普通文件
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Web 端崩溃捕获器
|
||||||
|
*/
|
||||||
|
|
||||||
|
type ErrorHandler = (error: Error, tags?: Record<string, unknown>) => void
|
||||||
|
|
||||||
|
let _started = false
|
||||||
|
let _handler: ErrorHandler | null = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动崩溃捕获
|
||||||
|
*/
|
||||||
|
export function startCrashCapture(handler: ErrorHandler): void {
|
||||||
|
if (_started) return
|
||||||
|
_started = true
|
||||||
|
_handler = handler
|
||||||
|
|
||||||
|
// 捕获未处理的错误
|
||||||
|
window.addEventListener('error', (event) => {
|
||||||
|
if (event.error) {
|
||||||
|
handler(event.error, { type: 'uncaught_error', filename: event.filename, lineno: event.lineno, colno: event.colno })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 捕获未处理的 Promise rejection
|
||||||
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
const error = event.reason instanceof Error
|
||||||
|
? event.reason
|
||||||
|
: new Error(String(event.reason))
|
||||||
|
handler(error, { type: 'unhandled_rejection' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止崩溃捕获
|
||||||
|
*/
|
||||||
|
export function stopCrashCapture(): void {
|
||||||
|
_started = false
|
||||||
|
_handler = null
|
||||||
|
}
|
||||||
38
src/bugcollect/fingerprint.ts
普通文件
38
src/bugcollect/fingerprint.ts
普通文件
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 错误指纹计算
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算错误指纹
|
||||||
|
*/
|
||||||
|
export async function computeFingerprint(type: string, message: string, stacktrace?: string): Promise<string> {
|
||||||
|
const normalizedMessage = normalizeMessage(message)
|
||||||
|
const top3Frames = extractTop3Frames(stacktrace)
|
||||||
|
const raw = `${type}:${normalizedMessage}:${top3Frames}`
|
||||||
|
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const data = encoder.encode(raw)
|
||||||
|
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||||
|
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归一化消息(数字替换为 N,长 hex 替换为 H)
|
||||||
|
*/
|
||||||
|
function normalizeMessage(message: string): string {
|
||||||
|
return message
|
||||||
|
.replace(/\d+/g, 'N')
|
||||||
|
.replace(/[0-9a-fA-F]{8,}/g, 'H')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取堆栈顶部帧
|
||||||
|
*/
|
||||||
|
function extractTop3Frames(stacktrace?: string): string {
|
||||||
|
if (!stacktrace) return ''
|
||||||
|
return stacktrace
|
||||||
|
.split('\n')
|
||||||
|
.slice(0, 3)
|
||||||
|
.join('|')
|
||||||
|
}
|
||||||
26
src/bugcollect/funnel-tracker.ts
普通文件
26
src/bugcollect/funnel-tracker.ts
普通文件
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 漏斗追踪器
|
||||||
|
*/
|
||||||
|
|
||||||
|
const sessions = new Map<string, string[]>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 追踪事件
|
||||||
|
*/
|
||||||
|
export function trackFunnelEvent(name: string, properties?: Record<string, unknown>): void {
|
||||||
|
// 简化实现,后续可扩展
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前会话的事件序列
|
||||||
|
*/
|
||||||
|
export function getSessionEvents(sessionId: string): string[] {
|
||||||
|
return sessions.get(sessionId) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除会话
|
||||||
|
*/
|
||||||
|
export function clearSession(sessionId: string): void {
|
||||||
|
sessions.delete(sessionId)
|
||||||
|
}
|
||||||
6
src/bugcollect/index.ts
普通文件
6
src/bugcollect/index.ts
普通文件
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* BugCollect 模块导出
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { initBugCollect, captureError, captureCrash, event, addBreadcrumb, warn, info, flush, destroy } from './bugcollect'
|
||||||
|
export type { BugCollectOptions, BugCollectAPI, IssueEvent, LogEvent, ExceptionInfo, UserInfo, DeviceInfo, SdkInfo, Breadcrumb, LogLevel } from './types'
|
||||||
150
src/bugcollect/log-queue.ts
普通文件
150
src/bugcollect/log-queue.ts
普通文件
@ -0,0 +1,150 @@
|
|||||||
|
/**
|
||||||
|
* 日志队列 - 内存队列 + 定时上传
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { IssueEvent, LogEvent } from './types'
|
||||||
|
|
||||||
|
const BATCH_SIZE = 30
|
||||||
|
const FLUSH_INTERVAL_MS = 10000
|
||||||
|
const MAX_QUEUE_SIZE = 500
|
||||||
|
const MAX_RETRY = 3
|
||||||
|
const STORAGE_KEY = '@xuqm_bugcollect:queue'
|
||||||
|
|
||||||
|
type Event = IssueEvent | LogEvent
|
||||||
|
|
||||||
|
export class LogQueue {
|
||||||
|
private memory: Event[] = []
|
||||||
|
private flushTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
private retryCount = 0
|
||||||
|
private appKey: string
|
||||||
|
private baseUrl: string
|
||||||
|
|
||||||
|
constructor(appKey: string, baseUrl: string) {
|
||||||
|
this.appKey = appKey
|
||||||
|
this.baseUrl = baseUrl
|
||||||
|
this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送事件
|
||||||
|
*/
|
||||||
|
push(event: Event): void {
|
||||||
|
if (this.shouldPersist(event)) {
|
||||||
|
const stored = this.readStored()
|
||||||
|
if (stored.length >= MAX_QUEUE_SIZE) stored.shift()
|
||||||
|
stored.push(event)
|
||||||
|
this.writeStored(stored)
|
||||||
|
} else {
|
||||||
|
if (this.memory.length >= MAX_QUEUE_SIZE) this.memory.shift()
|
||||||
|
this.memory.push(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新队列
|
||||||
|
*/
|
||||||
|
async flush(): Promise<void> {
|
||||||
|
const stored = this.readStored()
|
||||||
|
const combined = [...stored, ...this.memory]
|
||||||
|
if (combined.length === 0) return
|
||||||
|
|
||||||
|
const batch = combined.slice(0, BATCH_SIZE)
|
||||||
|
const storedUsed = Math.min(stored.length, BATCH_SIZE)
|
||||||
|
const memUsed = batch.length - storedUsed
|
||||||
|
|
||||||
|
// 乐观移除
|
||||||
|
this.writeStored(stored.slice(storedUsed))
|
||||||
|
this.memory.splice(0, memUsed)
|
||||||
|
|
||||||
|
const issues = batch.filter((e): e is IssueEvent => e.type === 'issue')
|
||||||
|
const events = batch.filter((e): e is LogEvent => e.type === 'event')
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (issues.length > 0) {
|
||||||
|
await this.post('/bugcollect/v1/issues/batch', issues)
|
||||||
|
}
|
||||||
|
if (events.length > 0) {
|
||||||
|
await this.post('/bugcollect/v1/events/batch', events)
|
||||||
|
}
|
||||||
|
this.retryCount = 0
|
||||||
|
} catch {
|
||||||
|
this.retryCount++
|
||||||
|
if (this.retryCount < MAX_RETRY) {
|
||||||
|
const toRequeue = batch.filter(e => this.shouldPersist(e))
|
||||||
|
if (toRequeue.length > 0) {
|
||||||
|
const current = this.readStored()
|
||||||
|
this.writeStored([...toRequeue, ...current])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁队列
|
||||||
|
*/
|
||||||
|
destroy(): void {
|
||||||
|
if (this.flushTimer) {
|
||||||
|
clearInterval(this.flushTimer)
|
||||||
|
this.flushTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否需要持久化
|
||||||
|
*/
|
||||||
|
private shouldPersist(event: Event): boolean {
|
||||||
|
return event.type === 'issue' && (event.level === 'fatal' || event.level === 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传数据
|
||||||
|
*/
|
||||||
|
private async post(path: string, data: Event[]): Promise<void> {
|
||||||
|
const body = JSON.stringify({
|
||||||
|
sentAt: new Date().toISOString(),
|
||||||
|
sdk: { name: 'bugcollect.vue3', version: '1.0.0' },
|
||||||
|
events: data,
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(`${this.baseUrl}${path}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-App-Key': this.appKey,
|
||||||
|
},
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Upload failed: ${response.status}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取持久化队列
|
||||||
|
*/
|
||||||
|
private readStored(): Event[] {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY)
|
||||||
|
if (!raw) return []
|
||||||
|
return JSON.parse(raw) as Event[]
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入持久化队列
|
||||||
|
*/
|
||||||
|
private writeStored(queue: Event[]): void {
|
||||||
|
try {
|
||||||
|
if (queue.length === 0) {
|
||||||
|
localStorage.removeItem(STORAGE_KEY)
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(queue))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/bugcollect/types.ts
普通文件
93
src/bugcollect/types.ts
普通文件
@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* BugCollect 数据模型
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface IssueEvent {
|
||||||
|
type: 'issue'
|
||||||
|
eventId: string
|
||||||
|
level: 'fatal' | 'error' | 'warning' | 'info' | 'debug'
|
||||||
|
platform: string
|
||||||
|
fingerprint: string
|
||||||
|
appKey: string
|
||||||
|
exception?: ExceptionInfo
|
||||||
|
breadcrumbs?: Breadcrumb[]
|
||||||
|
release: string
|
||||||
|
environment: string
|
||||||
|
userId?: string
|
||||||
|
user?: UserInfo
|
||||||
|
device?: DeviceInfo
|
||||||
|
tags?: Record<string, unknown>
|
||||||
|
sdk?: SdkInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogEvent {
|
||||||
|
type: 'event'
|
||||||
|
eventId: string
|
||||||
|
name: string
|
||||||
|
properties?: Record<string, unknown>
|
||||||
|
appKey: string
|
||||||
|
userId?: string
|
||||||
|
platform: string
|
||||||
|
release: string
|
||||||
|
environment: string
|
||||||
|
device?: DeviceInfo
|
||||||
|
sdk?: SdkInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExceptionInfo {
|
||||||
|
type: string
|
||||||
|
value: string
|
||||||
|
stacktrace?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeviceInfo {
|
||||||
|
model?: string
|
||||||
|
manufacturer?: string
|
||||||
|
osName?: string
|
||||||
|
osVersion?: string
|
||||||
|
locale?: string
|
||||||
|
timezone?: string
|
||||||
|
userAgent?: string
|
||||||
|
screenWidth?: number
|
||||||
|
screenHeight?: number
|
||||||
|
buildType?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SdkInfo {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Breadcrumb {
|
||||||
|
timestamp: number
|
||||||
|
category: string
|
||||||
|
message: string
|
||||||
|
level: string
|
||||||
|
data?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none'
|
||||||
|
|
||||||
|
export interface BugCollectOptions {
|
||||||
|
appKey: string
|
||||||
|
baseUrl: string
|
||||||
|
release?: string
|
||||||
|
environment?: string
|
||||||
|
logLevel?: LogLevel
|
||||||
|
userId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BugCollectAPI {
|
||||||
|
captureError(error: Error, tags?: Record<string, unknown>, fingerprint?: string): void
|
||||||
|
captureCrash(error: Error, tags?: Record<string, unknown>): void
|
||||||
|
event(name: string, properties?: Record<string, unknown>): void
|
||||||
|
addBreadcrumb(category: string, message: string, level?: string, data?: Record<string, unknown>): void
|
||||||
|
warn(message: string, tags?: Record<string, unknown>): void
|
||||||
|
info(message: string, tags?: Record<string, unknown>): void
|
||||||
|
flush(): Promise<void>
|
||||||
|
destroy(): void
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
export { init, login, logout, setToken, setUserId, getToken, getUserId, getConfig } from './core/sdk'
|
export { init, login, logout, setToken, setUserId, getToken, getUserId, getConfig } from './core/sdk'
|
||||||
export { http } from './core/http'
|
export { http } from './core/http'
|
||||||
export { ImClient } from './im/ImClient'
|
export { ImClient } from './im/ImClient'
|
||||||
|
export { initBugCollect, captureError, captureCrash, event, addBreadcrumb, warn, info, flush, destroy } from './bugcollect'
|
||||||
export {
|
export {
|
||||||
acceptFriendRequest,
|
acceptFriendRequest,
|
||||||
acceptGroupJoinRequest,
|
acceptGroupJoinRequest,
|
||||||
@ -90,3 +91,4 @@ export type {
|
|||||||
ImEventMap,
|
ImEventMap,
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
} from './types'
|
} from './types'
|
||||||
|
export type { BugCollectOptions, BugCollectAPI, IssueEvent, LogEvent, ExceptionInfo, UserInfo, DeviceInfo, SdkInfo, Breadcrumb, LogLevel } from './bugcollect'
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户