You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Python标准logging库实现集中式10%比例日志采样

Python标准logging全局10%请求采样实现方案

完全可以基于logging库原生能力实现,不需要引入第三方依赖,核心逻辑是利用logging的过滤器(Filter)机制+日志冒泡规则,在根日志器(root logger)上统一挂载采样过滤器,即可管控所有子logger的日志输出决策,不需要修改业务侧任何打日志的代码。

核心设计原则

采样必须以请求为维度,而非单条日志为维度:如果单条日志随机采样,会出现同一个请求的日志部分输出、部分丢弃的情况,碎片化的日志完全无法用于问题排查。我们需要保证命中采样的请求输出全量日志,未命中的请求不输出任何业务日志,最终整体日志量控制在10%左右。

具体实现代码

1. 初始化全局采样配置

基于Python 3.7+自带的contextvars实现跨协程/线程的请求上下文标记,避免多请求并发时的标记串扰:

import logging
import random
from contextvars import ContextVar

# 上下文变量:标记当前请求是否命中采样,默认未命中
is_sampled: ContextVar[bool] = ContextVar("is_sampled", default=False)
# 全局采样率,10%对应0.1
SAMPLE_RATE = 0.1

class RequestSamplingFilter(logging.Filter):
    def filter(self, record: logging.LogRecord) -> bool:
        # 直接返回当前请求的采样标记,同一个请求所有日志判定结果完全一致
        return is_sampled.get()

def init_log_config():
    root_logger = logging.getLogger()
    # 清空默认handler避免重复打印(可选,根据自己项目情况调整)
    root_logger.handlers.clear()
    # 核心:给根logger挂载采样过滤器,所有子logger冒泡上来的日志都会经过该过滤
    root_logger.addFilter(RequestSamplingFilter())
    
    # 后续正常配置你的日志handler即可,比如控制台输出、日志上报handler等
    stream_handler = logging.StreamHandler()
    stream_handler.setFormatter(logging.Formatter(
        "%(asctime)s - %(levelname)s - %(name)s - %(message)s"
    ))
    root_logger.addHandler(stream_handler)
    root_logger.setLevel(logging.INFO)

2. 请求入口添加采样判定逻辑

在你所用web框架的请求中间件/拦截器里,每个请求进入时先做采样判定,请求结束后重置上下文标记避免污染:

def process_request_middleware(req):
    # 请求进入时随机判定是否采样
    sampled = random.random() < SAMPLE_RATE
    token = is_sampled.set(sampled)
    try:
        # 执行后续的请求处理、业务逻辑
        return handle_request(req)
    finally:
        # 请求结束重置上下文,防止复用工作线程/协程时标记串到下一个请求
        is_sampled.reset(token)

方案优势

  • 完全原生:基于logging标准库的Filter、日志冒泡原生机制实现,无额外依赖
  • 集中管控:只需要在项目启动时执行一次init_log_config(),所有业务代码中通过logging.getLogger(__name__)获取的子logger,都会自动遵循采样规则,不需要修改任何业务侧打日志的代码
  • 日志完整:命中采样的请求保留全量日志,完全满足排查问题的需求
  • 灵活调整:需要修改采样率时,只需要调整全局SAMPLE_RATE常量即可,不需要散改多处配置

特殊场景说明

如果你的项目中有部分子logger手动设置了propagate = False(关闭日志向父级冒泡),这部分日志不会经过根logger的过滤器,只需要给这类子logger单独挂载同一个RequestSamplingFilter实例即可,一般不建议业务代码随意关闭日志冒泡,默认配置下全局一次挂载就能覆盖所有场景。

如果你不需要保留请求维度的完整日志,只想按日志条数随机采样10%,可以把过滤器逻辑简化,不需要上下文变量:

class CountBasedSamplingFilter(logging.Filter):
    def filter(self, record: logging.LogRecord) -> bool:
        return random.random() < SAMPLE_RATE

注意:该方式会导致日志碎片化,生产环境不推荐使用


内容的提问来源于stack exchange,提问作者Jean Carlo Machado

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 02:54:40