api-logging-guidelines

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API Route Logging Guidelines

API路由日志使用指南

Comprehensive guidance for appropriate use of logging in API routes to maintain clean, useful, and performant logs.

本指南全面介绍了API路由中日志的合理使用方式,以保持日志的简洁性、实用性与高性能。

Core Principles

核心原则

1. Avoid Redundant Logging

1. 避免冗余日志

DON'T log what's already logged by middleware:
typescript
// ❌ BAD - Request details are already logged by middleware
logger.info({ tenantId, projectId }, 'Getting project details');
DO rely on request middleware logging:
  • Request/response middleware already logs: method, path, status, duration, path params
  • These logs include tenant/project IDs from the URL path
  • Adding duplicate logs creates noise without value
请勿记录已由中间件记录的内容:
typescript
// ❌ BAD - Request details are already logged by middleware
logger.info({ tenantId, projectId }, 'Getting project details');
请依赖请求中间件的日志记录:
  • 请求/响应中间件已记录:请求方法、路径、状态码、耗时、路径参数
  • 这些日志已包含URL路径中的租户/项目ID
  • 添加重复日志只会产生无价值的噪音

2. Log Level Guidelines

2. 日志级别指南

LevelUse CaseExamples
ERRORUnexpected failures requiring attentionDatabase connection failures, unhandled exceptions, critical service errors
WARNRecoverable issues or concerning patternsRate limiting triggered, deprecated API usage, fallback behavior activated
INFOImportant business events (NOT routine operations)User account created, payment processed, critical configuration changed
DEBUGDetailed diagnostic informationQuery parameters, intermediate calculations, cache hit/miss details
级别适用场景示例
ERROR需要关注的意外故障数据库连接失败、未处理的异常、关键服务错误
WARN可恢复问题或值得关注的模式触发限流、使用已废弃的API、激活降级行为
INFO重要业务事件(非常规操作)用户账户创建、支付完成、关键配置变更
DEBUG详细诊断信息查询参数、中间计算结果、缓存命中/未命中详情

3. What TO Log

3. 应当记录的内容

Log these important events:
typescript
// ✅ GOOD - Important business event
logger.info({
  userId,
  oldPlan: 'free',
  newPlan: 'pro',
  mrr: 99
}, 'User upgraded subscription');

// ✅ GOOD - Error with context
logger.error({
  error,
  tenantId,
  webhookUrl,
  attemptNumber: 3
}, 'Webhook delivery failed after retries');

// ✅ GOOD - Security-relevant event
logger.warn({
  ip: c.req.header('x-forwarded-for'),
  userId,
  attemptedResource
}, 'Unauthorized access attempt');

// ✅ GOOD - Performance issue
logger.warn({
  duration: 5234,
  query,
  resultCount: 10000
}, 'Slow query detected');
请记录以下重要事件:
typescript
// ✅ GOOD - Important business event
logger.info({
  userId,
  oldPlan: 'free',
  newPlan: 'pro',
  mrr: 99
}, 'User upgraded subscription');

// ✅ GOOD - Error with context
logger.error({
  error,
  tenantId,
  webhookUrl,
  attemptNumber: 3
}, 'Webhook delivery failed after retries');

// ✅ GOOD - Security-relevant event
logger.warn({
  ip: c.req.header('x-forwarded-for'),
  userId,
  attemptedResource
}, 'Unauthorized access attempt');

// ✅ GOOD - Performance issue
logger.warn({
  duration: 5234,
  query,
  resultCount: 10000
}, 'Slow query detected');

4. What NOT to Log

4. 应当避免记录的内容

Avoid logging routine operations:
typescript
// ❌ BAD - Routine CRUD operation
logger.info('Getting user by ID');

// ❌ BAD - Already logged by middleware
logger.info(`Processing GET request to /api/users/${id}`);

// ❌ BAD - No actionable information
logger.info('Starting database query');

// ❌ BAD - Sensitive information
logger.info({ password, apiKey }, 'User login attempt');

// ❌ BAD - Overly granular
logger.debug('Entering function processUser');
logger.debug('Exiting function processUser');

请勿记录常规操作:
typescript
// ❌ BAD - Routine CRUD operation
logger.info('Getting user by ID');

// ❌ BAD - Already logged by middleware
logger.info(`Processing GET request to /api/users/${id}`);

// ❌ BAD - No actionable information
logger.info('Starting database query');

// ❌ BAD - Sensitive information
logger.info({ password, apiKey }, 'User login attempt');

