可观测数据留存治理实战:我如何把日志成本砍了70%还不影响排障

一、可观测数据治理的核心目标与一句话结论

可观测数据天然具有"越多越好"的冲动,但在大规模系统里,这种冲动会变成三个致命的问题:成本黑洞、查询性能劣化、合规风险

治理的目标不是"少采数据",而是"够用、可查、可控、可审计"。我们要在排障需求、成本和合规之间找到最佳平衡点。

成熟的可观测数据治理必须覆盖四个核心能力:

  • 够用:需要的时候能查到关键数据
  • 可查:查询速度快,不会因为数据量太大而超时
  • 可控:成本可预测,不会突然暴涨
  • 可审计:所有数据的采集、存储、删除都有记录,符合合规要求

一句话结论:留存治理的关键是 分层(hot/warm/cold) + 字段白名单 + 采样策略 + 成本Gate

采集

清洗/脱敏

分层存储

查询与告警

留存/归档/删除

成本与合规Gate

二、紧急情况:10分钟成本爆炸止血SOP

当出现"日志/Trace成本突然暴涨、查询越来越慢、审计发现敏感字段"时,不要等账单出来再处理。按照下面的步骤执行,10分钟内就能控制住成本

2.1 最短排查三问(30秒定位方向)

  1. 哪类数据增长最快?是日志还是链路追踪?
  2. 增长来自哪个服务、哪个接口或哪个字段?
  3. 是否是最近的日志级别变更或采样率调整导致的?

2.2 10分钟紧急止血SOP(附命令)

  1. 先降噪:临时降级高频日志

    # 动态调整Spring Boot日志级别
    curl -X POST "http://your-service:8080/actuator/loggers/com.example.service" \
    -H "Content-Type: application/json" \
    -d '{"configuredLevel": "WARN"}'
    
    # 临时关闭某个特定日志
    curl -X POST "http://your-service:8080/actuator/loggers/com.example.service.OrderService" \
    -H "Content-Type: application/json" \
    -d '{"configuredLevel": "ERROR"}'
    
  2. 先限量:提高Trace采样率

    # 动态调整Jaeger采样率
    curl -X POST "http://jaeger-agent:5778/sampling" \
    -H "Content-Type: application/json" \
    -d '{"strategy": "probabilistic", "param": 0.1}'
    
    # 按服务设置不同的采样率
    curl -X POST "http://jaeger-collector:14268/api/sampling" \
    -H "Content-Type: application/json" \
    -d '{"service": "high-traffic-service", "strategy": "probabilistic", "param": 0.01}'
    
  3. 快速定位Top消耗源

    # Elasticsearch查看各索引大小
    curl -X GET "http://es:9200/_cat/indices?v&s=store.size:desc"
    
    # 查看日志中出现频率最高的字段
    grep -o '"[^"]*":' logs/*.log | sort | uniq -c | sort -nr | head -20
    
    # 查看哪个服务产生的日志最多
    grep -o '"service":"[^"]*"' logs/*.log | sort | uniq -c | sort -nr | head -10
    
  4. 临时关闭高基数索引

    # Elasticsearch临时删除高基数字段的索引
    curl -X DELETE "http://es:9200/logs-*/_mapping" \
    -H "Content-Type: application/json" \
    -d '{"properties": {"userId": {"type": "keyword", "index": false}}}'
    

三、标准分层留存模型(建议直接套用)

不要把所有数据都存在昂贵的SSD上,根据数据的使用频率和价值,分成三层存储,成本可以降低一个数量级。

层级 存储介质 保留时间 索引策略 用途 成本对比
Hot热层 SSD 1-3天 全量索引 线上紧急排障、实时告警 100%
Warm温层 HDD 7-14天 核心字段索引 非紧急问题排查、日常分析 30%
Cold冷层 对象存储(S3/OSS) 30-180天 无索引,仅元数据索引 合规审计、事后追溯 5%

