跳到主要内容

Pydantic Logfire — 总览

30 秒导读: Logfire 是 Pydantic 团队做的可观测性平台。你在这个克隆里看到的是它的 开源 Python SDK——一个"开发者体验优先"的 OpenTelemetry 封装:写一行 logfire.configure(), 再用带 f-string 的 logfire.info(...) / logfire.span(...) 记录,SDK 就把这些调用变成 结构化的 trace/span,可靠地推到 Logfire 的闭源后端去展示和用 SQL 查询。

本章是这组文档的门厅:只回答"这是什么、大盘怎么转、一条主线怎么走、该读哪一章", 不钻各章的实现细节。看完你应能对着源码判断"我的问题该翻哪一章"。


1. 这是什么(零基础也能懂)

一句话定义: Logfire = 一个开发者体验优先的 OpenTelemetry(简称 OTel,业界标准的 遥测数据规范)封装库。它把"记录程序里发生了什么"这件事,做得像写普通日志一样简单, 但产出的却是符合 OTel 标准的结构化 trace。

logfire/__init__.py:1 的模块 docstring 就是它的自我定位:

"Logfire is the observability tool focused on developer experience."

开源边界(重要): 这个仓库是纯 SDK + 文档README.md:26 明说:记录和展示数据的 服务端应用是闭源的。所以本组文档讲的全是"数据在离开你进程之前,SDK 干了什么", 后端(UI、存储、SQL 引擎)不在克隆里,我们只讲到"数据被推出去"为止。

解决谁的什么问题: 假设你有个 Python 服务,想知道"某个请求慢在哪、哪次 Pydantic 校验失败了、这段代码调了几次数据库"。传统上你要么手写一堆 logging,要么直接上原生 OTel——后者强大但样板代码多、心智负担重。Logfire 的赌注是:把 OTel 的能力保留, 把它的难用磨掉

它对外提供什么(功能一览):

  • 手动埋点:logfire.span(...) / logfire.info(...) 等一组像日志一样的 API。
  • f-string 魔法:logfire.info('Hello {name}', name='world') 里的 {name} 会被自动 拆成结构化字段,而不是拼成一句死字符串(见 02)。
  • 富对象展示:Python 对象(尤其 Pydantic 模型)按其结构保留,而非 str() 拍平。
  • 自动埋点:install_auto_tracing(...) 用 AST 改写给整个模块的函数自动包 span。
  • 生态集成:instrument_fastapi / instrument_openai / instrument_httpx 等一大票 instrument_*,一行接管第三方库的埋点(见 logfire/__init__.py:42-71)。

用起来什么样(最小真实示例): 取自 README.md:52-64——

from datetime import date

import logfire

logfire.configure() # 装配整条流水线(见 03 章)
logfire.info('Hello, {name}!', name='world') # 一条结构化日志

with logfire.span('Asking the user their {question}', question='age'):
user_input = input('How old are you [YYYY-mm-dd]? ')
dob = date.fromisoformat(user_input)
logfire.debug('{dob=} {age=!r}', dob=dob, age=date.today() - dob)

直觉就这么两步:configure() 一次把管道接好,之后像写 print/logging 一样 记录,产出的却是可查询的结构化 trace。

一句话类比: 把 Logfire 当成"OTel 的舒适层"——OTel 是发动机和标准接口,Logfire 是 包在外面的方向盘、座椅和仪表盘:同一套动力,但你不用直接拧螺丝。


2. 顶层全景(它大概怎么转)

怎么读这张图: 从上到下是一条单向流水线——你的一次 API 调用,经过"加工 → 按配置 装配好的处理器链 → 导出",最后离开进程去闭源后端。左边是"你调的东西",右边越往下越底层。

你的代码
logfire.info('Hello {name}', name='world')


┌───────────────────────────────────────────────┐
│ ① 用户 API 门面 │
│ logfire/__init__.py │
│ DEFAULT_LOGFIRE_INSTANCE = Logfire() │
│ 模块级 info/span/… 全是它的方法绑定 │
└───────────────────────────────────────────────┘
│ 转调实例方法

┌───────────────────────────────────────────────┐
│ ② 加工:把一次调用变成一个 span │
│ _internal/main.py (Logfire._span / log) │
│ _internal/formatter.py (拆 f-string→字段) │
└───────────────────────────────────────────────┘
│ 需要一个已装配好的 tracer + 处理器

┌───────────────────────────────────────────────┐
│ ③ 配置装配(一次性,configure 时) │
│ _internal/config.py LogfireConfig._initialize│
│ 搭好 tracer_provider + 处理器链 + 导出器 │
└───────────────────────────────────────────────┘
│ span 结束 / 开始时流经

