21.06-用户行为日志

要点

  • 用户行为日志记录用户在系统中的操作:登录、创建 API key、发送消息等
  • 用于安全审计、用户画像和产品分析
  • 区分关键行为(登录、付费)和普通行为(浏览、点击)
  • 注意隐私合规,不要记录敏感操作细节

内容

1. 用户行为日志的用途

用户行为日志(也叫审计日志或操作日志)有三个主要用途:

用途示例需要的字段
安全审计谁在什么时候登录了系统?谁删除了 API key?userId, action, timestamp, ip, userAgent
用户画像用户 A 每天调用 100 次 API,主要在早上 9-11 点userId, action, timestamp, metadata
产品分析80% 的用户在注册后 7 天内创建了 API keyuserId, action, timestamp

不同用途对日志的详细程度要求不同:

  • 安全审计:需要完整信息(IP、userAgent、操作对象)
  • 用户画像:只需要 userId + action + timestamp
  • 产品分析:可以采样,不需要全量记录

2. 哪些行为需要记录

AI 网关的典型用户行为:

账户相关

  • user.login - 用户登录
  • user.logout - 用户登出
  • user.register - 用户注册
  • user.password_change - 修改密码
  • user.api_key.create - 创建 API key
  • user.api_key.delete - 删除 API key

AI 调用相关

  • ai.call.success - AI 调用成功(已经在前几节记录过)
  • ai.call.failed - AI 调用失败
  • ai.conversation.create - 创建新对话
  • ai.message.send - 发送消息

付费相关

  • payment.subscription.create - 创建订阅
  • payment.subscription.cancel - 取消订阅
  • payment.invoice.paid - 支付成功

管理操作

  • admin.user.disable - 禁用用户
  • admin.api_key.revoke - 撤销 API key

3. 基础实现

// src/lib/audit-log.ts
export interface AuditEvent {
  userId: string
  action: string
  timestamp: number
  ip?: string
  userAgent?: string
  metadata?: Record<string, unknown>
}
 
export async function recordAuditEvent(
  env: Env,
  event: AuditEvent
) {
  // 1. 记录到 Analytics Engine(用于分析)
  env.ANALYTICS.writeDataPoint({
    blobs: [event.userId, event.action, JSON.stringify(event.metadata)],
    doubles: [event.timestamp],
    indexes: [event.userId],
  })
 
  // 2. 记录到数据库(用于审计查询)
  await env.DB.prepare(`
    INSERT INTO audit_logs (
      user_id, action, timestamp, ip, user_agent, metadata
    ) VALUES (?, ?, ?, ?, ?, ?)
  `).bind(
    event.userId,
    event.action,
    event.timestamp,
    event.ip,
    event.userAgent,
    JSON.stringify(event.metadata)
  ).run()
}
// src/routes/auth.ts
import { recordAuditEvent } from '../lib/audit-log'
 
app.post('/auth/login', async (c) => {
  const { email, password } = await c.req.json()
  const ip = c.req.header('CF-Connecting-IP')
  const userAgent = c.req.header('User-Agent')
 
  const user = await verifyPassword(email, password)
 
  if (!user) {
    // 登录失败也记录
    await recordAuditEvent(c.env, {
      userId: email,  // 登录失败时还没有 userId
      action: 'user.login.failed',
      timestamp: Date.now(),
      ip,
      userAgent,
      metadata: { reason: 'invalid_password' },
    })
 
    return c.json({ error: 'Invalid credentials' }, 401)
  }
 
  // 登录成功
  await recordAuditEvent(c.env, {
    userId: user.id,
    action: 'user.login',
    timestamp: Date.now(),
    ip,
    userAgent,
  })
 
  const token = generateJWT(user)
  return c.json({ token })
})

4. 中间件统一记录

对于所有需要鉴权的接口,可以在中间件里统一记录用户行为:

