跳到主要内容

configure:把流水线搭起来

30 秒导读: 你调一次 logfire.configure(),Logfire 就在幕后干两件事:先把配置收敛——从函数实参、环境变量、pyproject.toml、内置默认值里,按优先级挑出每个参数的最终值;再照这份配置装配一条导出管线——建 resource、建采样器、建 SDKTracerProvider,然后一层层往上叠处理器(baggage → 你的处理器 → console → 每个 token 一套 OTLP 导出器),最后把它挂成 OTEL 的全局 provider。本章只讲"配置 → 拓扑"这层映射;处理器内部怎么加工、导出怎么重试留给 04,采样细节留给 06

本章是整个系列的控制平面。前面 01 讲了一个 span 怎么诞生、02 讲了它的属性怎么来的——但那些 span 最终流向哪里、经过哪些加工,全由这一次 configure() 决定。


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

一句话定义: logfire.configure() 是 Logfire 的总开关兼装配车间——它读一堆配置,然后把"span 从产生到发出去"这条流水线搭起来。

它要解决的问题: OpenTelemetry(下称 OTEL,一套开源的可观测性标准)本身很灵活,但也很啰嗦——要发数据,你得手动建 provider、建 exporter、配 endpoint、配 header、配采样、配 resource…… Logfire 的卖点就是"opinionated wrapper(有主见的封装)":你给我一个 token,剩下的我全默认配好。 这份"配好"的活,就发生在 configure() 里。

用起来什么样: 最小用法只有一行——

# 示意,非源码:最常见的三种起手式
import logfire

logfire.configure() # 全靠环境变量 / pyproject.toml / 凭证文件
logfire.configure(token='pylf_v1_us_...') # 显式给 token
logfire.configure(service_name='api', console=False) # 覆盖个别选项

一句话直觉:configure() 想成装修房子的总包工头。你说"我要三室一厅,厨房朝南"(实参),他还会去查小区规定(环境变量)、看你之前留的装修图纸(pyproject.toml),都没有就按标准户型来(默认值)。定完方案,他再按顺序把水电、墙、家具一件件装上(处理器/导出器)。装完,房子就能住了(能发数据了)。

本节不碰底层。记住两个阶段就行:先收敛配置,再装配管线。


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

2.1 两个阶段、四个入口函数

configure() 的调用链很短,但每一层各有分工:

阶段函数文件:行干什么
入口configure()config.py:483模块级门面函数,收 30 来个 kwarg,转发给全局配置对象
收敛_LogfireConfigData._load_configuration()config.py:816把每个参数按"实参 > env > 文件 > 默认"敲定,存到 self.*
装配LogfireConfig._initialize()config.py:1170照收敛后的配置,建 resource/采样器/provider,叠处理器,挂全局
建 resourceLogfireConfig._build_resource()config.py:1083把 service/version/env/detectors/属性按优先级合成一个 Resource

阶段之间的控制流:

logfire.configure(...) config.py:483
│ new GLOBAL_CONFIG.configure() (或 local 新建)

LogfireConfig.configure() config.py:1027
│ ① 收敛
├─► _load_configuration() config.py:816
│ └─ ParamManager 逐参数取值 config_params.py:170
│ ② 装配
└─► initialize() → _initialize() config.py:1170
├─ _build_resource() config.py:1083
├─ 建 sampler + SDKTracerProvider
├─ add_span_processor() 累加处理器
├─ 按 token 建 OTLP 导出器链
└─ set_tracer/meter/logger_provider (全局)

2.2 配置从哪来:四层优先级

收敛阶段的核心是一个"就近覆盖"规则,由 ParamManager.load_param()(config_params.py:170)实现:

运行时实参 > 环境变量(可多个别名) > pyproject.toml [tool.logfire] > 默认值
(你写的) (LOGFIRE_* / OTEL_*) (allow_file_config) (含运行时回调)
命中即停 ──────────────────────────────────────────────────────────►

怎么读: 从左到右找,第一个非空值就用它,后面的都不看了。所以显式传参永远赢过环境变量,环境变量赢过配置文件。

2.3 装配出来的拓扑长什么样

装配阶段(_initialize)把一堆处理器叠成一棵树,再交给 SDKTracerProvider。一个 span 结束后,会从根往下流过所有处理器:

SDKTracerProvider
│ (只挂一个根)

CheckSuppressInstrumentationProcessorWrapper config.py:1400

