跳到主要内容

日志与监控体系

问题

服务端如何设计日志和监控体系?前端需要了解哪些?

面试速答版

服务端日志和监控体系怎么设计? 核心是「可观测性三支柱」:日志(Logging)、指标(Metrics)、链路追踪(Tracing):

  • 日志Winston/Pino 输出结构化 JSON(带 traceIduserId),通过 Filebeat → Logstash → Elasticsearch → Kibana(ELK)或 Loki + Grafana 检索。
  • 指标Prometheus/metrics 端点(QPS、P99、错误率、CPU/内存),Grafana 出图,Alertmanager 告警。
  • 链路追踪OpenTelemetry 标准 + Jaeger/Zipkin,每次请求生成 traceId 串起所有服务,定位跨服务慢调用。
  • 日志分级error/warn/info/debug 严格区分,生产用 info,错误日志单独落文件触发告警。

前端工程师为什么也要懂?

  • 排查线上问题靠 traceId 串联前后端日志,前端上报和后端日志要对齐。
  • BFF/Node 服务出错时,自己得会看 Kibana、Grafana,不能凡事找运维。
  • 前端监控(Sentry、自建 SDK)的设计思路和服务端监控是相通的。

答案

监控体系全景

日志分级

logger.ts
import winston from '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(),
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});

// 日志级别(从高到低)
// error: 错误,需要立即处理
// warn: 警告,可能有问题
// info: 信息,正常业务流程
// debug: 调试,开发阶段使用
级别用途示例
error系统错误数据库连接失败、未捕获异常
warn潜在问题接口超时重试、缓存未命中率过高
info业务流程用户登录、订单创建、支付完成
debug调试信息请求参数、SQL 查询、中间状态

结构化日志

structured-log.ts
// ✅ 结构化日志(易于搜索和分析)
logger.info('Order created', {
orderId: 'ORD-001',
userId: 'USR-123',
amount: 99.99,
duration: 45, // 处理耗时 ms
traceId: 'abc-def-123',
});

// ❌ 非结构化日志(难以检索)
logger.info(`User USR-123 created order ORD-001, amount: 99.99`);

请求链路追踪

trace-middleware.ts
import { v4 as uuid } from 'uuid';

// 中间件:为每个请求添加 traceId
const traceMiddleware = (req: Request, res: Response, next: NextFunction) => {
const traceId = req.headers['x-trace-id'] as string || uuid();
req.traceId = traceId;
res.setHeader('x-trace-id', traceId);

// 记录请求日志
logger.info('Request received', {
traceId,
method: req.method,
path: req.path,
userAgent: req.headers['user-agent'],
ip: req.ip,
});

// 记录响应日志
const start = Date.now();
res.on('finish', () => {
logger.info('Response sent', {
traceId,
statusCode: res.statusCode,
duration: Date.now() - start,
});
});

next();
};

日志全生命周期:从产生到归档

面试时如果只说"用 Winston 记日志"是不够的——面试官想听的是从应用产生日志到最终归档的完整链路。下面按阶段讲清楚。

① 日志产出:写到哪里?

输出方式说明适用场景
stdout/stderr直接打到标准输出,由容器运行时(Docker/K8s)接管容器化部署推荐,符合 12-Factor App 原则
本地文件写入 /var/log/app/*.log,需配置轮转传统虚拟机部署
直接推送应用内直接调 HTTP/TCP 推送到日志服务边缘场景、Serverless
容器化最佳实践

在 Docker/K8s 环境中,应用只管往 stdout 输出,不要自己管理文件。容器运行时会自动把 stdout 写入 /var/lib/docker/containers/<id>/<id>-json.log,再由日志采集器统一收集。好处是应用和日志基础设施完全解耦。

NestJS 生产配置示例
// 生产环境输出 JSON 到 stdout,由 Filebeat/Fluentd 采集
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(), // 必须是 JSON,不要 pretty-print
),
transports: [
new winston.transports.Console(), // stdout
],
});

② 日志采集:谁来收集?

应用产出日志后,需要一个采集 Agent 把日志从各节点收集起来,送到下一步处理。

采集器特点典型搭配
FilebeatElastic 官方,Go 写,轻量,适合发送到 Logstash/ESELK 全家桶
Fluentd / Fluent BitCNCF 项目,插件丰富,K8s 标配K8s + Loki/ES/S3
VectorRust 写,高性能,灵活路由替代 Logstash + Filebeat
PromtailGrafana 官方,专门采集发送到 LokiLoki + Grafana

采集器的工作方式

  1. 以 DaemonSet(K8s)或 Sidecar 方式部署在每个节点
  2. Tail 监听日志文件或 Docker stdout
  3. 添加元信息(Pod 名、Namespace、Node 等)
  4. 缓冲 + 批量发送到下游,支持断点续传(offset 记录)
filebeat.yml 核心配置
filebeat.inputs:
- type: container
paths:
- /var/lib/docker/containers/*/*.log
# 自动解析 Docker JSON 格式
json.keys_under_root: true
json.add_error_key: true