最佳实践

  • Hot层只保留最近3天的数据,这是90%的排障都会用到的时间范围
  • Warm层保留最近14天的数据,只索引错误级别日志和核心字段
  • Cold层保留合规要求的最长时间,数据压缩后归档,不支持实时查询
  • 超过保留期限的数据,自动永久删除,避免合规风险

四、真实翻车场景库(附解决方案)

下面这三个场景是90%的团队都会遇到的可观测数据噩梦,每一个我都踩过坑。

4.1 场景1:一行debug日志导致成本翻10倍

事故经过:一个开发同学为了排查一个问题,在代码里加了一行debug日志,打印了整个HTTP请求体。结果上线后,当天的日志量就从每天1TB涨到了10TB,账单直接多了十几万。更糟糕的是,请求体里包含了用户的手机号和身份证号,违反了数据保护法规。

根因分析:没有日志标准,日志级别变更没有任何审核和Gate。任何人都可以随意修改日志级别,打印任何内容。

治理方案:日志级别变更纳入变更审计与发布Gate

# 日志级别变更审批流程
apiVersion: policy/v1
kind: LoggingPolicy
metadata:
  name: log-level-policy
spec:
  allowedLevels:
    - ERROR
    - WARN
  restrictedLevels:
    - INFO
    - DEBUG
  approvalRequired:
    - DEBUG
  maxRetention:
    DEBUG: 1h
    INFO: 3d
// 日志级别动态变更审计
@Aspect
@Component
public class LogLevelChangeAspect {
    @Around("execution(* org.springframework.boot.actuate.logging.LoggersEndpoint.setLogLevel(..))")
    public Object auditLogLevelChange(ProceedingJoinPoint joinPoint) throws Throwable {
        String loggerName = (String) joinPoint.getArgs()[0];
        LogLevel level = (LogLevel) joinPoint.getArgs()[1];
        String user = SecurityContextHolder.getContext().getAuthentication().getName();
        
        log.warn("日志级别变更: user={}, logger={}, level={}", user, loggerName, level);
        
        // DEBUG级别需要审批
        if (level == LogLevel.DEBUG && !hasApproval(user, loggerName)) {
            throw new SecurityException("DEBUG级别日志需要审批才能开启");
        }
        
        // DEBUG级别1小时后自动降级
        if (level == LogLevel.DEBUG) {
            scheduler.schedule(() -> {
                loggersEndpoint.setLogLevel(loggerName, LogLevel.INFO);
                log.warn("DEBUG级别自动降级: logger={}", loggerName);
            }, 1, TimeUnit.HOURS);
        }
        
        return joinPoint.proceed();
    }
}

4.2 场景2:Trace采样太低导致排障抓不到

事故经过:为了降低成本,我们把Trace的采样率设成了1%。结果线上出现了一个偶发的异常,我们查了整整一天,都没有抓到对应的Trace。最后只能重启服务,问题才消失,但根因一直没有找到。

根因分析:只做了固定采样,没有异常驱动采样。正常请求可以少采,但错误请求和高延迟请求必须100%采样。

治理方案:分层采样策略,异常和高延迟请求强采样

// OpenTelemetry分层采样器实现
public class TieredSampler implements Sampler {
    private final Sampler defaultSampler = Sampler.traceIdRatioBased(0.01); // 默认1%采样
    private final Sampler errorSampler = Sampler.alwaysOn(); // 错误请求100%采样
    private final Sampler highLatencySampler = Sampler.alwaysOn(); // 高延迟请求100%采样
    
    @Override
    public SamplingResult shouldSample(Context context, String traceId, String name, SpanKind spanKind, Attributes attributes, List<Link> parentLinks) {
        // 错误请求强制采样
        if (attributes.get(SemanticAttributes.HTTP_STATUS_CODE) != null 
            && attributes.get(SemanticAttributes.HTTP_STATUS_CODE) >= 500) {
            return errorSampler.shouldSample(context, traceId, name, spanKind, attributes, parentLinks);
        }
        
        // 高延迟请求强制采样
        if (attributes.get(SemanticAttributes.HTTP_SERVER_DURATION) != null 
            && attributes.get(SemanticAttributes.HTTP_SERVER_DURATION) > 1000) {
            return highLatencySampler.shouldSample(context, traceId, name, spanKind, attributes, parentLinks);
        }
        
        // 正常请求按比例采样
        return defaultSampler.shouldSample(context, traceId, name, spanKind, attributes, parentLinks);
    }
}