// ❌ BAD - Overly granular
logger.debug('Entering function processUser');
logger.debug('Exiting function processUser');

API Route Patterns

API路由模式

Pattern 1: Error Handling

模式1:错误处理

typescript
// ✅ GOOD - Log errors with context
export const route = router.get('/:id', async (c) => {
  try {
    const result = await riskyOperation();
    return c.json(result);
  } catch (error) {
    // Log error with relevant context
    logger.error({
      error,
      userId: c.get('userId'),
      operation: 'riskyOperation',
      // Include any relevant debugging context
      requestId: c.get('requestId')
    }, 'Operation failed');

    // Return generic error to client (don't leak internals)
    return c.json({ error: 'Internal server error' }, 500);
  }
});
typescript
// ✅ GOOD - Log errors with context
export const route = router.get('/:id', async (c) => {
  try {
    const result = await riskyOperation();
    return c.json(result);
  } catch (error) {
    // Log error with relevant context
    logger.error({
      error,
      userId: c.get('userId'),
      operation: 'riskyOperation',
      // Include any relevant debugging context
      requestId: c.get('requestId')
    }, 'Operation failed');

    // Return generic error to client (don't leak internals)
    return c.json({ error: 'Internal server error' }, 500);
  }
});

Pattern 2: Business Events

模式2:业务事件

typescript
// ✅ GOOD - Log significant business events
export const route = router.post('/subscription/upgrade', async (c) => {
  const { planId } = await c.req.json();

  const result = await upgradeSubscription(userId, planId);

  // This is worth logging - it's a significant business event
  logger.info({
    userId,
    oldPlan: result.previousPlan,
    newPlan: result.newPlan,
    mrr: result.mrr,
    timestamp: new Date().toISOString()
  }, 'Subscription upgraded');

  return c.json(result);
});
typescript
// ✅ GOOD - Log significant business events
export const route = router.post('/subscription/upgrade', async (c) => {
  const { planId } = await c.req.json();

  const result = await upgradeSubscription(userId, planId);

  // This is worth logging - it's a significant business event
  logger.info({
    userId,
    oldPlan: result.previousPlan,
    newPlan: result.newPlan,
    mrr: result.mrr,
    timestamp: new Date().toISOString()
  }, 'Subscription upgraded');

  return c.json(result);
});

Pattern 3: Performance Monitoring

模式3:性能监控

typescript
// ✅ GOOD - Log performance issues
export const route = router.get('/search', async (c) => {
  const start = Date.now();
  const results = await performSearch(query);
  const duration = Date.now() - start;

  // Only log if performance is concerning
  if (duration > 1000) {
    logger.warn({
      duration,
      query,
      resultCount: results.length,
      cached: false
    }, 'Slow search query');
  }

  return c.json(results);
});
typescript
// ✅ GOOD - Log performance issues
export const route = router.get('/search', async (c) => {
  const start = Date.now();
  const results = await performSearch(query);
  const duration = Date.now() - start;

  // Only log if performance is concerning
  if (duration > 1000) {
    logger.warn({
      duration,
      query,
      resultCount: results.length,
      cached: false
    }, 'Slow search query');
  }

  return c.json(results);
});

Pattern 4: Security Events

模式4:安全事件

typescript
// ✅ GOOD - Log security-relevant events
export const route = router.post('/api/admin/*', async (c) => {
  const hasPermission = await checkPermission(userId, resource);

  if (!hasPermission) {
    // Log unauthorized access attempts
    logger.warn({
      userId,
      resource,
      ip: c.req.header('x-forwarded-for'),
      userAgent: c.req.header('user-agent')
    }, 'Unauthorized access attempt');

    return c.json({ error: 'Forbidden' }, 403);
  }

  // Proceed with authorized request...
});

typescript
// ✅ GOOD - Log security-relevant events
export const route = router.post('/api/admin/*', async (c) => {
  const hasPermission = await checkPermission(userId, resource);

  if (!hasPermission) {
    // Log unauthorized access attempts
    logger.warn({
      userId,
      resource,
      ip: c.req.header('x-forwarded-for'),
      userAgent: c.req.header('user-agent')
    }, 'Unauthorized access attempt');

    return c.json({ error: 'Forbidden' }, 403);
  }

  // Proceed with authorized request...
});

Environment-Specific Guidelines

环境特定指南

Development

开发环境