processors:
- add_kubernetes_metadata: ~ # 自动关联 K8s Pod 信息

output.logstash:
hosts: ["logstash:5044"]

③ 日志处理:清洗与转换

采集器收到原始日志后,通常需要经过一层处理管道做清洗、解析、丰富:

  • 解析:从非结构化文本中提取字段(Grok 正则、JSON 解析)
  • 丰富:补充 GeoIP、用户信息、环境标签
  • 脱敏:手机号/身份证/Token 等敏感字段替换为 ***
  • 过滤:丢弃健康检查等无意义日志
  • 路由:错误日志走 ES(需要全文检索),访问日志走 S3(只做归档)
logstash pipeline 示例
input {
beats { port => 5044 }
}

filter {
# 解析 JSON 结构
json { source => "message" }

# 脱敏:替换手机号
mutate {
gsub => ["phone", "\d{4}$", "****"]
}

# 丢弃健康检查日志
if [path] == "/health" {
drop {}
}

# GeoIP 丰富
geoip { source => "clientIp" }
}

output {
# 错误日志 → ES 热节点
if [level] == "error" {
elasticsearch {
hosts => ["es-hot:9200"]
index => "error-logs-%{+YYYY.MM.dd}"
}
}
# 其他日志 → ES 温节点
else {
elasticsearch {
hosts => ["es-warm:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}
}

④ 日志存储:存在哪里?

存储方案优势劣势适用场景
Elasticsearch全文搜索强、聚合能力好资源消耗大、成本高需要按内容搜索、复杂查询
Grafana Loki轻量、只索引标签不索引内容、成本低不适合复杂全文搜索中小团队、已用 Grafana
S3/OSS + Athena极低成本、无限容量查询慢、不支持实时归档、合规审计
ClickHouse列式存储、聚合超快运维复杂超大日志量、需要统计分析
热温冷分层存储(ILM)

Elasticsearch 支持 Index Lifecycle Management,按数据新鲜度自动迁移:

  • 热节点(SSD,7天内):最近的日志,查询最频繁
  • 温节点(HDD,7-30天):偶尔查询,降低成本
  • 冷节点/冻结(30-90天):几乎不查,极低成本
  • 删除(>90天):超出保留期的日志自动删除
ES ILM 策略示例
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": { "max_age": "1d", "max_size": "50gb" }
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 }
}
},
"cold": {
"min_age": "30d",
"actions": {
"searchable_snapshot": { "snapshot_repository": "s3-repo" }
}
},
"delete": {
"min_age": "90d",
"actions": { "delete": {} }
}
}
}
}

⑤ 日志消费:怎么查?怎么用?

日志存下来之后,消费方式有几种:

消费方式工具场景
实时搜索Kibana Discover / Grafana Explore线上问题排查:搜 traceId、userId、错误关键词
仪表盘Kibana Dashboard / Grafana Dashboard日常巡检:错误趋势、接口耗时分布
告警ElastAlert / Grafana Alerting自动告警:5分钟内错误日志 >100 条 → 钉钉/Slack 通知
分析ES Aggregation / ClickHouse SQL业务分析:Top 10 慢接口、用户行为路径

实战:线上问题排查流程

⑥ 日志归档与保留策略

为什么需要归档?

  • 合规要求(金融、医疗行业日志需保留 3-7 年)
  • 成本控制(ES 存储成本是 S3 的 10-50 倍)
  • 容量管理(日志增长极快,不清理会打爆磁盘)

归档方案

阶段保留时间存储位置是否可查
实时0-7 天ES 热节点 / Loki毫秒级查询
近线7-30 天ES 温节点 / S3 Standard秒级查询
归档30-365 天S3 Glacier / OSS 归档需要恢复(分钟~小时)
删除>365 天自动清理不可查

自动化归档流程

  1. ES ILM 或 Loki Compactor 按策略自动迁移索引
  2. 到期的索引通过 Snapshot API 备份到 S3/OSS
  3. 超出保留期的 Snapshot 由 Curator 或 S3 Lifecycle Rule 自动删除
  4. 关键业务日志(支付、审计)走单独通道,保留周期更长
日志保留的成本估算

假设日均日志量 100GB:

  • ES 热节点存 7 天:约 700GB SSD(≈ $200/月)
  • ES 温节点存 30 天:约 3TB HDD(≈ $100/月)
  • S3 标准存 90 天:约 9TB(≈ $200/月)
  • S3 Glacier 存 365 天:约 36TB(≈ $150/月)