最佳实践

  • 正常请求:1%-5%采样率
  • 错误请求:100%采样
  • 高延迟请求(>1s):100%采样
  • 核心业务链路:100%采样

4.3 场景3:高基数索引导致查询慢且贵

事故经过:我们把userId、traceId、requestId等高基数字段都建了索引。结果Elasticsearch的索引大小是原始数据的3倍,查询速度越来越慢,成本也越来越高。有时候一个简单的查询,需要几分钟才能返回结果。

根因分析:索引字段选择错误,把不需要过滤和排序的高基数字段也建了索引。高基数字段的索引会占用大量的磁盘空间和内存,严重影响查询性能。

治理方案:索引字段白名单,高基数字段只做检索不建索引

# Elasticsearch索引模板配置
index_patterns: ["logs-*"]
mappings:
  properties:
    # 核心字段,建索引
    timestamp:
      type: date
    level:
      type: keyword
    service:
      type: keyword
    trace_id:
      type: keyword
    span_id:
      type: keyword
    
    # 高基数字段,不建索引,只存储
    user_id:
      type: keyword
      index: false
      doc_values: false
    request_id:
      type: keyword
      index: false
      doc_values: false
    request_body:
      type: text
      index: false

最佳实践

  • 只给需要过滤、排序和聚合的字段建索引
  • 高基数字段(如userId、requestId)不要建索引
  • 大文本字段(如请求体、响应体)不要建索引
  • 定期清理不需要的索引

五、核心治理建议

5.1 统一字段规范与敏感字段强制脱敏

  • 所有日志必须包含统一的核心字段:timestamp、level、service、traceId、spanId
  • 敏感字段(手机号、身份证号、银行卡号)必须在采集阶段强制脱敏
  • 建立敏感字段扫描机制,定期检查日志中是否有未脱敏的敏感信息
// Logback脱敏转换器
public class SensitiveDataConverter extends MessageConverter {
    private static final Pattern PHONE_PATTERN = Pattern.compile("(1[3-9]\\d{9})");
    private static final Pattern ID_CARD_PATTERN = Pattern.compile("(\\d{18})");
    
    @Override
    public String convert(ILoggingEvent event) {
        String message = super.convert(event);
        
        // 脱敏手机号
        message = PHONE_PATTERN.matcher(message).replaceAll("$1****$2");
        
        // 脱敏身份证号
        message = ID_CARD_PATTERN.matcher(message).replaceAll("$1**********$2");
        
        return message;
    }
}

5.2 留存与采样策略版本化与审计

  • 所有留存和采样策略都要纳入版本控制
  • 策略变更必须有审批记录和审计日志
  • 定期审计策略执行情况,确保符合合规要求

5.3 成本与合规Gate进入发布流程

  • 把可观测数据成本作为发布Gate,变更后如果成本增长超过10%,不能上线
  • 建立成本预警机制,当某个服务的日志量超过阈值时,自动告警
  • 定期进行成本分析,识别并优化高消耗的服务和字段

六、值班工程师必备Checklist

当你接到可观测数据相关的告警时,按照这个顺序执行:

  1. 增长定位:成本增长来自哪个维度?是服务、字段还是租户?
  2. 变更检查:是否是最近的采样率调整或日志级别变更导致的?
  3. 合规检查:是否存在敏感字段扩散或超期留存的情况?
  4. 止血执行:能否立刻通过限量或降噪来控制成本?

七、小结

可观测数据治理不是"省钱",而是"让排障可持续"。

很多人认为,为了排障,就必须全量采集所有数据。但实际上,99%的日志都是没用的。我们真正需要的,是在出问题的时候,能快速找到关键信息。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