先定保留策略,再选工具
日志体系的成本几乎全部由保留策略决定,工具只是执行者。实际使用中绝大多数查询发生在最近 7 天内,30 天以前的日志主要用于合规与追溯。按这个分布做冷热分层,存储成本能降一个数量级,而排障体验几乎没有损失。
| 层级 | 存储介质 | 保留期 | 查询要求 | 典型用途 |
|---|---|---|---|---|
| 热 | 本地 SSD 或高性能块存储 | 7~15 天 | 秒级返回 | 在线排障、实时看板 |
| 温 | 低配存储或压缩索引 | 30~90 天 | 分钟级可接受 | 周度复盘、审计抽查 |
| 冷 | 对象存储归档 | 180 天至数年 | 分钟到小时级取回 | 合规留证、事故复盘 |
分层的关键是查询入口统一。冷数据取回慢可以接受,但要求运维记三套工具、三种语法就是设计失败:没人会在凌晨三点记得住第三套语法。
结构化日志为什么比纯文本重要
纯文本日志靠正则提取字段,正则随日志格式微调就崩,而且无法做聚合统计。JSON 结构化日志把时间、级别、服务名、trace_id、用户标识、耗时都变成可索引的字段,查询从「捞字符串」变成「按字段过滤加聚合」。
一条日志如果没有 trace_id,在分布式系统里只能算半个证据——你知道出错了,但拼不出这一次请求经过了哪些服务。
实践上要求所有服务至少输出固定字段:timestamp、level、service、trace_id、span_id、message,业务数据放 extras。级别只用 debug、info、warn、error 四档,生产环境默认 info,开启 debug 必须带自动到期,否则某个人忘了关,磁盘就会替你记住。
ELK 与 Loki:两种设计哲学
ELK 走全文索引,写入时把日志拆词建成倒排索引,换来任意关键字的高速检索;Loki 只索引标签,正文压缩后原样存进对象存储,查询时按标签范围扫描。前者贵在索引,后者贵在扫描。
| 对比项 | ELK(Elasticsearch + Logstash + Kibana) | Loki(+ Promtail + Grafana) |
|---|---|---|
| 资源占用 | JVM 堆需求大,单节点常以 8GB 内存起步,写入对磁盘 IO 敏感 | 内存占用小,单机可跑,主要依赖对象存储 |
| 查询语言 | KQL / Lucene / DSL,全文检索能力强 | LogQL,语法接近 PromQL,标签过滤加管道 |
| 存储成本 | 索引膨胀明显,1GB 原始日志常落成 1~3GB 索引,必须配生命周期策略 | 正文压缩存储,成本主要由对象存储单价决定 |
| 适用规模 | 日日志量百 GB 级、需要复杂聚合与全文检索 | 日日志量几十 GB 以内、已有 Grafana 体系 |
| 典型短板 | 集群运维成本高,分片规划与版本升级容易出事 | 全文检索弱,标签设计不当会导致扫描量爆炸 |
选型判断其实很简单:如果排障主要靠 trace_id 和已知标签定位(大多数微服务场景就是这样),Loki 足够且便宜;如果需要按任意关键字做历史检索,或者有审计与安全分析需求,再考虑 ELK。价格区间以各家官网为准,量级差异通常在一倍到数倍之间,取决于检索频率而非日志总量。
nginx 访问日志该记哪些字段
默认 combined 格式够用但缺关键项,尤其是耗时没有拆分。建议自定义 log_format,把网关耗时和后端耗时分开。
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time urt=$upstream_response_time '
'uct=$upstream_connect_time urt_hdr=$upstream_header_time '
'rid=$request_id host=$host';
- request_time 与 upstream_response_time 必须分开,否则分不清是网关慢还是后端慢,这是排障时最常被问的问题。
- request_id 由入口生成并透传给后端,是日志与链路关联成本最低的方案。
- 记录 method、path、status、bytes,用于统计 5xx 与流量;但不要记录完整 query string,里面常有 token 和手机号。
- User-Agent 与 Referer 用于识别爬虫和异常来源,注意体积,必要时截断。
脱敏要在写入前做
日志一旦落盘,脱敏就变成了「删日志」,代价高且不可靠。手机号、身份证号、银行卡号、access_token、cookie、密码字段,必须在应用侧或采集代理侧替换掉。常见做法是在日志框架里挂一层过滤器:手机号保留前三位与后四位,身份证只留前六后四,token 整体替换成固定占位符。
注意别只处理 message 字段。结构化日志的 extras、HTTP header、异常堆栈里同样会漏,而堆栈恰恰是最容易被原样打出来的部分。另外,日志平台的访问权限要单独收,能查日志的人实际上能看到大量业务数据,「日志只读」不等于「日志无害」。
日志爆盘的三个防护
- logrotate:按大小和日期双条件轮转,配合 compress 与 maxage。注意区分 copytruncate 与信号重开,选错会丢一段日志;轮转后还要确认磁盘空间真正释放,别让被占用句柄继续压着空间。
- 限速:应用侧做采样与限流,把重复堆栈合并计数;采集侧设置单机每秒上限与单条长度上限,防止一条超长日志撑爆管道。
- 磁盘水位告警:在 80% 和 90% 两档设阈值,同时监控 inode 水位,并预置一条「保留最近 N 天」的应急清理脚本,别等爆盘了现场写命令。
三者缺一不可:只做轮转挡不住短时间暴涨,只做限速会丢掉关键错误,只做告警等于事后通知。容器环境下还要确认日志驱动与宿主目录的关系,可以对照 Docker 入门 里数据卷那节落实落盘位置。冷数据用对象存储归档时,各家取回计费方式差别很大,选型思路见 对象存储怎么选,跨区传输的流量也会进账单,可以参考 带宽与流量计费 先估一遍。
常见问题
已经有大量纯文本日志,怎么平滑迁移到结构化?
不要一次性改完。先在采集侧用正则解析成结构化字段,让查询方式先统一;再逐个服务改应用日志,改一个切一个。解析规则要和日志格式一起做版本管理,否则格式一改就全线失效。
Loki 能替代 Elasticsearch 做全文搜索吗?
不能。Loki 只在标签范围内扫描,关键字匹配是在扫描阶段做的,没有倒排索引。已知标签加时间范围的高频排障它很快,跨全量历史做任意词检索会很慢且很贵。
日志保留多久比较合理?
看合规要求和排障习惯,常见组合是热 7~15 天、温 30~90 天、冷按合规期。如果没有任何要求,从 30 天起步即可,真正需要长期保留的通常只有审计类和资金类日志。
容器里的日志为什么总是看不到?
多数是日志写到了容器内文件而非标准输出,或者被日志驱动截断。规范做法是应用只写 stdout,由运行时收集;确需写文件的,必须挂载到卷,否则容器重建即丢。
延伸阅读
- 对象存储怎么选:OSS、S3 与自建 MinIO 对比:冷数据归档的成本差异主要取决于取回方式与计费模型。
- 云服务器安全加固实战清单:上线前必须做的 10 件事:日志平台本身也是攻击面,端口与权限要一起收。
- 云服务器带宽与流量计费:怎么避免月底账单翻倍:跨区传输日志的流量会被计入账单,容易被忽略。
- 云计算成本优化(FinOps)实战:把月度账单砍掉 30%:保留期与降采样是日志成本优化的第一杠杆。
- Docker 入门:镜像、容器、数据卷与网络一次讲清:容器日志的落盘位置与卷配置,决定日志会不会随容器消失。
- 服务器监控告警怎么搭:从指标采集到告警收敛:日志是事后排查,指标是事前预警,两套要一起建。
- 备份与容灾:3-2-1 原则和一次真正的恢复演练:日志本身也要备份,否则出事时无从复盘。