日志管理 #

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