本代码仓库是基于 OpenTelemetry 针对 Python 应用进行可观测数据采集的探针。请根据以下原则 Review 代码,并给出修改建议。
埋点时请尽可能使用框架自带的扩展机制,比如 ASGI 的 middleware 机制。
避免:使用 monkey patch 去 patch 某个具体的方法,因为这个方法可能在某个版本存在,在下一个版本就不存在了。
- 原则上不要引入三方的依赖库,需要用拷贝源码的方式 shadow 到项目中,但 requests 库例外。
- 尽量复用已有的依赖,例如假如依赖 httpx 可以改为依赖 aiohttp,因为 aiohttp 是已有的依赖,而 httpx 需要新增
埋点逻辑需要保留用户的业务语义。
例如:在某个地方原本用户要抛出一个异常,探针就不可以吞掉这个异常,必须确保异常能够正常抛出。
埋点的逻辑需要足够轻量,避免占用用户过多的 CPU。
禁止:
deepcopy操作- 流量链路禁止使用 info 及以上级别,建议使用
logger.debug级别,仅在开启 DEBUG 模式时输出。原因是频繁打日志会占用用户过多的 CPU 和 I/O 资源。
需要注意潜在的资源泄漏情况,包括但不限于以下几种:
问题:引用计数无法处理循环引用,Python 会依靠循环垃圾回收器(gc)来清理。
风险点:如果循环引用中的对象定义了 __del__ 方法,Python 2 或早期版本可能无法自动回收(Python 3.4+ 已改进,但仍需注意)。
建议:避免在可能产生循环引用的对象中定义 __del__;或显式断开引用。
问题:全局字典、列表、缓存等在程序生命周期内不会被释放。
典型场景:
- 手动实现的缓存未设置淘汰策略(如 LRU)
- 使用
functools.lru_cache装饰长期运行的函数,但未限制大小
建议:使用 functools.lru_cache(maxsize=...) 或定期清理缓存。
问题:闭包可能无意中持有了大对象的引用。
建议:在闭包中显式 del 不需要的变量,或重构避免捕获大对象。
问题:C 扩展(如某些 numpy、pytorch、opencv 插件)若未正确释放内存,Python 的 gc 无法干预。
表现:tracemalloc 或 pympler 无法追踪到泄漏,但系统内存持续增长。
建议:
- 检查 C 扩展文档或更新版本
- 使用
objgraph、memory_profiler或系统工具(如 valgrind)辅助诊断
虽然严格来说不一定是"内存泄漏",但未关闭的资源会持有内存或系统句柄。
示例:
f = open('huge_file.txt')
# 忘记 f.close(),文件句柄和缓冲区内存可能长期占用建议:始终使用 with 语句管理资源。
问题:traceback 对象会持有栈帧,栈帧又引用局部变量,导致整个作用域的对象无法释放。
示例:
import sys
exc_info = None
try:
raise ValueError("oops")
except Exception as e:
exc_info = sys.exc_info() # 包含 traceback
# exc_info 持有栈帧 → 持有所有局部变量建议:避免长期保存 exc_info,使用后设为 None;或使用 traceback.format_exc() 代替。
禁止:创建新的线程以及定时任务。
如果一定要创建,需要严格评估对应的资源开销。
检查指标中的各个属性是否有可能是高基数的,高基数的属性容易造成发散问题。
特别注意以下属性:
endpointdestIdrpc
请检查插件中是否只对这个插件本身的一些类进行埋点。 禁止:对插件依赖的类进行埋点。 例如:CrewAI 依赖了 litellm 框架,在 CrewAI 的埋点里面就不要对 litellm 相关的类进行埋点操作。
建议统一使用 raise 来保留完整的异常堆栈,使用 raise e 会丢失原始堆栈信息。
问题:在并发场景下,不加锁的单例检查可能会创建多个实例。
多线程环境下修改全局变量可能导致竞态条件,建议:
- 使用线程锁保护读写操作
- 或将配置封装到单例类中
按照 PEP 8 规范,导入语句应按以下顺序排列:
- 标准库导入
- 第三方库导入
- 本地导入
各组之间用空行分隔。
按照 PLC 0415 的规范,如非必要,导入语句原则上应该放在文件的开头。如果由于某些原因不得不放在代码中间,必须添加注释说明,并使用行内注释# noqa: PLC0415来避免校验失败。
同一个模块只应导入一次,检查文件中是否存在重复的 import 语句。
对于可能不存在的库(如用户未安装的框架),建议使用延迟导入或 try-except 方式,避免在模块级别直接导入可能不存在的模块,这会导致整个模块加载失败。
禁止:使用 except BaseException,这会捕获 KeyboardInterrupt 和 SystemExit。
建议:使用 except Exception。
- 避免使用 Python 3.8 以上才支持的语法
- 文件末尾换行符:根据 PEP 8 规范,所有 Python 文件末尾应该有一个换行符。
- 顶级函数和类定义之间应有两个空行。
- 等号两侧应有空格
- 枚举命名风格一致性,符合之前命名的习惯
- 变量命名需要比较有意义,不要有一些很简单的字符
- 新增的功能模块应该有对应的单元测试覆盖,特别是:
- 核心业务逻辑
- 边界情况处理
- 异常情况处理
- 单元测试需要验证到响应的功能修改,例如新增一个插件的单元测试需要包含完成 trace 验证,metrics 验证,并且测试用例尽量真实,不能有太多的 mock 测试。
建议:
- 添加最大容量限制
- 添加定期清理机制(如基于时间的过期)
- 使用
WeakValueDictionary允许 GC 自动回收
- 检查是否存在拼写错误
- 在重构代码后,请确保调用的方法确实存在。常见问题是重构后方法名变更或被移除,导致运行时
AttributeError。
- 在插件的 uninstrument 的实现中,要保持 instrument 实现的对称性,例如在 instrument 中申请的资源,在 uninstrument 中释放
- 在 instrument 中实现的逻辑,在 uninstrument 方法中进行倒序的清理。
- 关键的方法,建议给出一些 input/output 的示例,提升代码可读性
- 避免引入无意义的注释,例如一些开发过程中的注释,忘记删除
- 有些代码被注释掉,这种情况建议直接删除
- PR 的标题需要符合 conventional commits 规范
- PR 中需要关联工作项
所有注释以及说明文档中都应遵循本约定。
- "探针"(名词)对应英文命名应使用 instrumentation,而非 probe
- "埋点"(动词)对应英文命名应使用 instrument