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

@ezijing/log

v0.1.1

Published

A lightweight and powerful log collection SDK for web applications

Readme

@ezijing/log

轻量级前端日志采集 SDK,用于收集和上报 Web 应用的日志信息。

特性

  • 🚀 轻量级 - 无外部依赖,gzip 后 < 5KB
  • 📦 批量上报 - 自动批量发送,减少网络请求
  • 🔄 离线支持 - 网络断开时自动缓存,恢复后重新发送
  • 🎯 多种日志类型 - 支持 API、错误、事件、行为等多种日志类型
  • 🛡️ 自动捕获 - 自动捕获未处理错误、Promise 拒绝、API 请求
  • 📱 设备信息 - 自动收集设备、浏览器、操作系统信息
  • 🔧 高度可配置 - 支持自定义 Transport、钩子函数等

安装

npm install @ezijing/log
# or
pnpm add @ezijing/log
# or
yarn add @ezijing/log

快速开始

import { EzijingLog } from '@ezijing/log'

// 初始化 SDK
const logger = EzijingLog.init({
  appName: 'my-app',
  endpoint: 'https://api.example.com/logs',
  env: 'production',
  appVersion: '1.0.0',
})

// 记录日志
logger.info('用户登录成功', { userId: '12345' })
logger.error('操作失败', { action: 'submit' })

API 文档

初始化配置

interface LogConfig {
  /** 应用名称(必填) */
  appName: string
  
  /** API 端点 */
  endpoint?: string
  
  /** 环境:development | staging | production */
  env?: Environment
  
  /** 应用版本 */
  appVersion?: string
  
  /** 用户 ID */
  userId?: string
  
  /** 批量发送的日志数量,默认 50 */
  batchSize?: number
  
  /** 自动发送间隔(毫秒),默认 5000 */
  flushInterval?: number
  
  /** 是否启用调试模式,默认 false */
  debug?: boolean
  
  /** 最低日志级别,默认 debug */
  minLevel?: LogLevel
  
  /** 是否自动捕获错误,默认 true */
  captureErrors?: boolean
  
  /** 是否自动捕获 console,默认 false */
  captureConsole?: boolean
  
  /** 是否自动捕获 API 请求,默认 false */
  captureApi?: boolean
  
  /** API 捕获过滤器 */
  apiFilter?: (url: string) => boolean
  
  /** 自定义请求头 */
  headers?: Record<string, string>
  
  /** 最大重试次数,默认 3 */
  maxRetries?: number
  
  /** 重试延迟(毫秒),默认 1000 */
  retryDelay?: number
  
  /** 发送前钩子,返回 null 则不发送 */
  beforeSend?: (entry: LogEntry) => LogEntry | null
}

日志方法

// 基础日志
logger.debug(message: string, metadata?: Record<string, unknown>)
logger.info(message: string, metadata?: Record<string, unknown>)
logger.warn(message: string, metadata?: Record<string, unknown>)
logger.error(message: string | Error, metadata?: Record<string, unknown>)
logger.fatal(message: string | Error, metadata?: Record<string, unknown>)

// 事件日志
logger.event(message: string, metadata?: Record<string, unknown>)

// 行为日志
logger.behavior(message: string, metadata?: Record<string, unknown>)

// API 日志
logger.api(details: ApiDetails, message?: string)

上下文管理

// 设置用户 ID
logger.setUser('user-123')

// 清除用户 ID
logger.clearUser()

// 设置全局上下文(会附加到所有日志)
logger.setContext({ tenant: 'company-a' })

// 清除全局上下文
logger.clearContext()

手动刷新

// 立即发送缓冲区中的所有日志
await logger.flush()

使用示例

基础使用

import { EzijingLog } from '@ezijing/log'

const logger = EzijingLog.init({
  appName: 'my-app',
  endpoint: '/api/logs',
  env: 'production',
})

// 信息日志
logger.info('页面加载完成', { page: '/home' })

// 警告日志
logger.warn('API 响应慢', { duration: 5000 })

// 错误日志
try {
  throw new Error('Something went wrong')
} catch (e) {
  logger.error(e, { context: 'user-action' })
}