// src/middleware/audit-logger.ts
import { Context, Next } from 'hono'
import { recordAuditEvent } from '../lib/audit-log'
 
export async function auditLogger(c: Context, next: Next) {
  const userId = c.get('userId')
  const startedAt = Date.now()
 
  await next()
 
  // 只对需要鉴权的接口记录
  if (!userId) return
 
  const ip = c.req.header('CF-Connecting-IP')
  const userAgent = c.req.header('User-Agent')
 
  // 根据请求生成 action 名称
  const action = generateActionName(c.req.method, c.req.path)
 
  await recordAuditEvent(c.env, {
    userId,
    action,
    timestamp: startedAt,
    ip,
    userAgent,
    metadata: {
      method: c.req.method,
      path: c.req.path,
      status: c.res.status,
      duration: Date.now() - startedAt,
    },
  })
}
 
function generateActionName(method: string, path: string): string {
  // POST /v1/chat/completions → ai.call
  // POST /api/keys → user.api_key.create
  // DELETE /api/keys/:id → user.api_key.delete
 
  if (path.includes('/chat/completions') && method === 'POST') {
    return 'ai.call'
  }
 
  if (path.includes('/api/keys')) {
    if (method === 'POST') return 'user.api_key.create'
    if (method === 'DELETE') return 'user.api_key.delete'
  }
 
  return `${method.toLowerCase()}.${path.replace(/\//g, '.')}`
}
// src/index.ts
app.use('/v1/*', auditLogger)
app.use('/api/*', auditLogger)

5. 安全审计查询

审计日志的主要场景是安全审计,提供查询接口:

// src/routes/admin/audit.ts
app.get('/admin/audit-logs', async (c) => {
  const userId = c.req.query('userId')
  const action = c.req.query('action')
  const startTime = c.req.query('startTime')
  const endTime = c.req.query('endTime')
  const limit = parseInt(c.req.query('limit') || '100')
 
  let query = `
    SELECT * FROM audit_logs
    WHERE 1=1
  `
  const params: any[] = []
 
  if (userId) {
    query += ' AND user_id = ?'
    params.push(userId)
  }
 
  if (action) {
    query += ' AND action = ?'
    params.push(action)
  }
 
  if (startTime) {
    query += ' AND timestamp >= ?'
    params.push(parseInt(startTime))
  }
 
  if (endTime) {
    query += ' AND timestamp <= ?'
    params.push(parseInt(endTime))
  }
 
  query += ' ORDER BY timestamp DESC LIMIT ?'
  params.push(limit)
 
  const logs = await c.env.DB.prepare(query)
    .bind(...params)
    .all()
 
  return c.json(logs)
})

管理员可以查询:

  • 某个用户在最近 7 天的所有操作
  • 某个 API key 的所有使用记录
  • 所有登录失败的记录

6. 敏感操作的特殊处理

某些敏感操作需要额外记录:

// 删除 API key
app.delete('/api/keys/:id', async (c) => {
  const userId = c.get('userId')
  const keyId = c.req.param('id')
 
  // 1. 记录操作前的状态
  const key = await c.env.DB.prepare(`
    SELECT * FROM api_keys WHERE id = ? AND user_id = ?
  `).bind(keyId, userId).first()
 
  if (!key) {
    return c.json({ error: 'Not found' }, 404)
  }
 
  // 2. 执行删除
  await c.env.DB.prepare(`
    DELETE FROM api_keys WHERE id = ?
  `).bind(keyId).run()
 
  // 3. 记录审计日志(包含被删除对象的快照)
  await recordAuditEvent(c.env, {
    userId,
    action: 'user.api_key.delete',
    timestamp: Date.now(),
    ip: c.req.header('CF-Connecting-IP'),
    userAgent: c.req.header('User-Agent'),
    metadata: {
      deletedKeyId: keyId,
      deletedKeyName: key.name,
      deletedKeyPrefix: key.key_prefix,  // 只记录前缀,不记录完整 key
      lastUsedAt: key.last_used_at,
    },
  })
 
  return c.json({ success: true })
})