对比全量存 ES 365 天(≈ 36TB SSD/HDD ≈ $3000+/月),分层后成本降低 80%+。

完整的 ELK 数据流

把上面的步骤串起来,一张图看完整条链路:


常见面试问题

Q1: 前端错误发生后,如何通过日志快速定位问题?

答案

  1. 前端上报时携带 traceId(从响应头获取)
  2. traceId 在服务端日志系统中搜索完整链路
  3. 查看链路上每个服务的日志:请求参数 → 处理过程 → 返回结果

Q2: ELK 是什么?

答案

  • Elasticsearch:日志存储和搜索引擎
  • Logstash:日志收集和处理管道
  • Kibana:可视化查询和仪表盘

日志采集流程:应用 → Filebeat → Logstash → Elasticsearch → Kibana

Q3: 什么样的日志应该记录?什么不该记录?

答案

应该记录:请求入口和出口、关键业务操作、错误堆栈、性能指标 不该记录:用户密码、信用卡号、Token 明文等敏感信息

Q4: Prometheus 和 ELK 的区别?

答案

  • Prometheus + Grafana:适合指标监控(CPU、内存、QPS、响应时间),时序数据
  • ELK:适合日志分析(搜索具体请求、错误排查),全文搜索

两者互补,通常一起使用。

Q5: 日志量太大怎么办?

答案

从产出到存储到归档的每个环节都可以优化:

产出侧减量

  1. 日志分级:生产环境只记 info 及以上,debug 日志关闭
  2. 采样:正常请求按比例采样(如 10%),错误请求全量记录
  3. 去噪:在采集器或 Logstash 层丢弃健康检查、静态资源访问等低价值日志

存储侧降本: 4. 热温冷分层(ILM):7 天内热存储(SSD),30 天温存储(HDD),超期归档到 S3 Glacier 5. 索引优化:关闭不需要搜索的字段索引、减少副本数、使用 best_compression 6. 按需选型:不需要全文搜索的日志直接存 Loki 或 S3,成本降 10 倍

写入侧保障: 7. 异步写入:日志 SDK 内部用缓冲队列,批量异步发送,不阻塞业务主流程 8. 背压控制:采集器满载时丢弃低优先级日志而非阻塞应用

Q6: 如何设计一个完整的日志系统?

答案

一个生产级日志系统需要覆盖「产出 → 采集 → 处理 → 存储 → 消费 → 归档」六个环节,面试时按以下框架回答:

1. 日志产出规范

  • 使用 Winston/Pino 输出结构化 JSON,每条日志必须包含:timestampleveltraceIdservicemessage、业务字段
  • 遵循 12-Factor App:应用只写 stdout,不管文件
  • 日志分 4 级:error(需告警)、warn(需关注)、info(业务流水)、debug(仅开发)
  • 敏感信息(密码、Token、手机号)在产出时即脱敏

2. 日志采集

  • 容器环境用 Filebeat/Fluent Bit 以 DaemonSet 部署,Tail 容器 stdout 文件
  • 采集器自动注入元数据:Pod 名、Namespace、Node、Container ID
  • 支持断点续传(记录 offset),网络中断后不丢日志

3. 日志处理(管道层)

  • LogstashVector 做中间处理:
    • 解析:JSON 解析、Grok 正则提取
    • 丰富:补 GeoIP、关联用户信息
    • 过滤:丢弃健康检查、静态资源等噪声
    • 路由:error 走 ES 热节点,access log 走 S3

4. 日志存储

  • 热数据(7 天):Elasticsearch 热节点 / Loki,支持毫秒级全文检索
  • 温数据(7-30 天):ES 温节点,降低副本数和分片
  • 冷数据(30-365 天):S3 Standard / OSS,按需恢复查询
  • 使用 ES ILM(Index Lifecycle Management) 自动在各层间迁移

5. 日志消费

  • 搜索排查:Kibana / Grafana,用 traceId 串联前后端全链路
  • 仪表盘:监控错误趋势、接口 P99、QPS 等
  • 告警:ElastAlert / Grafana Alerting,规则示例:5 分钟内 error 数 > 100 → 通知 on-call
  • 分析:慢接口 Top N、错误码分布、用户行为漏斗

6. 归档与保留

  • 制定保留策略:常规日志 90 天、审计日志 3 年、支付日志按合规要求
  • 到期索引通过 Snapshot API 归档到 S3 Glacier
  • S3 Lifecycle Rule 自动清理超期归档
  • 定期验证归档可恢复性

一句话总结:应用产 JSON → Filebeat 采集 → Logstash 清洗路由 → ES 热温冷分层存储 → Kibana 查 + Alertmanager 报警 → ILM 自动归档到 S3 → Lifecycle 过期删除。

相关链接