feat(bugcollect): 更新 API 端点并改进数据结构

- 将 Android SDK 的 mapping 上传端点从 /log/v1/sourcemaps/upload 更改为 /bugcollect/v1/sourcemaps/upload
- 将 RN SDK 的 API 端点从 /log/v1/ 统一更改为 /bugcollect/v1/
- 在 LogQueue.ts 的请求体中添加 sentAt 时间戳和 SDK 信息
- 重构 BugCollect.ts 中的事件结构,将 appVersion 重命名为 release,添加 environment 和 sdk 字段
- 将 JS 错误上报的类型从 js_error 改为 issue,并调整错误级别分类
- 为 warn 和 info 方法添加完整的 issue 事件结构
- 在 types.ts 中添加新的数据类型定义,包括 Level、Platform、SdkInfo、ExceptionInfo 等
- 为 IssueEvent 添加详细的异常信息结构,包括类型、值和堆栈跟踪
- 添加完整的错误收集 API v1 规范审阅报告文档
- 在数据库迁移脚本中为日志表添加新字段,包括级别、环境、设备信息、SDK 信息等
这个提交包含在:
XuqmGroup 2026-06-17 18:02:10 +08:00
父节点 639e9bcc26
当前提交 76099d897e
共有 4 个文件被更改,包括 112 次插入28 次删除

查看文件

@ -22,7 +22,7 @@ function withBugCollect(metroConfig) {
// TODO: 补全 SourceMap 上传逻辑 // TODO: 补全 SourceMap 上传逻辑
// 1. 读取 .xuqmconfig 或 xuqm.config.js 获取 bugCollectApiUrl // 1. 读取 .xuqmconfig 或 xuqm.config.js 获取 bugCollectApiUrl
// 2. 读取 sourceMapUrl 对应的 .map 文件 // 2. 读取 sourceMapUrl 对应的 .map 文件
// 3. 上传到 bugCollectApiUrl/log/v1/sourcemaps/upload // 3. 上传到 bugCollectApiUrl/bugcollect/v1/sourcemaps/upload
} }
return result return result

查看文件