typescript
// More verbose logging acceptable in development
if (process.env.NODE_ENV === 'development') {
  logger.debug({ params, body }, 'Request details');
}
typescript
// More verbose logging acceptable in development
if (process.env.NODE_ENV === 'development') {
  logger.debug({ params, body }, 'Request details');
}

Production

生产环境

  • Minimize INFO level logs to important events only
  • Never log sensitive data (passwords, tokens, keys, PII)
  • Use structured logging for better searchability
  • Include correlation IDs for tracing requests

  • 仅将INFO级别日志保留给重要事件
  • 绝不记录敏感数据(密码、令牌、密钥、个人可识别信息)
  • 使用结构化日志以提升可搜索性
  • 包含关联ID以追踪请求

Migration Strategy

迁移策略

When refactoring existing verbose logging:
  1. Identify redundant logs: Remove logs that duplicate middleware logging
  2. Downgrade routine operations: Move routine operations from INFO to DEBUG
  3. Enhance error logs: Add more context to error logs
  4. Add business event logs: Ensure important business events are logged
  5. Review log levels: Ensure each log uses the appropriate level
重构现有冗余日志时:
  1. 识别冗余日志:移除与中间件日志重复的记录
  2. 降级常规操作日志:将常规操作从INFO级别调整为DEBUG级别
  3. 增强错误日志:为错误日志添加更多上下文信息
  4. 添加业务事件日志:确保重要业务事件被记录
  5. 审核日志级别:确保每条日志使用正确的级别

Before:

重构前:

typescript
router.get('/:id', async (c) => {
  const { id } = c.req.param();
  logger.info({ id }, 'Getting item by ID');  // Redundant

  const item = await getItem(id);
  logger.info({ item }, 'Retrieved item');     // Too verbose

  return c.json(item);
});
typescript
router.get('/:id', async (c) => {
  const { id } = c.req.param();
  logger.info({ id }, 'Getting item by ID');  // Redundant

  const item = await getItem(id);
  logger.info({ item }, 'Retrieved item');     // Too verbose

  return c.json(item);
});

After:

重构后:

typescript
router.get('/:id', async (c) => {
  const { id } = c.req.param();

  try {
    const item = await getItem(id);
    // No logging needed - routine successful operation
    return c.json(item);
  } catch (error) {
    // Only log errors
    logger.error({ error, id }, 'Failed to retrieve item');
    return c.json({ error: 'Item not found' }, 404);
  }
});

typescript
router.get('/:id', async (c) => {
  const { id } = c.req.param();

  try {
    const item = await getItem(id);
    // No logging needed - routine successful operation
    return c.json(item);
  } catch (error) {
    // Only log errors
    logger.error({ error, id }, 'Failed to retrieve item');
    return c.json({ error: 'Item not found' }, 404);
  }
});

Debugging Without Verbose Logs

无冗余日志的调试策略

Instead of verbose logging, use these strategies:
  1. Use debug mode selectively: Enable DEBUG level for specific modules when troubleshooting
  2. Use tracing: OpenTelemetry/Jaeger for distributed tracing
  3. Use metrics: Prometheus/StatsD for performance metrics
  4. Use error tracking: Sentry/Rollbar for error aggregation
  5. Use feature flags: Enable verbose logging for specific users/requests when debugging

替代冗余日志的调试方法:
  1. 选择性启用调试模式:排查问题时仅为特定模块启用DEBUG级别日志
  2. 使用追踪工具:OpenTelemetry/Jaeger用于分布式追踪
  3. 使用指标工具:Prometheus/StatsD用于性能指标监控
  4. 使用错误追踪工具:Sentry/Rollbar用于错误聚合
  5. 使用功能标志:调试时为特定用户/请求启用详细日志

Summary Checklist

总结检查清单

Before adding a log statement, ask:
  • Is this already logged by middleware? (method, path, status, params)
  • Is this a significant business event or just routine operation?
  • Does this log provide actionable information?
  • Am I using the correct log level?
  • Am I including helpful context without sensitive data?
  • Will this log be useful in production or just create noise?
Remember: Good logging is about signal, not volume. Every log should serve a purpose.
添加日志语句前,请确认:
  • 该内容是否已由中间件记录?(方法、路径、状态、参数)
  • 这是重要业务事件还是常规操作?
  • 该日志是否提供可操作的信息?
  • 是否使用了正确的日志级别?
  • 是否包含了有用的上下文且未涉及敏感数据?
  • 该日志在生产环境中是否有用,还是只会产生噪音?
谨记:优秀的日志关注的是有效信息,而非数量。每条日志都应有其存在的意义。