日志管理 #
Logplex 概述 #
Logplex 是 Heroku 的日志聚合和路由系统,负责收集、聚合和分发所有 Dyno 的日志。
架构图 #
text
┌─────────────────────────────────────────────────────┐
│ Logplex 架构 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ web.1 │ │ web.2 │ │ worker.1│ │
│ │ stdout │ │ stdout │ │ stdout │ │
│ │ stderr │ │ stderr │ │ stderr │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └───────────┼───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Logplex │ │
│ │ 日志聚合 │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ CLI │ │ Add-ons │ │ Syslog │ │
│ │ Output │ │Papertrail│ │ Drain │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────┘
日志格式 #
标准格式 #
text
timestamp source[dyno]: message
示例 #
text
2024-01-15T10:30:00.123456+00:00 app[web.1]: Server started on port 3000
2024-01-15T10:30:01.234567+00:00 heroku[web.1]: State changed from starting to up
2024-01-15T10:30:02.345678+00:00 heroku[router]: at=info method=GET path="/" host=myapp.herokuapp.com request_id=abc123 fwd="1.2.3.4" dyno=web.1 connect=0ms service=10ms status=200 bytes=1234
日志源 #
| 源 | 说明 | 示例 |
|---|---|---|
| app | 应用输出 | app[web.1]: … |
| heroku | 系统消息 | heroku[web.1]: … |
| router | HTTP 路由 | heroku[router]: … |
查看日志 #
基本命令 #
bash
# 查看实时日志
heroku logs --tail
# 查看最近 100 行
heroku logs -n 100
# 查看最近 500 行
heroku logs -n 500
# 简写
heroku logs -t # 等同于 --tail
过滤日志 #
bash
# 按来源过滤
heroku logs --source app
heroku logs --source heroku
heroku logs --source router
# 按 Dyno 过滤
heroku logs --dyno web.1
heroku logs --dyno worker.1
# 组合过滤
heroku logs --source app --dyno web.1
# 使用 grep 过滤
heroku logs | grep -i error
heroku logs | grep "POST /api"
Router 日志详解 #
text
heroku[router]: at=info method=GET path="/api/users" host=myapp.herokuapp.com request_id=abc-123-def fwd="1.2.3.4" dyno=web.1 connect=0ms service=15ms status=200 bytes=1234 protocol=https
| 字段 | 说明 |
|---|---|
| at | 日志级别 (info, error, warn) |
| method | HTTP 方法 |
| path | 请求路径 |
| host | 主机名 |
| request_id | 请求唯一 ID |
| fwd | 客户端 IP |
| dyno | 处理请求的 Dyno |
| connect | 建立连接时间 |
| service | 处理请求时间 |
| status | HTTP 状态码 |
| bytes | 响应大小 |
| protocol | 协议 (http/https) |
应用日志输出 #
Node.js 日志 #
javascript
// 基本日志
console.log('Server started');
console.error('Error occurred');
// 结构化日志
console.log(JSON.stringify({
level: 'info',
message: 'User logged in',
userId: 123,
timestamp: new Date().toISOString()
}));
// 使用 winston 日志库
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [new winston.transports.Console()]
});
logger.info('Server started', { port: 3000 });
logger.error('Database connection failed', { error: err.message });
Python 日志 #
python
import logging
import json
from datetime import datetime
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 基本日志
logger.info('Server started')
logger.error('Error occurred')
# 结构化日志
def log_json(level, message, **kwargs):
log_entry = {
'level': level,
'message': message,
'timestamp': datetime.utcnow().isoformat(),
**kwargs
}
print(json.dumps(log_entry))
log_json('info', 'User logged in', user_id=123)
Ruby 日志 #
ruby
# Rails 日志配置
# config/environments/production.rb
config.log_level = :info
config.log_tags = [:request_id]
# 基本日志
Rails.logger.info "Server started"
Rails.logger.error "Error occurred: #{error.message}"
# 结构化日志
Rails.logger.info({
level: 'info',
message: 'User logged in',
user_id: 123
}.to_json)
日志级别 #
标准级别 #
javascript
// 日志级别从低到高
const levels = {
debug: 0, // 调试信息
info: 1, // 一般信息
notice: 2, // 重要信息
warning: 3, // 警告
error: 4, // 错误
critical: 5, // 严重错误
alert: 6, // 需要立即处理
emergency: 7 // 系统不可用
};
设置日志级别 #
bash
# 设置环境变量控制日志级别
heroku config:set LOG_LEVEL=info
# 生产环境建议
heroku config:set LOG_LEVEL=warn
# 开发环境
heroku config:set LOG_LEVEL=debug
日志 Drain #
什么是日志 Drain? #
日志 Drain 允许将日志发送到外部服务进行长期存储和分析。
添加 Syslog Drain #
bash
# 添加 Syslog Drain
heroku drains:add syslog://logs.example.com:514
# 添加带 Token 的 Drain
heroku drains:add syslog://token@logs.example.com:514
# HTTPS Drain
heroku drains:add https://logs.example.com/heroku
管理 Drain #
bash
# 列出所有 Drain
heroku drains
# 输出示例
# === myapp Drains
# syslog://logs.example.com:514 (token-abc123)
# 删除 Drain
heroku drains:remove syslog://logs.example.com:514
日志 Add-ons #
Papertrail #
bash
# 添加 Papertrail
heroku addons:create papertrail:choklad
# 打开 Papertrail 控制台
heroku addons:open papertrail
# 查看日志
# Papertrail 提供实时日志搜索和归档功能
Logentries #
bash
# 添加 Logentries
heroku addons:create logentries:tryit
# 打开控制台
heroku addons:open logentries
Sumo Logic #
bash
# 添加 Sumo Logic
heroku addons:create sumologic:free
# 打开控制台
heroku addons:open sumologic
Coralogix #
bash
# 添加 Coralogix
heroku addons:create coralogix:free
# 打开控制台
heroku addons:open coralogix
日志归档 #
为什么需要归档? #
Heroku 默认只保留最近的日志(约 1500 行),长期存储需要使用日志 Drain 或 Add-ons。
归档方案 #
text
┌─────────────────────────────────────────────────────┐
│ 日志归档方案 │
├─────────────────────────────────────────────────────┤
│ │
│ 方案一:日志 Add-ons │
│ ├── Papertrail(推荐) │
│ ├── Logentries │
│ └── Sumo Logic │
│ │
│ 方案二:Syslog Drain │
│ ├── 自建 Syslog 服务器 │
│ └── 云日志服务 │
│ │
│ 方案三:对象存储 │
│ ├── AWS S3 │
│ └── Google Cloud Storage │
│ │
└─────────────────────────────────────────────────────┘
日志分析 #
错误统计 #
bash
# 统计错误数量
heroku logs -n 1000 | grep -c "Error"
# 统计 HTTP 错误
heroku logs -n 1000 | grep "status=5"
# 查找特定错误
heroku logs | grep "ReferenceError"
性能分析 #
bash
# 查找慢请求(> 1秒)
heroku logs | grep "service=" | awk -F'service=' '{print $2}' | awk -F'ms' '{if($1>1000) print}'
# 统计请求时间分布
heroku logs -n 1000 --source router | grep "service=" | awk -F'service=' '{print $2}' | awk -F'ms' '{print $1}' | sort -n
访问分析 #
bash
# 统计访问量
heroku logs -n 1000 --source router | wc -l
# 按 URL 统计
heroku logs -n 1000 --source router | grep -oP 'path=\K[^ ]+' | sort | uniq -c | sort -rn
# 按 IP 统计
heroku logs -n 1000 --source router | grep -oP 'fwd="\K[^"]+' | sort | uniq -c | sort -rn
日志最佳实践 #
1. 结构化日志 #
javascript
// 好的做法:结构化日志
logger.info({
event: 'user_login',
userId: 123,
ip: req.ip,
userAgent: req.headers['user-agent']
});
// 避免:非结构化日志
console.log('User 123 logged in from ' + req.ip);
2. 包含请求 ID #
javascript
// 使用 request-id 追踪请求
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] || uuid.v4();
res.setHeader('X-Request-ID', req.id);
next();
});
// 日志中包含请求 ID
logger.info({
requestId: req.id,
message: 'Processing request'
});
3. 敏感信息处理 #
javascript
// 过滤敏感信息
const filterSensitive = (obj) => {
const sensitive = ['password', 'token', 'secret', 'credit_card'];
const filtered = { ...obj };
sensitive.forEach(key => {
if (filtered[key]) filtered[key] = '[FILTERED]';
});
return filtered;
};
logger.info('User data', filterSensitive(userData));
4. 合理的日志级别 #
javascript
// 开发环境:详细日志
if (process.env.NODE_ENV === 'development') {
logger.level = 'debug';
}
// 生产环境:精简日志
if (process.env.NODE_ENV === 'production') {
logger.level = 'info';
}
// 根据场景选择级别
logger.debug('Detailed debug info'); // 仅开发环境
logger.info('User action logged'); // 正常日志
logger.warn('Rate limit approaching'); // 警告
logger.error('Database error', err); // 错误
故障排查 #
日志丢失 #
bash
# Heroku 默认只保留最近 1500 行
# 解决方案:使用日志 Add-ons 或 Drain
# 检查 Drain 状态
heroku drains
# 检查 Add-ons
heroku addons
日志延迟 #
bash
# Logplex 可能有几秒延迟
# 如果延迟严重,检查:
# 1. 日志量是否过大
# 2. Drain 服务是否正常
日志格式问题 #
javascript
// 确保输出到 stdout/stderr
console.log('Info message'); // stdout
console.error('Error message'); // stderr
// 不要输出到文件
// 错误做法
fs.writeFileSync('app.log', message);
下一步 #
日志管理掌握后,接下来学习 Git 部署 了解更多部署方式!
最后更新:2026-03-28