MainSpanProcessorWrapper(scrubber) ← 脱敏在这层,见 06

root_processor = main_multiprocessor (或 TailSamplingProcessor)
│ 按加入顺序广播给每个子处理器
┌──────────────┬──┴───────────┬─────────────────────┐
▼ ▼ ▼ ▼
DirectBaggage... 你的 additional SimpleSpanProcessor DynamicBatchSpanProcessor
(baggage→属性) _span_processors (console 输出) (每个 token 一套,发 OTLP)
config.py:1213 config.py:1215 config.py:1237 config.py:1327

三条并行的 signal 管线(trace / metric / log)最后分别挂到三个全局 provider 上(config.py:1505-1509)。本章聚焦 trace 这条主线,metric/log 顺带点到。


3. 收敛阶段:一份配置怎么敲定

3.1 门面 configure():只做转发

configure()(config.py:483)本身几乎不含逻辑。它的职责是收下所有 kwarg,选一个配置对象,把参数原样转发:

  • local=True → 新建一个独立的 LogfireConfig()(用于同时发往多个项目);否则用全局单例 GLOBAL_CONFIG(config.py:1853)。
  • config.configure(...)(config.py:700-726),把 30 来个参数一字不落传进去。
  • 收尾:emit_configuration_span() 可选地发一条"Logfire configured"日志,再按需启动 variable provider(config.py:735-742)。

真正的活在 LogfireConfig.configure()(config.py:1027):加锁 → 收敛 → 装配三步,骨架就这么简单:

# 真实结构见 config.py:1051-1076,这里只留骨架
with self._lock:
self._initialized = False
self._load_configuration(...) # ① 收敛:敲定每个 self.* 字段
self.initialize() # ② 装配:据此搭管线

3.2 参数分两类:选项打包 vs 单值

configure() 的参数分两种风格:

  • 单值参数:tokenservice_namemin_levelsend_to_logfire 等,直接是标量。
  • Options dataclass:把一组相关开关打包成一个不可变对象,传进去。共五个:
Options文件:行管什么几个关键字段
ConsoleOptionsconfig.py:156终端输出长啥样span_style(simple/indented/show-parents)、min_log_levelshow_project_link
AdvancedOptionsconfig.py:181测试/高级旋钮base_urlid_generatorresource_detectorsserver_response_hooklog_record_processors
MetricsOptionsconfig.py:303指标DEFAULT_VIEWS(丢弃 SDK 自监控、指数桶直方图)、additional_readersviews
CodeSourceconfig.py:359源码回链repositoryrevisionroot_path(让 UI 能跳到 GitHub 对应行)
PydanticPluginconfig.py:280Pydantic 校验埋点(已废弃,改用 instrument_pydantic())record(off/all/failure/metrics)、include/exclude

一个细节: MetricsOptions.DEFAULT_VIEWS(config.py:306)是 ClassVar,官方鼓励你 MetricsOptions(views=[*MetricsOptions.DEFAULT_VIEWS, View(...)]) 这样在默认之上追加,而不是整个替换掉——默认那三条 view 会丢掉 OTEL 自身的 meta 指标、给所有直方图用指数桶、给 http.server.active_requests 削减基数。

3.3 _load_configuration:逐字段收敛

_LogfireConfigData._load_configuration()(config.py:816)是收敛的主体。它做的事高度重复:对每个参数,交给 ParamManager.load_param(名字, 运行时值) 敲定,存回 self 几个代表:

# 真实见 config.py:844-863
self.param_manager = param_manager = ParamManager.create(config_dir) # 先加载 pyproject.toml
self.send_to_logfire = param_manager.load_param('send_to_logfire', send_to_logfire)
self.token = normalize_token(token) or normalize_token(param_manager.load_param('token', None))
self.service_name = param_manager.load_param('service_name', service_name)
min_level = param_manager.load_param('min_level', min_level) # 字符串级别 → 数字

几个不只是"透传"的收敛点,值得记:

  • token 归一化(config.py:848):normalize_token()(config_params.py:298)把单 token 存成 str、多 token(逗号分隔或 list)存成 list[str]、空的存成 None。多 token 是"同时发往多项目"的基础。
  • Options 的字典反序列化:每个 Options 参数都先判 isinstance(x, dict)(如 config.py:867/877/895/927)。这是给 integrations/executors.py 用的——子进程里配置以字典形态传来,这里再还原成 dataclass。
  • console 的三态(config.py:877-893):传了对象就用对象;文件里显式 console=false 就关掉;都没有就逐个 LOGFIRE_CONSOLE_* 环境变量拼出一个 ConsoleOptions
  • sampling 兜底(config.py:895-902):没传 SamplingOptions 就用 head=trace_sample_rate 造一个——把老的 LOGFIRE_TRACE_SAMPLE_RATE 平滑接到新采样体系上。
  • scrubbing → scrubber(config.py:866-875):None 变默认 ScrubbingOptions(),FalseNOOP_SCRUBBER。真正的脱敏器就在这里定型,细节见 06