┌───────────────────────────────────────────────┐
│ ④ 处理器链 │
│ _internal/tracer.py (Proxy tracer/provider, │
│ PendingSpanProcessor) │
│ _internal/exporters/processor_wrapper.py │
└───────────────────────────────────────────────┘
│ 批量、编码

┌───────────────────────────────────────────────┐
│ ⑤ 导出:可靠地送出 OTLP │
│ _internal/exporters/otlp.py │
│ (HTTP + gzip,失败落磁盘重试) │
└───────────────────────────────────────────────┘
│ OTLP over HTTP

╔═══════════════════════╗
║ Logfire 闭源后端 ║ ← 不在本克隆内
║ (UI / 存储 / SQL 查询) ║
╚═══════════════════════╝

部件一句话职责:

部件干什么文件(相对克隆根)
用户 API 门面把模块级 logfire.xxx 绑到一个默认实例上logfire/__init__.py
Logfire / LogfireSpan核心类:一次调用怎么变成 span、绑字段、上报logfire/_internal/main.py
格式化器'{name}' 模板拆成消息 + 结构化字段logfire/_internal/formatter.py
配置装配configure():搭好 tracer、处理器链、导出器logfire/_internal/config.py
Proxy tracer / pending 处理器包装 OTel tracer;为每个 span 额外发"开始事件"logfire/_internal/tracer.py
处理器包装在 span 出入口做统一加工(脱敏、抑制等)logfire/_internal/exporters/processor_wrapper.py
OTLP 导出器HTTP+gzip 送出;失败落磁盘重试logfire/_internal/exporters/otlp.py
自动埋点AST 改写,给模块函数自动包 spanlogfire/_internal/auto_trace/
采样 / 脱敏控量的采样器 + 去敏感信息的 scrubberlogfire/sampling/logfire/_internal/scrubbing.py

3. 主线走一遍:logfire.info('Hello {name}', name='world')

只走高层,不进代码——细节各章有。这条主线串起上面五个部件:

  1. 命中门面。 logfire.info 并不是独立函数,而是 logfire/__init__.py:85DEFAULT_LOGFIRE_INSTANCE.info 绑成了模块级名字。你调的其实是那个默认 Logfire 实例的方法。

  2. info 收敛到 log。 Logfire.info(main.py:374)只做参数校验,然后转调 self.log('info', ...)(main.py:404)。log(main.py:718)是所有日志级别的公共入口。

  3. 拆 f-string、建 span。 log 里,模板 'Hello {name}' 被交给格式化器 (formatter.py)拆成人读的消息 Hello world + 结构化字段 name="world"。 一条日志在 OTel 里其实是一个瞬时 span

  4. 流经处理器链。 span 通过配置阶段装配好的 tracer(tracer.py 的 proxy)进入处理器链 ——脱敏、抑制检查、批处理都在这层(processor_wrapper.pytracer.py)。

  5. 导出到后端。 批处理器攒够一批,交给 OTLP 导出器(otlp.py)编码成 OTLP、gzip 压缩、 HTTP 发出;网络抖了就落磁盘重试。数据就此离开你的进程,进闭源后端。

一句话:你写的是一行"日志",SDK 把它变成一个结构化 span,顺着一条装配好的流水线, 可靠地推走。


4. 一个要先点破的设计:门面模式(façade)

初次读源码最容易困惑的是:"logfire.infologfire.span 这些函数,定义在哪?"

答案在 logfire/__init__.py:34-112。它先造一个默认单例:

DEFAULT_LOGFIRE_INSTANCE: Logfire = Logfire() # __init__.py:34
span = DEFAULT_LOGFIRE_INSTANCE.span # :35
info = DEFAULT_LOGFIRE_INSTANCE.info # :85
# …几十行都是把实例方法绑成模块级名字…

从第 35 行到第 112 行,几乎全是 名字 = DEFAULT_LOGFIRE_INSTANCE.某方法 的绑定。

这么做的好处:import logfire 后直接 logfire.info(...),不用先 lf = Logfire(); 但真正的逻辑全在 Logfire 类(main.py)里,可测试、可多实例。模块只是薄门面, 所有实现看 main.pyLogfire 类。 这一点在这里点破即可,细节见 01 章


5. 阅读地图(建议顺序)

这组文档由浅入深。按下面顺序读最顺,也可按需跳章:

