@jerry_aurora/sky-monitor-sdk
v0.2.0
Published
AI 应用监控 SDK - 支持 SSE 流式追踪、错误监控、性能采集
Downloads
9
Maintainers
Readme
Sky Monitor SDK
轻量级 AI 应用监控 SDK,核心 gzip 仅 1.4KB。
为什么需要它?
传统监控 SDK(Sentry、LogRocket)针对通用 Web 应用设计,无法满足 AI 对话场景的特殊需求:
| 痛点 | 传统方案 | Sky Monitor | |-----|---------|-------------| | SSE 流式响应 | 只能监控请求开始/结束 | 完整生命周期追踪(TTFB/TTLB/阶段/卡顿) | | Tool Calling | 无法追踪 | 工具调用链路、耗时、结果 | | AI 生图 | 无法追踪 | 图片加载状态、尺寸、耗时 | | 对话重试 | 丢失关联 | traceId 串联重试链路 | | 包体积 | 50KB+ | 核心 1.4KB,按需加载 |
核心能力
┌─────────────────────────────────────────────────────────────┐
│ AI 对话追踪 SSE 生命周期 · 阶段拆分 · Tool Calling · 卡顿检测 │
├─────────────────────────────────────────────────────────────┤
│ 错误监控 JS 错误 · Promise 拒绝 · 资源加载失败 · 去重 │
├─────────────────────────────────────────────────────────────┤
│ 性能采集 Web Vitals (FCP/LCP/CLS/INP) · HTTP 请求耗时 │
├─────────────────────────────────────────────────────────────┤
│ 智能上报 优先级队列 · 按类型差异化策略 · 离线缓存 · 指数退避重试 │
├─────────────────────────────────────────────────────────────┤
│ 会话录制 基于 rrweb · 问题复现 · 隐私脱敏 │
└─────────────────────────────────────────────────────────────┘安装
npm install @sky-monitor/sdk快速接入
import { createMonitorWithPreset } from '@sky-monitor/sdk'
const monitor = createMonitorWithPreset({
appId: 'my-ai-app',
endpoint: '/api/monitor', // 上报地址
preset: 'production', // 预设模式
})
monitor.setContext({ userId: 'user_123', version: '1.0.0' })| 预设 | 上报模式 | 采样率 | 插件 | |-----|---------|-------|------| | development | immediate | 100% | 全部 | | production | batch | 10% | 核心 | | minimal | batch | 5% | 仅错误 |
全量配置
对象配置方式,插件按 priority 自动排序,无需关心注册顺序:
import { createMonitorWithPreset } from '@sky-monitor/sdk'
const monitor = createMonitorWithPreset({
// ===== 基础配置 =====
appId: 'my-ai-app',
endpoint: '/api/monitor',
debug: true,
// ===== 启用的插件 =====
plugins: ['error', 'performance', 'fetch', 'xhr', 'session', 'dedupe', 'sampling'],
// ===== 传输配置 =====
transport: {
mode: 'batch',
typeConfig: {
js_error: 'immediate',
promise_error: 'immediate',
web_vital: 'batch',
http_request: 'throttle',
},
batchSize: 10,
flushInterval: 5000,
throttleInterval: 1000,
maxRetries: 3,
baseRetryDelay: 1000,
maxRetryDelay: 30000,
},
// ===== 采样配置 =====
sampling: {
rate: 0.1,
mode: 'session',
typeRates: { web_vital: 0.5 },
alwaysSample: ['js_error'],
},
// ===== 错误监控配置 =====
error: {
captureConsoleError: false,
dedupeWindow: 5000,
ignoreErrors: [/ResizeObserver/],
ignoreUrls: [/chrome-extension/],
},
// ===== Fetch 拦截配置 =====
fetch: {
includeUrls: [/\/api\//],
excludeUrls: [/\/health/],
},
// ===== XHR 拦截配置 =====
xhr: {
includeUrls: [/\/api\//],
excludeUrls: [/\/health/],
},
// ===== 去重配置 =====
dedupe: {
windowMs: 5000,
},
// ===== 离线队列配置 =====
offlineQueue: {
maxRetries: 3,
retryInterval: 10000,
},
// ===== 会话录制配置(需单独导入)=====
// replay: { ... } // 见下方 "会话录制" 章节
})
monitor.setContext({ userId: 'user_123', version: '1.0.0' })手动注册插件
需要更细粒度控制时,可以手动注册插件(插件有 priority 自动排序):
import { Monitor } from '@sky-monitor/sdk'
import { ErrorPlugin } from '@sky-monitor/sdk/plugins/error'
import { FetchPlugin } from '@sky-monitor/sdk/plugins/fetch'
import { TransportPlugin } from '@sky-monitor/sdk/plugins/transport'
const monitor = new Monitor({ appId: 'my-ai-app', debug: true })
// 注册顺序无关,按 priority 自动排序
monitor.use(new TransportPlugin({ endpoint: '/api/monitor' }))
monitor.use(new ErrorPlugin({ ignoreErrors: [/ResizeObserver/] }))
monitor.use(new FetchPlugin({ includeUrls: [/\/api\//] }))会话录制(Replay)
Replay 插件内置 rrweb(~182KB),单独打包以保持主包体积:
import { createMonitorWithPreset } from '@sky-monitor/sdk'
import { ReplayPlugin } from '@sky-monitor/sdk/plugins/replay'
const monitor = createMonitorWithPreset({
appId: 'my-ai-app',
endpoint: '/api/monitor',
})
// 单独注册 Replay 插件
monitor.use(new ReplayPlugin({
checkoutEveryNms: 30000,
bufferSize: 1000,
flushInterval: 10000,
blockSelector: '.no-record',
maskInputOptions: { password: true },
}))配置类型定义
// ===== 完整配置 =====
interface MonitorConfig {
appId: string // 应用标识(必填)
endpoint?: string // 上报地址,默认 '/api/monitor'
debug?: boolean // 调试模式
preset?: 'development' | 'production' | 'minimal'
plugins?: PluginName[] // 启用的插件
// 插件配置
transport?: TransportPluginOptions
sampling?: SamplingPluginOptions
error?: ErrorPluginOptions
fetch?: FetchPluginOptions
xhr?: XHRPluginOptions
dedupe?: DedupePluginOptions
offlineQueue?: OfflineQueuePluginOptions
replay?: ReplayPluginOptions
}
// ===== 传输配置 =====
interface TransportPluginOptions {
mode?: 'immediate' | 'batch' | 'throttle'
typeConfig?: Record<string, 'immediate' | 'batch' | 'throttle'>
batchSize?: number // 默认 10
flushInterval?: number // 默认 5000ms
throttleInterval?: number // 默认 1000ms
maxRetries?: number // 默认 3
baseRetryDelay?: number // 默认 1000ms
maxRetryDelay?: number // 默认 30000ms
}
// ===== 采样配置 =====
interface SamplingPluginOptions {
rate?: number // 默认采样率 (0-1)
mode?: 'random' | 'session' | 'user'
typeRates?: Record<string, number>
alwaysSample?: string[]
}
// ===== 错误监控配置 =====
interface ErrorPluginOptions {
captureConsoleError?: boolean // 默认 false
dedupeWindow?: number // 默认 5000ms
ignoreErrors?: RegExp[]
ignoreUrls?: RegExp[]
}
// ===== 网络请求配置 =====
interface FetchPluginOptions {
includeUrls?: RegExp[]
excludeUrls?: RegExp[]
}
interface XHRPluginOptions {
includeUrls?: RegExp[]
excludeUrls?: RegExp[]
}
// ===== 去重配置 =====
interface DedupePluginOptions {
windowMs?: number // 默认 5000ms
}
// ===== 离线队列配置 =====
interface OfflineQueuePluginOptions {
maxRetries?: number // 默认 3
retryInterval?: number // 默认 10000ms
}
// ===== 会话录制配置 =====
interface ReplayPluginOptions {
checkoutEveryNms?: number // 默认 30000ms
bufferSize?: number // 默认 1000
flushInterval?: number // 默认 10000ms
blockSelector?: string
maskInputOptions?: { password?: boolean; email?: boolean }
}
// ===== AI 对话追踪 =====
interface TraceOptions {
aiMessageId: string // AI 消息 ID(必填)
previousTraceId?: string // 重试时的前一个 traceId
stallThreshold?: number // 卡顿检测阈值(毫秒)
}AI 对话追踪使用
const trace = monitor.createTrace({ aiMessageId: 'msg_123', stallThreshold: 5000 })
trace.start() // SSE 开始
trace.firstChunk() // 首字节 (TTFB)
trace.phaseStart('thinking') // 阶段开始
trace.phaseEnd('thinking')
trace.toolStart('web_search', { query: 'AI' })
trace.toolEnd('web_search', { success: true, resultCount: 5 })
trace.complete() // SSE 完成 (TTLB)后端接收示例
// app/api/monitor/route.ts
import { NextRequest, NextResponse } from 'next/server'
interface MonitorEvent {
id: string
type: string // 'js_error' | 'http_request' | 'sse_complete' | ...
timestamp: number
data: {
// http_request
url?: string
method?: string
status?: number
duration?: number // HTTP 请求耗时(毫秒)
// js_error
message?: string
stack?: string
// sse_complete
ttfb?: number
ttlb?: number
// web_vital
name?: string
value?: number
[key: string]: unknown
}
context: {
appId: string
sessionId?: string
userId?: string
url?: string
}
}
export async function POST(req: NextRequest) {
const events: MonitorEvent[] = await req.json()
await db.monitorEvents.createMany({ data: events })
return NextResponse.json({ success: true })
}包体积
| 模块 | 原始 | Gzip | |-----|------|------| | 核心 | 4.1 KB | 1.4 KB | | 全量 UMD | 32 KB | 8.2 KB | | Replay UMD(含 rrweb) | 182 KB | 58 KB |
Replay 插件内置 rrweb,需单独导入
CDN 使用(UMD)
<!-- 核心 SDK -->
<script src="https://cdn.example.com/sky-monitor.umd.js"></script>
<script>
const monitor = SkyMonitor.createMonitor({ appId: 'my-app' })
monitor.setContext({ userId: 'user_123' })
</script>
<!-- 需要 Replay 时额外加载 -->
<script src="https://cdn.example.com/sky-monitor-replay.umd.js"></script>
<script>
monitor.use(new SkyMonitorReplay.ReplayPlugin({
blockSelector: '.no-record'
}))
</script>License
MIT