3.4 ParamManager:优先级到底怎么查

load_param()(config_params.py:170)是那条"实参 > env > 文件 > 默认"规则的唯一实现:

# 真实见 config_params.py:187-204
if runtime is not None and runtime != '': # ① 运行时实参,非空即用
return runtime
param = CONFIG_PARAMS[name]
for env_var in param.env_vars: # ② 环境变量(可多个别名)
value = os.getenv(env_var)
if value: # 注意:'' 和未设当同一回事
return self._cast(value, name, param.tp)
if param.allow_file_config: # ③ pyproject.toml [tool.logfire]
value = self.config_from_file.get(name)
if value is not None:
return self._cast(value, name, param.tp)
return self._cast(param.default, name, param.tp) # ④ 默认(可能是运行时回调)

每个参数由一个 ConfigParam(config_params.py:28)描述,登记在 CONFIG_PARAMS 表里(config_params.py:123)。几个有意思的点:

  • 一个参数可有多个环境变量别名:如 SERVICE_NAME 同时认 LOGFIRE_SERVICE_NAME 和 OTEL 标准的 OTEL_SERVICE_NAME(config_params.py:64);token 的 LOGFIRE_TOKEN 支持逗号分隔多值(config_params.py:60)。
  • allow_file_config 才能写进 pyproject.toml——token/api_key 故意不允许入文件(没这个标志),避免把密钥提交进仓库。
  • 默认值可以是运行时回调:_DefaultCallback(config_params.py:43)。经典例子是 send_to_logfire 默认值——'PYTEST_VERSION' not in os.environ(config_params.py:52),即跑测试时默认不发数据
  • _cast(config_params.py:214)负责把环境变量的字符串转成目标类型:bool1/true/tfloatPath、逗号分隔的 set/list/dict,还能递归处理 Union
  • 文件配置来自 pyproject.toml[tool.logfire] 段(config_params.py:317-325)。

4. 装配阶段:_initialize 把管线搭出来

这是全章的核心。_initialize()(config.py:1170)长约 370 行,但结构是清晰的七步走。整段包在 with suppress_instrumentation()(config.py:1177)里——装配自己产生的 OTEL 噪音不该被记录。

4.1 第一步:建 resource

resource = self._build_resource()(config.py:1178)。resource 是"这批遥测数据是谁产生的"的一组属性(service.name、host.name、os 等),会贴在每一条 span/metric/log 上

_build_resource()(config.py:1083)的精髓是多层 merge,后者覆盖前者,优先级从低到高:

优先级来源例子代码
最低自动派生的公共属性 + 随机 instance.id + git 版本process.runtime.*host.*os.*config.py:1136-1156
OTEL 默认 + OTEL_RESOURCE_ATTRIBUTES 等环境变量Resource.create({})config.py:1156
resource_detectors 参数跑出来的process.commandprocess.ownerconfig.py:1158-1163
最高显式属性:resource_attributes + 专用参数service.nameenvironment、code_sourceconfig.py:1124-1168

专用参数 vs 通用属性的冲突警告(config.py:1115-1122):如果你既用 service_name='a' 又在 resource_attributes={'service.name': 'b'} 里设了同一个 key 且值不同,它会 warn 一句"专用参数胜出",而不是静默。

git 版本兜底(config.py:1143-1155):没显式给 service_version 时,尝试 git rev-parse HEAD(config.py:2214)填进去——但放在最低优先级,所以 OTEL_RESOURCE_ATTRIBUTES 里的显式版本仍然赢。

4.2 第二步:建采样器 + SDKTracerProvider

head 采样(在 span 一开始就决定要不要采,便宜)在这里落地:

# 真实见 config.py:1180-1191
head = self.sampling.head
sampler = None
if isinstance(head, (int, float)):
if head < 1: # 1.0 = 全采,不用采样器
sampler = ParentBasedTraceIdRatio(head) # 按 trace-id 比例采,尊重父级决定
else:
sampler = head # 也可直接传一个自定义 Sampler
tracer_provider = SDKTracerProvider(
sampler=sampler,
resource=resource,
id_generator=self.advanced.id_generator, # 默认 SeededRandomIdGenerator
)

注意这里只是 head 采样;tail 采样(等 span 结束、看全貌再决定)是后面处理器层的事(见 4.5)。采样的完整语义留给 06

建好后立刻换掉旧的:self._tracer_provider.shutdown()set_provider(tracer_provider)(config.py:1193-1194)——self._tracer_provider 是个 ProxyTracerProvider,configure 之前它包着 no-op,这一刻才换成真的。

4.3 第三步:add_span_processor——处理器的累加器

接下来所有处理器都通过一个本地闭包 add_span_processor()(config.py:1200)加入。它做两件分流:

# 真实见 config.py:1200-1210
def add_span_processor(span_processor):
if self.sampling.tail and getattr(span_processor, 'tail_sampling_defer_on_start', False):
user_deferred_processors.append(span_processor) # tail 采样时,某些处理器要延后
else:
main_multiprocessor.add_span_processor(span_processor) # 常规:加进主广播器
# 会产生 "pending span"(半成品 span)的导出器,单独记一份
if isinstance(getattr(span_processor, 'span_exporter', None),
(TestExporter, RemovePendingSpansExporter, SimpleConsoleSpanExporter)):
processors_with_pending_spans.append(span_processor)
  • main_multiprocessor(SynchronousMultiSpanProcessor,config.py:1198)是"主广播器"——加进去的处理器都会在 span 结束时被依次调用。
  • processors_with_pending_spans 是给"需要看未结束 span"的导出器留的钩子(console、TestExporter 等),后面会用 PendingSpanProcessor 包起来(config.py:1372-1380)。

4.4 第四~六步:按固定顺序往上叠处理器

这是"配置 → 拓扑"映射最直观的地方——每个配置开关对应往 add_span_processor 里塞一个处理器,顺序固定:

顺序触发条件加入的处理器代码
add_baggage_to_attributes(默认 True)DirectBaggageAttributesSpanProcessorconfig.py:1212-1213
用户传了 additional_span_processors逐个用户处理器config.py:1215-1217
console 未禁用SimpleSpanProcessor(console 导出器)config.py:1221-1237
send_to_logfire 且有 token每个 token 一套 OTLP 导出器链config.py:1244-1369
设了 OTEL_EXPORTER_OTLP_* 环境变量BatchSpanProcessor(OTLPSpanExporter())config.py:1405-1414

console 那步(③)顺带做了件事:除了给 trace 加 console 处理器,还往 log_record_processors 里塞一个 ConsoleLogExporter(config.py:1238),让日志也能打到终端。console 的样式(simple/indented/show-parents)决定用哪个 exporter 类(config.py:1222-1228)。

token 那步(④)是重头戏send_to_logfire 为真时(config.py:1244),先处理凭证(见 §5),拿到 token 列表后对每个 token 建一整套导出器:

# 真实见 config.py:1308-1328,省略 metric/log 部分
for token in token_list:
base_url = self.advanced.generate_base_url(token) # 从 token 解析 region → URL
headers = {'User-Agent': f'logfire/{VERSION}', 'Authorization': token}
session = OTLPExporterHttpSession()
install_logfire_response_hook(session, self.advanced.server_response_hook)
span_exporter = BodySizeCheckingOTLPSpanExporter( # 最内层:真正发 HTTP
endpoint=urljoin(base_url, '/v1/traces'), session=session,
compression=Compression.Gzip, headers=headers)
span_exporter = QuietSpanExporter(span_exporter) # 层层包裹(装饰器链)
span_exporter = RetryFewerSpansSpanExporter(span_exporter)
span_exporter = RemovePendingSpansExporter(span_exporter)
logfire_processor = DynamicBatchSpanProcessor(span_exporter) # 批处理
add_span_processor(logfire_processor)

这条"套娃"就是 span 导出链的核心拓扑——从外到内是:DynamicBatchSpanProcessor(攒批)→ RemovePendingSpansExporter(去掉半成品)→ RetryFewerSpansSpanExporter(失败重试、超大就拆小)→ QuietSpanExporter(压噪音)→ BodySizeCheckingOTLPSpanExporter(查体积、真正 POST 到 /v1/traces)。每层各干一件事的内部逻辑,是 04 的主题;本章只需知道"一个 token = 一套这样的链,挂进主广播器"。