顺序章节讲什么什么时候读它
101-public-api-and-span.md门面 API + 一个 span 从 span/infoLogfireSpan 的诞生想搞懂"我调的函数到底是什么"
202-fstring-magic-and-schema.mdf-string 怎么拆成字段、Python 对象怎么富展示成 JSON schema好奇 {name} 为什么能变结构化字段
303-config-and-pipeline-assembly.mdconfigure() 怎么把 tracer / 处理器 / 导出器搭成流水线想懂 configure() 背后发生了什么
404-processing-and-export.mdpending span、批处理、OTLP 编码、磁盘重试关心 span 如何被可靠送走
505-auto-trace-and-instrumentation.mdAST 自动埋点 + instrument_* 集成家族想零侵入接入,或接第三方库
606-sampling-and-scrubbing.md采样(控量)与脱敏(去敏感信息)两道闸关心成本控制与数据安全

建议: 先读本页 + 01 建立直觉;想懂"魔法"读 02;想懂"引擎装配"读 03-04; 运维/安全关切看 05-06。


6. 巧妙之处速览(精华,细节在各章)

这里只点名"妙在哪、在哪一章展开",不重复实现。

  • f-string 魔法 —— logfire.info('Hello {name}', name=...) 里的模板不是死字符串, 而是被拆成"人读消息 + 结构化字段"。核心在 formatter.py (logfire_format_with_magic,formatter.py:246)。展开见 02

  • 富对象展示(x-python-datatype) —— 把 Pydantic 模型、dataclass、numpy 数组等任意 Python 对象转成一份带类型标记的 JSON schema,让后端能按原类型还原富展示,而不是 str() 拍平(json_schema.pycreate_json_schema)。展开见 02

  • pending span(开始即上报) —— 普通 OTel 只在 span 结束时才发数据,长任务在结束前 是"看不见"的。Logfire 用 PendingSpanProcessor(tracer.py:340)在 span 开始时就额外 发一条"pending"事件,后端因此能实时显示"正在进行中"。展开见 04

  • 磁盘重试(不丢数据) —— 网络抖动时,导出器不把待发数据堆在内存,而是落到磁盘 由后台线程慢慢重试(otlp.pyDiskRetryer,otlp.py:124 起)。省内存、抗断网。 展开见 04

  • AST 自动埋点 —— install_auto_tracing导入时改写模块的抽象语法树,给函数自动 包 span,业务代码一行不改(logfire/_internal/auto_trace/,rewrite_ast.py)。 展开见 05

  • 采样与脱敏两道闸 —— 头/尾采样控数据量、正则脱敏堵敏感信息泄露,都以旁路闸门挂在导出 管线上,配一次即对所有 span 生效(logfire/sampling/_tail_sampling.pyscrubbing.py)。 展开见 06


7. 边界(本组文档讲到哪为止)

  • 只讲开源 SDK。 后端 UI / 存储 / SQL 查询是闭源的(README.md:26), 不在克隆内,我们讲到"数据被 OTLP 推出去"为止。
  • 只讲 Python SDK。 Logfire 另有 TypeScript / Rust SDK,不在此仓库。
  • 点到为止的门面。 本页只点破门面模式与流水线骨架;每个部件的真实代码走读在对应章。

8. 代码地图(导航索引)

主题文件(相对克隆根)符号名
模块自我定位logfire/__init__.py模块 docstring(第 1 行)
默认实例 + 门面绑定logfire/__init__.pyDEFAULT_LOGFIRE_INSTANCE(第 34 行起)
核心类:span 的诞生与上报logfire/_internal/main.pyLogfireLogfireSpanLogfire._spanLogfire.log
f-string 拆字段logfire/_internal/formatter.pylogfire_formatlogfire_format_with_magicChunksFormatter
对象 → JSON schemalogfire/_internal/json_schema.pycreate_json_schemaattributes_json_schema_properties
配置装配流水线logfire/_internal/config.pyconfigureLogfireConfigLogfireConfig._initialize
Proxy tracer + pending spanlogfire/_internal/tracer.pyProxyTracerProvider_ProxyTracerPendingSpanProcessor
处理器出入口加工logfire/_internal/exporters/processor_wrapper.pyMainSpanProcessorWrapperCheckSuppressInstrumentationProcessorWrapper
OTLP 导出 + 磁盘重试logfire/_internal/exporters/otlp.pyBodySizeCheckingOTLPSpanExporterOTLPExporterHttpSessionDiskRetryer
自动埋点(AST 改写)logfire/_internal/auto_trace/rewrite_astimport_hookno_auto_trace
采样 / 脱敏logfire/sampling/_tail_sampling.pylogfire/_internal/scrubbing.pySamplingOptionsTailSamplingProcessorScrubberDEFAULT_PATTERNS