注意:

  • API key 本身不要记录到审计日志(只记录前缀或 ID)
  • 密码、token 等敏感信息不要记录
  • 如果需要记录请求体,先做脱敏(参考 21.04 节)

7. 用户行为分析

审计日志不仅用于安全审计,还可以用于产品分析:

// 查询用户活跃度
export async function getUserActivity(env: Env, userId: string) {
  const activity = await env.DB.prepare(`
    SELECT
      action,
      COUNT(*) as count,
      MIN(timestamp) as first_seen,
      MAX(timestamp) as last_seen
    FROM audit_logs
    WHERE user_id = ?
      AND timestamp > ?
    GROUP BY action
    ORDER BY count DESC
  `).bind(
    userId,
    Date.now() - 30 * 24 * 60 * 60 * 1000  // 最近 30 天
  ).all()
 
  return activity.results
}
// 查询新用户转化率
export async function getNewUserConversion(env: Env) {
  // 统计注册后 7 天内创建 API key 的用户比例
  const result = await env.DB.prepare(`
    WITH new_users AS (
      SELECT user_id, timestamp as registered_at
      FROM audit_logs
      WHERE action = 'user.register'
        AND timestamp > ?
    ),
    with_api_key AS (
      SELECT DISTINCT n.user_id
      FROM new_users n
      JOIN audit_logs a ON n.user_id = a.user_id
      WHERE a.action = 'user.api_key.create'
        AND a.timestamp < n.registered_at + 7 * 24 * 60 * 60 * 1000
    )
    SELECT
      COUNT(DISTINCT n.user_id) as total_new_users,
      COUNT(DISTINCT w.user_id) as converted_users,
      COUNT(DISTINCT w.user_id) * 100.0 / COUNT(DISTINCT n.user_id) as conversion_rate
    FROM new_users n
    LEFT JOIN with_api_key w ON n.user_id = w.user_id
  `).bind(Date.now() - 90 * 24 * 60 * 60 * 1000).first()
 
  return result
}

8. 日志保留策略

审计日志会快速增长,需要设置保留策略:

// src/cron/cleanup-audit-logs.ts
export default {
  async scheduled(event, env, ctx) {
    // 保留策略:
    // - 普通操作日志:保留 90 天
    // - 安全相关日志(登录、API key):保留 1 年
    // - 付费相关日志:永久保留
 
    const ninetyDaysAgo = Date.now() - 90 * 24 * 60 * 60 * 1000
    const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1000
 
    // 清理普通操作日志(90 天)
    await env.DB.prepare(`
      DELETE FROM audit_logs
      WHERE action NOT IN (
        'user.login', 'user.login.failed', 'user.logout',
        'user.api_key.create', 'user.api_key.delete',
        'payment.*'
      )
      AND timestamp < ?
    `).bind(ninetyDaysAgo).run()
 
    // 清理安全相关日志(1 年)
    await env.DB.prepare(`
      DELETE FROM audit_logs
      WHERE action IN (
        'user.login', 'user.login.failed', 'user.logout',
        'user.api_key.create', 'user.api_key.delete'
      )
      AND timestamp < ?
    `).bind(oneYearAgo).run()
 
    // 付费相关日志永久保留,不清理
  },
}

9. 小结

用户行为日志的关键点:

  1. 记录什么:登录、API key 管理、AI 调用、付费操作等关键行为
  2. 安全审计:记录 IP、userAgent、操作对象,支持追溯
  3. 敏感脱敏:API key、密码等敏感信息不要完整记录
  4. 保留策略:不同类型的日志设置不同的保留期限
  5. 产品分析:审计日志可以用于用户活跃度、转化率分析

用户行为日志是安全合规的基础,也是产品改进的重要依据。下一节讲 Trace 追踪,看看怎么追踪跨服务的请求链路。