同一个循环里还并行建了另外两条 signal 的链(共用同一个 sessionheaders):

  • metric:PeriodicExportingMetricReader(QuietMetricExporter(OTLPMetricExporter(.../v1/metrics))),追加进 metric_readers(config.py:1332-1349)。
  • log:BatchLogRecordProcessor(QuietLogExporter(OTLPLogExporter(.../v1/logs))),追加进 log_record_processors(config.py:1351-1364)。

emscripten/Pyodide 例外:浏览器里没线程,所以凡是用后台线程批处理的地方(DynamicBatchSpanProcessorBatchLogRecordProcessor),都降级成 SimpleSpanProcessor/SimpleLogRecordProcessor(如 config.py:1323-13271359-1363)。

4.5 第七步:组根处理器(tail 采样的岔路)

前面处理器都进了 main_multiprocessor。收尾时决定长什么样(config.py:1383-1403):

有 tail 采样?

┌───────┴────────┐
是 否
│ │
TailSamplingProcessor root = main_multiprocessor
(包住 main + (deferred 处理器直接并进 main)
deferred 处理器)
│ │
└───────┬────────┘

MainSpanProcessorWrapper(root, scrubber) ← 脱敏在这层

CheckSuppressInstrumentationProcessorWrapper ← 唯一挂到 provider 的根

tracer_provider.add_span_processor(...) config.py:1399

无论哪条岔路,最终只有一个根处理器被挂到 SDKTracerProvider——一个 CheckSuppressInstrumentationProcessorWrapper 包着 MainSpanProcessorWrapper(带 scrubber)包着 root。这两个 wrapper 的内部加工(脱敏、抑制标记)见 0406

4.6 收尾:建 meter/logger provider,挂全局

trace 链搭好后,另外两条也定型:

  • metric:如果 metric_readers is not None,建 MeterProvider(metric_readers, resource, views)(config.py:1431-1437),否则 NoOpMeterProvider。还会把每个 reader 的自监控静音(config.py:1438-1443)。
  • log:把所有 log_record_processors 装进 SynchronousMultiLogRecordProcessor,包上同款 wrapper,挂到 SDKLoggerProvider,并设 min_level(config.py:1492-1503)。

最后一步——只有全局单例才动 OTEL 的全局状态(config.py:1505-1509):

# 真实见 config.py:1505-1509
if self is GLOBAL_CONFIG and not self._has_set_providers:
self._has_set_providers = True
trace.set_tracer_provider(self._tracer_provider)
set_meter_provider(self._meter_provider)
set_logger_provider(self._logger_provider)

local=True 的实例不碰全局,所以能安全地并存多套配置。收尾还注册了 atexit 刷新钩子、按 distributed_tracing 换 propagator(config.py:1534-1543)、给 AWS Lambda 打 flush 补丁——但这些属于生命周期管理,不是本章的装配主线。


5. 凭证与项目创建:token 从哪来

§4.4 的 token 循环有个前提:得先有 token。这就是 LogfireCredentials(config.py:1857)的活。它是个 dataclass,四个字段:tokenproject_nameproject_urllogfire_api_url

send_to_logfire 为真时,_initialize 按这个顺序找 token(config.py:1247-1270):

① 读本地凭证文件 .logfire/logfire_credentials.json load_creds_file config.py:1869
│ 没有 & 也没配 token?

② send_to_logfire is True 且啥都没有 → 交互式建项目 initialize_project config.py:2123
│ 拿到 credentials,写回文件

③ 用凭证补全 token / base_url / project_url config.py:1265-1270
│ (env 里的 token 优先于文件里的)

④ 有了 token → 后台线程校验其有效性 check_tokens config.py:1292-1305

几个关键方法:

方法文件:行干什么
load_creds_fileconfig.py:1869logfire_credentials.json;兼容老字段 dashboard_url → project_url
from_tokenconfig.py:1899拿 token 打 GET /v1/info 校验;401 才算硬失败,其余只 warn 不阻断
use_existing_projectconfig.py:1941列出用户已有项目,交互式选一个,换出 write token
create_new_projectconfig.py:2036选组织 + 输项目名(校验 PROJECT_NAME_PATTERN),建新项目
initialize_projectconfig.py:2123上面两者的调度:有旧项目就问要不要复用,否则建新的
write_creds_fileconfig.py:2166把凭证 dump 成 JSON 写进 data_dir

