日志管理概述
日志是排查问题和监控系统运行状态的关键。Claude Code 可以帮你快速搭建完整的日志系统。
日志级别
常见的日志级别:DEBUG(调试)、INFO(信息)、WARN(警告)、ERROR(错误)、FATAL(致命)。
日志工具封装
Winston 日志库
bash
帮我创建一个统一日志模块,使用 Winston,包含:
- 日志分级输出
- 文件轮转
- 控制台彩色输出
- 自定义格式javascript
// lib/logger/index.js
import winston from "winston";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// 日志格式
const logFormat = winston.format.combine(
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.errors({ stack: true }),
winston.format.printf(({ level, message, timestamp, stack, ...meta }) => {
let msg = `${timestamp} [${level.toUpperCase()}]: ${message}`;
if (Object.keys(meta).length > 0) {
msg += ` ${JSON.stringify(meta)}`;
}
if (stack) {
msg += `\n${stack}`;
}
return msg;
})
);
// 控制台格式(带颜色)
const consoleFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({ format: "HH:mm:ss" }),
winston.format.printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
})
);
// 创建 logger 实例
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || "info",
format: logFormat,
transports: [
// 控制台输出
new winston.transports.Console({
format: consoleFormat,
}),
// 错误日志文件
new winston.transports.File({
filename: path.join(__dirname, "../../logs/error.log"),
level: "error",
maxsize: 10 * 1024 * 1024, // 10MB
maxFiles: 5,
}),
// 所有日志文件
new winston.transports.File({
filename: path.join(__dirname, "../../logs/combined.log"),
maxsize: 10 * 1024 * 1024, // 10MB
maxFiles: 5,
}),
// JSON 格式日志(供 ELK 使用)
new winston.transports.File({
filename: path.join(__dirname, "../../logs/json.log"),
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
maxsize: 10 * 1024 * 1024,
maxFiles: 5,
}),
],
});
// 添加入参方法
logger.logWithMeta = (level, message, meta = {}) => {
logger.log(level, message, { meta });
};
export default logger;日志中间件
javascript
// lib/middleware/logger.js
import logger from "../logger/index.js";
export function requestLogger(req, res, next) {
const start = Date.now();
// 请求完成后的日志
res.on("finish", () => {
const duration = Date.now() - start;
const logData = {
method: req.method,
url: req.originalUrl,
status: res.statusCode,
duration: `${duration}ms`,
ip: req.ip,
userAgent: req.get("user-agent"),
};
if (res.statusCode >= 400) {
logger.warn("Request completed with error", logData);
} else {
logger.info("Request completed", logData);
}
});
next();
}
// 错误日志中间件
export function errorLogger(err, req, res, next) {
logger.error("Unhandled error", {
message: err.message,
stack: err.stack,
url: req.originalUrl,
method: req.method,
body: req.body,
});
next(err);
}日志收集系统
ELK Stack 集成
bash
帮我创建一个 Logstash 配置文件,用于收集应用日志。conf
# logstash/app.conf
input {
# 从文件读取日志
file {
path => "/var/log/myapp/*.log"
start_position => "beginning"
sincedb_path => "/dev/null"
codec => json
}
# 从 TCP 端口接收日志
tcp {
port => 5000
codec => json_lines
}
}
filter {
# 解析 JSON 日志
if [message] =~ /^\{/ {
json {
source => "message"
target => "parsed"
}
}
# 添加时间戳
date {
match => ["[timestamp]", "ISO8601"]
target => "@timestamp"
}
# 添加来源标记
mutate {
add_field => {
"application" => "myapp"
"environment" => "production"
}
}
# 过滤敏感信息
mutate {
gsub => [
"[password]", ".", "*",
"[token]", ".", "*",
"[api_key]", ".", "*"
]
}
}
output {
# 输出到 Elasticsearch
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
# 调试输出
stdout {
codec => rubydebug
}
}Docker 日志收集
yaml
# docker-compose.yml (部分)
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
logstash:
image: logstash:8.12.0
volumes:
- ./logstash/app.conf:/usr/share/logstash/pipeline/app.conf
- app_logs:/var/log/myapp
elasticsearch:
image: elasticsearch:8.12.0
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- es_data:/usr/share/elasticsearch/data
kibana:
image: kibana:8.12.0
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
volumes:
app_logs:
es_data:日志分析
Kibana 查询示例
bash
# 错误日志统计
# 统计每分钟的错误数量
GET app-logs-*/_search
{
"size": 0,
"query": {
"bool": {
"must": [
{ "match": { "level": "ERROR" } }
]
}
},
"aggs": {
"errors_over_time": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "1m"
}
}
}
}bash
# 慢请求分析
GET app-logs-*/_search
{
"size": 10,
"sort": [{ "duration": "desc" }],
"query": {
"range": {
"duration": { "gt": 1000 }
}
}
}结构化日志
日志追踪 ID
javascript
// lib/logger/traced.js
import logger from "./index.js";
import { randomUUID } from "crypto";
// 为每个请求生成唯一追踪 ID
export function generateTraceId() {
return randomUUID();
}
// 添加追踪上下文的日志函数
export function tracedLog(traceId) {
return {
info: (message, meta = {}) => {
logger.info(message, { traceId, ...meta });
},
warn: (message, meta = {}) => {
logger.warn(message, { traceId, ...meta });
},
error: (message, meta = {}) => {
logger.error(message, { traceId, ...meta });
},
debug: (message, meta = {}) => {
logger.debug(message, { traceId, ...meta });
},
};
}
// Express 中间件
export function traceMiddleware(req, res, next) {
const traceId = req.headers["x-trace-id"] || generateTraceId();
req.traceId = traceId;
res.setHeader("X-Trace-ID", traceId);
// 将 traceId 传递到子任务
req.log = tracedLog(traceId);
next();
}使用示例
javascript
// routes/user.js
import express from "express";
const router = express.Router();
router.post("/users", async (req, res) => {
req.log.info("Creating new user", { email: req.body.email });
try {
const user = await userService.create(req.body);
req.log.info("User created successfully", { userId: user.id });
res.json(user);
} catch (error) {
req.log.error("Failed to create user", { error: error.message });
res.status(500).json({ error: "创建用户失败" });
}
});
export default router;日志告警
错误率告警
bash
# Kibana 告警规则
# 当 5 分钟内错误数超过 10 时触发告警
PUT _watcher/watch/error_rate_alert
{
"trigger": {
"schedule": { "interval": "5m" }
},
"input": {
"search": {
"request": {
"indices": ["app-logs-*"],
"body": {
"query": {
"bool": {
"must": [
{ "match": { "level": "ERROR" } },
{ "range": { "@timestamp": { "gte": "now-5m" } } }
]
}
}
}
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total.value": {
"gt": 10
}
}
},
"actions": {
"notify_slack": {
"slack": {
"message": "错误率告警: 过去5分钟有 {{ctx.payload.hits.total.value}} 个错误"
}
}
}
}最佳实践
1. 日志内容规范
javascript
// 好的日志示例
logger.info("用户登录成功", {
userId: user.id,
email: user.email,
ip: req.ip,
userAgent: req.get("user-agent"),
loginTime: new Date().toISOString(),
});
// 避免的日志
logger.info("用户登录"); // 信息不足
logger.info("用户登录成功", { password: user.password }); // 敏感信息2. 性能注意事项
javascript
// 使用异步日志避免阻塞主线程
// Winston 默认是异步的
// 避免在日志中记录大对象
logger.info("请求处理完成", { bigArray: JSON.stringify(largeArray) }); // 不好
// 改用引用或摘要
logger.info("请求处理完成", {
arrayLength: largeArray.length,
totalSize: calculateSize(largeArray),
});3. 日志轮转配置
javascript
// 合理的日志轮转策略
const logConfig = {
errorLog: {
maxSize: "10m",
maxFiles: 30, // 保留 30 个文件,约 10 天
},
accessLog: {
maxSize: "50m",
maxFiles: 14, // 保留 14 个文件,约 2 周
},
jsonLog: {
maxSize: "100m",
maxFiles: 7, // 保留 7 个文件,约一周
},
};日志安全
确保日志中不包含密码、Token、身份证号等敏感信息。使用环境变量或脱敏工具处理。
总结
使用 Claude Code 实现完整的日志系统:
- 搭建统一的日志框架(Winston/Pino)
- 实现请求追踪和错误日志中间件
- 配置 ELK Stack 进行日志收集和分析
- 设置日志告警规则
- 遵循日志最佳实践
合理的日志管理是排查生产环境问题的关键。