api-logging-guidelines
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAPI 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. 日志级别指南
| Level | Use Case | Examples |
|---|---|---|
| ERROR | Unexpected failures requiring attention | Database connection failures, unhandled exceptions, critical service errors |
| WARN | Recoverable issues or concerning patterns | Rate limiting triggered, deprecated API usage, fallback behavior activated |
| INFO | Important business events (NOT routine operations) | User account created, payment processed, critical configuration changed |
| DEBUG | Detailed diagnostic information | Query 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:
- Identify redundant logs: Remove logs that duplicate middleware logging
- Downgrade routine operations: Move routine operations from INFO to DEBUG
- Enhance error logs: Add more context to error logs
- Add business event logs: Ensure important business events are logged
- Review log levels: Ensure each log uses the appropriate level
重构现有冗余日志时:
- 识别冗余日志:移除与中间件日志重复的记录
- 降级常规操作日志:将常规操作从INFO级别调整为DEBUG级别
- 增强错误日志:为错误日志添加更多上下文信息
- 添加业务事件日志:确保重要业务事件被记录
- 审核日志级别:确保每条日志使用正确的级别
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:
- Use debug mode selectively: Enable DEBUG level for specific modules when troubleshooting
- Use tracing: OpenTelemetry/Jaeger for distributed tracing
- Use metrics: Prometheus/StatsD for performance metrics
- Use error tracking: Sentry/Rollbar for error aggregation
- Use feature flags: Enable verbose logging for specific users/requests when debugging
替代冗余日志的调试方法:
- 选择性启用调试模式:排查问题时仅为特定模块启用DEBUG级别日志
- 使用追踪工具:OpenTelemetry/Jaeger用于分布式追踪
- 使用指标工具:Prometheus/StatsD用于性能指标监控
- 使用错误追踪工具:Sentry/Rollbar用于错误聚合
- 使用功能标志:调试时为特定用户/请求启用详细日志
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.
添加日志语句前,请确认:
- 该内容是否已由中间件记录?(方法、路径、状态、参数)
- 这是重要业务事件还是常规操作?
- 该日志是否提供可操作的信息?
- 是否使用了正确的日志级别?
- 是否包含了有用的上下文且未涉及敏感数据?
- 该日志在生产环境中是否有用,还是只会产生噪音?
谨记:优秀的日志关注的是有效信息,而非数量。每条日志都应有其存在的意义。