token → region → URL:get_base_url_from_token()(config.py:2196)从 token 前缀里解析出区域(如 pylf_v1_us_... 里的 us),映射到对应的 API base_url。这就是为什么"只给 token"就够了——region 信息编码在 token 里,不用你单独配 endpoint。老 token 没 region 就默认 US。

容错哲学: from_token 只在 401(token 无效)时报错,API 不可达或 500 都只 warnings.warn 然后继续(config.py:1916-1931)——网络抖动不该让你的应用崩,数据可以先本地缓存回填。

校验放在后台线程(config.py:1304)是为了不阻塞 configure() 返回;凭证文件在手时能先眼前打印项目链接,校验只是补确认(config.py:1279-1299)。


6. 巧妙之处(可借鉴的技术)

  • 收敛与装配彻底分离。 _LogfireConfigData(纯数据)和 LogfireConfig(带装配逻辑)拆成父子类(config.py:747)。好处:纯数据部分能 dataclasses.asdict 序列化,跨进程(executors)传给子进程再还原——这也解释了 §3.3 里满地的 isinstance(x, dict) 反序列化分支。

  • 优先级只有一处实现。 "实参 > env > 文件 > 默认"这条规则,全项目只在 load_param()(config_params.py:170)写了一遍,30 多个参数复用。想加参数,只需往 CONFIG_PARAMS 表里登记一个 ConfigParam

  • Proxy provider 让"随时重配"成立。 configure() 可以反复调。因为对外暴露的一直是 ProxyTracerProvider/ProxyMeterProvider(config.py:1015-1020),每次 _initialize 只是把 proxy 内部指向的真 provider 换掉(set_provider),已经拿到 tracer 的调用方无感知。

  • 一个 token 一套完整链,天然支持多项目。 多 token 只是 for token in token_list 多跑几圈(config.py:1308),每圈独立建 span/metric/log 三条导出链。项目迁移时"双写"零额外代码。

  • 导出器用装饰器链堆功能。 span 导出不是一个大类,而是 BodySizeChecking → Quiet → RetryFewerSpans → RemovePendingSpans → DynamicBatch 一层套一层(config.py:1314-1327),每层单一职责、可独立测试。


7. 边界与局限

  • 不是幂等的纯函数。 configure() 有大量副作用:设 OTEL 全局 provider、注册 atexit、patch os._exit(config.py:1847)、改全局 propagator、甚至给 AWS Lambda 打猴补丁。它是"配置世界状态",不是"返回一个配置"。
  • 重配的收尾窗口很短。 _initialize 关旧 meter provider 时只给 200ms(config.py:1460),超时会抛异常——文档提示重配前先手动 logfire.shutdown()
  • remote variables 需要 API key 而非 write token(config.py:1479-1483):两种凭证不通用,配了 VariablesOptions 却没 api_key 会直接报错。
  • 交互式建项目会阻塞在 stdin:initialize_projectrich.Prompt.ask(config.py:2149),CI 等无人值守环境应显式提供 token,别指望它自动建项目。

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

主题文件路径符号名
门面入口logfire/_internal/config.pyconfigure
收敛主体logfire/_internal/config.py_LogfireConfigData._load_configuration
数据/装配拆分logfire/_internal/config.py_LogfireConfigData / LogfireConfig
三步:锁/收敛/装配logfire/_internal/config.pyLogfireConfig.configure
装配主体(七步)logfire/_internal/config.pyLogfireConfig._initialize
resource 合成logfire/_internal/config.pyLogfireConfig._build_resource
处理器累加器logfire/_internal/config.pyadd_span_processor(_initialize 内闭包)
五个 Optionslogfire/_internal/config.pyConsoleOptions AdvancedOptions MetricsOptions CodeSource PydanticPlugin
凭证与项目logfire/_internal/config.pyLogfireCredentials(load_creds_file/from_token/use_existing_project/create_new_project/initialize_project)
token→region→URLlogfire/_internal/config.pyget_base_url_from_token
优先级实现logfire/_internal/config_params.pyParamManager.load_param
参数登记表logfire/_internal/config_params.pyCONFIG_PARAMS / ConfigParam
token 归一化logfire/_internal/config_params.pynormalize_token

接着读: 处理器/导出器内部怎么加工与可靠导出 → 04;采样(head/tail)与脱敏的完整语义 → 06;一个 span 怎么诞生并进入这条管线 → 01。总览见 index