npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jerry_aurora/sky-monitor-sdk

v0.2.0

Published

AI 应用监控 SDK - 支持 SSE 流式追踪、错误监控、性能采集

Downloads

9

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