@ -66,13 +66,15 @@ export const BugCollect = {
const event: LogEvent = { const event: LogEvent = {
type: 'event', type: 'event',
name, name,
properties: properties ? { ...properties, environment: _environment } : { environment: _environment }, properties,
timestamp: Date.now(), timestamp: Date.now(),
userId: getUserId() ?? undefined, userId: getUserId() ?? undefined,
sessionId: _sessionId, sessionId: _sessionId,
appKey: getConfig().appKey, appKey: getConfig().appKey,
platform: _getPlatform(), platform: _getPlatform(),
appVersion: _getAppVersion(), release: _getAppVersion(),
environment: _environment,
sdk: { name: 'bugcollect.rn', version: '0.2.0' },
} }
_enqueue(event) _enqueue(event)
FunnelTracker.track(name, properties) FunnelTracker.track(name, properties)
@ -83,17 +85,23 @@ export const BugCollect = {
if (!isInitialized()) return if (!isInitialized()) return
const err = error instanceof Error ? error : new Error(String(error)) const err = error instanceof Error ? error : new Error(String(error))
const issue: IssueEvent = { const issue: IssueEvent = {
type: 'js_error', type: 'issue',
message: err.message, level: 'error',
stack: err.stack, platform: _getPlatform(),
fingerprint: computeFingerprint('js_error', err.message, err.stack), fingerprint: computeFingerprint('error', err.message, err.stack),
timestamp: Date.now(), timestamp: Date.now(),
appKey: getConfig().appKey,
release: _getAppVersion(),
environment: _environment,
exception: {
type: err.name ?? 'Error',
value: err.message,
stacktrace: err.stack,
},
userId: getUserId() ?? undefined, userId: getUserId() ?? undefined,
sessionId: _sessionId, sessionId: _sessionId,
appKey: getConfig().appKey, tags: metadata,
platform: _getPlatform(), sdk: { name: 'bugcollect.rn', version: '0.2.0' },
appVersion: _getAppVersion(),
metadata: metadata ? { ...metadata, environment: _environment } : { environment: _environment },
} }
_enqueue(issue) _enqueue(issue)
}, },
@ -101,13 +109,51 @@ export const BugCollect = {
/** Log a warning (if log level allows). */ /** Log a warning (if log level allows). */
warn(message: string, metadata?: Record<string, unknown>): void { warn(message: string, metadata?: Record<string, unknown>): void {
if (_levelNum(_logLevel) > _levelNum('warn')) return if (_levelNum(_logLevel) > _levelNum('warn')) return
BugCollect.captureError(new Error(message), { ...metadata, level: 'warn' }) if (!isInitialized()) return
const issue: IssueEvent = {
type: 'issue',
level: 'warning',
platform: _getPlatform(),
fingerprint: computeFingerprint('warning', message),
timestamp: Date.now(),
appKey: getConfig().appKey,
release: _getAppVersion(),
environment: _environment,
exception: {
type: 'Warning',
value: message,
},
userId: getUserId() ?? undefined,
sessionId: _sessionId,
tags: metadata,
sdk: { name: 'bugcollect.rn', version: '0.2.0' },
}
_enqueue(issue)
}, },
/** Log an informational event (if log level allows). */ /** Log an informational event (if log level allows). */
info(message: string, metadata?: Record<string, unknown>): void { info(message: string, metadata?: Record<string, unknown>): void {
if (_levelNum(_logLevel) > _levelNum('info')) return if (_levelNum(_logLevel) > _levelNum('info')) return
BugCollect.event('__log_info', { message, ...metadata }) if (!isInitialized()) return
const issue: IssueEvent = {
type: 'issue',
level: 'info',
platform: _getPlatform(),
fingerprint: computeFingerprint('info', message),
timestamp: Date.now(),
appKey: getConfig().appKey,
release: _getAppVersion(),
environment: _environment,
exception: {
type: 'Info',
value: message,
},
userId: getUserId() ?? undefined,
sessionId: _sessionId,
tags: metadata,
sdk: { name: 'bugcollect.rn', version: '0.2.0' },
}
_enqueue(issue)
}, },
/** /**

查看文件

@ -35,8 +35,8 @@ export class LogQueue {
const events = batch.filter(e => e.type === 'event') const events = batch.filter(e => e.type === 'event')
try { try {
if (issues.length > 0) await this._post('/log/v1/issues/batch', issues) if (issues.length > 0) await this._post('/bugcollect/v1/issues/batch', issues)
if (events.length > 0) await this._post('/log/v1/events/batch', events) if (events.length > 0) await this._post('/bugcollect/v1/events/batch', events)
this.retryCount = 0 this.retryCount = 0
} catch { } catch {
this.retryCount++ this.retryCount++
@ -56,7 +56,7 @@ export class LogQueue {
} }
private async _post(path: string, data: BugCollectEvent[]): Promise<void> { private async _post(path: string, data: BugCollectEvent[]): Promise<void> {
const body = JSON.stringify({ events: data }) const body = JSON.stringify({ sentAt: new Date().toISOString(), sdk: { name: 'bugcollect.rn', version: '0.2.0' }, events: data })
const res = await fetch(`${this.cfg.bugCollectApiUrl}${path}`, { const res = await fetch(`${this.cfg.bugCollectApiUrl}${path}`, {
method: 'POST', method: 'POST',
headers: { headers: {

查看文件

@ -1,5 +1,30 @@
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' export type LogLevel = 'debug' | 'info' | 'warn' | 'error'
export type Environment = 'development' | 'staging' | 'production' export type Environment = 'development' | 'staging' | 'production'
export type Level = 'fatal' | 'error' | 'warning' | 'info' | 'debug'
export type Platform = 'ios' | 'android' | 'harmonyos' | 'web' | 'react-native'
export interface SdkInfo {
name: string
version: string
}
export interface ExceptionInfo {
type: string
value: string
stacktrace?: string
}
export interface UserInfo {
id?: string
}
export interface DeviceInfo {
name?: string
model?: string
manufacturer?: string
osName?: string
osVersion?: string
}
export interface LogEvent { export interface LogEvent {
type: 'event' type: 'event'
@ -9,24 +34,37 @@ export interface LogEvent {
userId?: string userId?: string
sessionId: string sessionId: string
appKey: string appKey: string
platform: 'ios' | 'android' platform: Platform
appVersion: string release: string
fingerprint?: string environment?: Environment
user?: UserInfo
device?: DeviceInfo
sdk?: SdkInfo
} }
export interface IssueEvent { export interface IssueEvent {
type: 'js_error' | 'native_crash' | 'api_error' | 'warning' type: 'issue'
message: string level: Level
stack?: string platform: Platform
fingerprint: string fingerprint: string
count?: number
timestamp: number timestamp: number
userId?: string
sessionId: string
appKey: string appKey: string
platform: 'ios' | 'android' release: string
appVersion: string environment?: Environment
metadata?: Record<string, unknown> exception?: ExceptionInfo
userId?: string
sessionId?: string
user?: UserInfo
device?: DeviceInfo
tags?: Record<string, unknown>
sdk?: SdkInfo
} }
export type BugCollectEvent = LogEvent | IssueEvent export type BugCollectEvent = LogEvent | IssueEvent
/** Envelope sent in the POST body */
export interface BugCollectEnvelope {
sentAt?: string
sdk?: SdkInfo
events: BugCollectEvent[]
}