自动捕获 API 请求

const logger = EzijingLog.init({
  appName: 'my-app',
  endpoint: '/api/logs',
  captureApi: true,
  apiFilter: (url) => !url.includes('/health'), // 过滤健康检查
})

// 之后所有的 fetch 和 XMLHttpRequest 请求都会被自动记录
fetch('/api/users')

记录用户行为

// 用户点击按钮
logger.behavior('点击购买按钮', {
  productId: 'prod-123',
  price: 99.99,
})

// 用户浏览页面
logger.behavior('浏览商品详情', {
  productId: 'prod-123',
  duration: 30000,
})

手动记录 API 请求

const startTime = performance.now()

try {
  const response = await fetch('/api/users')
  const duration = Math.round(performance.now() - startTime)
  
  logger.api({
    method: 'GET',
    url: '/api/users',
    statusCode: response.status,
    duration,
  })
} catch (error) {
  logger.api({
    method: 'GET',
    url: '/api/users',
    statusCode: 0,
    duration: Math.round(performance.now() - startTime),
  }, '请求失败')
}

使用 beforeSend 钩子

const logger = EzijingLog.init({
  appName: 'my-app',
  endpoint: '/api/logs',
  beforeSend: (entry) => {
    // 过滤敏感信息
    if (entry.metadata?.password) {
      delete entry.metadata.password
    }
    
    // 返回 null 则不发送
    if (entry.message.includes('debug-only')) {
      return null
    }
    
    return entry
  },
})

在 React 中使用

import { useEffect } from 'react'
import { EzijingLog } from '@ezijing/log'

// 初始化(在应用入口)
const logger = EzijingLog.init({
  appName: 'react-app',
  endpoint: '/api/logs',
  env: import.meta.env.MODE as 'development' | 'production',
  captureErrors: true,
})

// 错误边界组件
class ErrorBoundary extends React.Component {
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    logger.error(error, {
      componentStack: errorInfo.componentStack,
    })
  }
  
  render() {
    return this.props.children
  }
}

// 在组件中使用
function MyComponent() {
  useEffect(() => {
    logger.behavior('组件挂载', { component: 'MyComponent' })
    
    return () => {
      logger.behavior('组件卸载', { component: 'MyComponent' })
    }
  }, [])
  
  const handleClick = () => {
    logger.event('按钮点击', { button: 'submit' })
  }
  
  return <button onClick={handleClick}>提交</button>
}

在 Vue 中使用

// main.ts
import { createApp } from 'vue'
import { EzijingLog } from '@ezijing/log'

const logger = EzijingLog.init({
  appName: 'vue-app',
  endpoint: '/api/logs',
  env: import.meta.env.MODE as 'development' | 'production',
})

const app = createApp(App)

// 全局错误处理
app.config.errorHandler = (error, instance, info) => {
  logger.error(error as Error, {
    component: instance?.$options.name,
    info,
  })
}

// 提供给组件使用
app.provide('logger', logger)
<script setup>
import { inject, onMounted } from 'vue'

const logger = inject('logger')

onMounted(() => {
  logger.info('组件已挂载')
})

function handleClick() {
  logger.event('用户点击', { action: 'click' })
}
</script>

日志数据结构

发送到服务器的日志格式:

interface LogEntry {
  type: 'api' | 'error' | 'event' | 'behavior' | 'custom'
  level: 'debug' | 'info' | 'warn' | 'error' | 'fatal'
  message: string
  appName: string
  userId?: string
  env?: 'development' | 'staging' | 'production'
  appVersion?: string
  url?: string
  userAgent?: string
  device?: {
    type: 'mobile' | 'tablet' | 'desktop'
    os: string
    browser: string
  }
  api?: {
    method: string
    url: string
    statusCode: number
    duration: number
    request?: unknown
    response?: unknown
  }
  error?: {
    name: string
    message: string
    stack?: string
    componentStack?: string
  }
  metadata?: Record<string, unknown>
}

请求体格式:

{
  "logs": [
    { /* LogEntry */ },
    { /* LogEntry */ }
  ]
}

License

MIT