FastGPT 统一可观测性 SDK 实战:用 @fastgpt-sdk/otel 一站式接入 OpenTelemetry 日志、指标与链路追踪
【免费下载链接】FastGPTFastGPT is a knowledge-based platform built on the LLMs, offers a comprehensive suite of out-of-the-box capabilities such as data processing, RAG retrieval, and visual AI workflow orchestration, letting you easily develop and deploy complex question-answering systems without the need for extensive setup or configuration.项目地址: https://gitcode.com/GitHub_Trending/fa/FastGPT
本篇技术指南讲解 FastGPT 仓库中统一的可观测性 SDK——@fastgpt-sdk/otel(位于 sdk/otel/README.md)。该包的目标是把分散的@fastgpt-sdk/logger、@fastgpt-sdk/metrics与 tracing 能力收拢到单一入口,同时提供“统一入口”和“渐进迁移入口”两种用法。读完本文,你将掌握其环境变量体系、日志结构化输出与安全归一化规则、指标与采样的底层实现,以及如何像 FastGPT service 层那样用它接入 OpenTelemetry Collector / OTLP 后端。
一、定位:为什么需要一个统一的可观测性 SDK
从源码看,@fastgpt-sdk/otel是一个自包含(self-contained)包:
- 内部自带 logger 实现、metrics 实现与 tracing 实现;
- 不依赖
@fastgpt-sdk/logger或@fastgpt-sdk/metrics,三者可独立工作; - 它的定位是“整理好的统一入口”,而不是“已经迁移完成的替换方案”——现有 logger 与 metrics 包仍然可以继续独立使用,后续再逐步迁移。
它对外提供四类能力:
| 能力 | 说明 | 统一入口 | 渐进迁移入口 |
|---|---|---|---|
| 日志 | 基于 LogTape 的 logger | @fastgpt-sdk/otel | @fastgpt-sdk/otel/logger |
| 指标 | OpenTelemetry Metrics | @fastgpt-sdk/otel | @fastgpt-sdk/otel/metrics |
| 链路追踪 | OpenTelemetry Tracing | @fastgpt-sdk/otel | @fastgpt-sdk/otel/tracing |
| 初始化 | 统一的configureOtel()/configureOtelFromEnv() | @fastgpt-sdk/otel | — |
两种入口对应两种接入策略:新代码直接用统一入口;存量代码按模块逐个切换到子路径入口,实现渐进式迁移。包版本与导出结构可查看 sdk/otel/package.json(当前版本 0.1.2,exports中声明了.、./logger、./metrics、./tracing四个子路径,包以 ESM 方式构建,产物为dist/index.mjs等)。
二、快速开始
2.1 统一入口
最直接的用法是在应用启动时调用configureOtelFromEnv(),把日志、指标、追踪三者一次性从环境变量初始化:
import { configureOtelFromEnv, getLogger, getMeter, getTracer } from '@fastgpt-sdk/otel'; await configureOtelFromEnv({ defaultServiceName: 'fastgpt-client' }); const logger = getLogger(['system']); const meter = getMeter('fastgpt-client'); const tracer = getTracer('fastgpt-client');其中defaultServiceName是兜底服务名:当环境变量没有显式指定时,日志的LOG_OTEL_SERVICE_NAME、指标的METRICS_OTEL_SERVICE_NAME、追踪的TRACING_OTEL_SERVICE_NAME以及标准 OTEL 的OTEL_SERVICE_NAME均会回退到它。
2.2 渐进迁移入口
如果只想逐步替换某个模块,可以按需导入子路径:
import { configureLoggerFromEnv, getLogger } from '@fastgpt-sdk/otel/logger'; import { configureMetricsFromEnv, getMeter } from '@fastgpt-sdk/otel/metrics'; import { configureTracingFromEnv, getTracer } from '@fastgpt-sdk/otel/tracing';2.3 初始化与销毁的底层行为
configureOtel()/configureOtelFromEnv()的实现(见 src/client.ts 与 src/env.ts)本质上是并发执行三个子模块的初始化:
await Promise.all([ configureLogger(options.logger ?? {}), configureMetrics(options.metrics ?? {}), configureTracing(options.tracing ?? {}) ]);三者互不阻塞、互不依赖,任何一个失败都会让整体初始化失败。对应的disposeOtel()也并行执行disposeLogger()、disposeMetrics()、disposeTracing(),分别完成 LogTape dispose、MeterProvider shutdown、TracerProvider shutdown,用于优雅退出。
值得注意的工程细节是:三个子模块的configure*都是幂等的。以 logger 为例(src/logger/client.ts),内部维护configured标志与configurePromise,重复调用不会重复创建 sink,并发调用则复用同一个 Promise——这保证了在模块多处初始化、或测试与生产代码混跑时不会出现双重注册。
三、日志(logger)能力深入
3.1 环境变量与默认值
logger 的初始化由configureLoggerFromEnv完成(src/logger/env.ts),支持的变量与默认值如下:
| 环境变量 | 作用 | 默认值 |
|---|---|---|
LOG_ENABLE_CONSOLE | 是否输出到控制台 | true |
LOG_CONSOLE_LEVEL | 控制台最低日志级别 | trace |
LOG_ENABLE_OTEL | 是否开启 OTel 日志导出 | false |
LOG_OTEL_LEVEL | OTel 导出的最低日志级别 | warning |
LOG_OTEL_SERVICE_NAME | 导出时的服务名 | defaultServiceName |
LOG_OTEL_LOGGER_NAME | Logger 名称 | 同服务名 |
LOG_OTEL_URL | OTLP HTTP logs 端点 | http://localhost:4318/v1/logs |
日志级别取值集合为trace / debug / info / warning / error / fatal,非法值会静默回退到默认值(src/logger/env.ts)。布尔环境变量解析遵循1/true/yes/on为真、0/false/no/off为假的规则,见 src/env-utils.ts。
3.2 双 sink 架构:控制台 + OTel
sink 的组装在 src/logger/sinks.ts:
- 控制台 sink:使用 LogTape 的
getConsoleSink+getPrettyFormatter,输出人类可读的格式化日志,级别低于LOG_CONSOLE_LEVEL的记录被过滤; - OTel sink:通过
getOpenTelemetrySink(src/logger/otel.ts)创建,内部使用OTLPLogExporter+BatchLogRecordProcessor批量上报,支持懒初始化——第一条日志到达时才初始化 exporter,初始化前的日志先进入pendingRecords缓冲队列,初始化完成后统一补发;若初始化失败(如端点不可达),则丢弃缓冲并只向控制台打印错误。
日志级别到 OTel SeverityNumber 的映射由mapLevelToSeverityNumber完成(src/logger/helpers.ts),trace→TRACE、debug→DEBUG、info→INFO、warning→WARN、error→ERROR、fatal→FATAL。
3.3 OTel 日志的结构化输出约定
这是 README 重点强调的设计(src/logger/otel.ts):
body承载业务数据:形如{ __log_message, ...properties }。__log_message是把 LogTape 的 message 模板与占位符拼接后的人读摘要,properties中剩余字段作为结构化上下文扁平放入 body;attributes只保留日志元信息:仅保留category(即['system']、['http', 'request']这类分类数组),不再复制 properties,避免 body 与 attributes 内容重复;- 名为
traceId/spanId的 properties 是保留字段,会被跳过(避免与链路上下文重复,见 src/logger/otel.ts)。
单元测试 sdk/otel/test/logger/otel.test.ts 用createMemoryLoggerProvider直接验证了这一点:带completionsBody等复杂对象属性时,body中完整保留结构化内容,而attributes严格等于{ category: ['test'] }。
3.4 安全归一化:JS 特有值不再出现[object Object]
在把 properties 写入 OTel body 之前,所有值都要经过normalizeLogValue(src/logger/otel.ts)安全归一化,规则包括:
| 输入类型 | 归一化结果 |
|---|---|
Map | 展开为普通对象(key 字符串化) |
Set | 转换为数组 |
BigInt | 转为字符串 |
Symbol | 转为字符串 |
Function | [Function name] |
Date | ISO 8601 字符串 |
Error | { name, message, stack, cause, errors }(含AggregateError的errors数组) |
Uint8Array | 原样保留(或按bytesAsSummary输出[Uint8Array length=N]摘要) |
| class 实例 | 普通对象化并附带__type: 构造函数名,必要时用util.inspect兜底 |
| 循环引用 | 标记为[Circular] |
| 超出深度/键数 | 深度上限 8 层([MaxDepth]),单对象键数上限 128 个(__truncated: true) |
对应测试见 sdk/otel/test/logger/otel.test.ts:Map被展开、Set变数组、bigint变字符串、class 实例带__type: 'CustomValue'、循环引用输出'[Circular]'。这一设计保证了即使业务属性里混入原生 JS 类型,也不会在序列化时抛异常或产出无意义的[object Object]。
3.5 敏感属性过滤与日志上下文
OTel sink 还支持sensitiveProperties过滤(src/logger/sinks.ts):当一条日志的 properties 中出现命中名单的键时,该记录不会被导出到 OTel。FastGPT service 层在初始化时传入了sensitiveProperties: ['fastgpt'](见 packages/service/common/logger/client.ts)。
此外,getLogger()返回的是带 Proxy 的 logger:当调用形式为logger.info(msg, { verbose: false, ...properties })时,verbose: false标记会被剥离、其余属性作为结构化上下文传入,避免把控制开关写进日志数据(src/logger/client.ts)。withContext()与withCategoryPrefix()也从@logtape/logtape直接转发,用于在异步上下文中注入requestId等关联字段。
四、指标(metrics)能力
4.1 环境变量
configureMetricsFromEnv(src/metrics/env.ts)支持的变量:
| 环境变量 | 作用 | 默认值 |
|---|---|---|
METRICS_ENABLE_OTEL | 是否开启 OTel 指标导出 | false(或OTEL_METRICS_EXPORTER=otlp时自动开启) |
METRICS_OTEL_SERVICE_NAME | 指标服务名 | 回退到OTEL_SERVICE_NAME/defaultServiceName |
METRICS_OTEL_URL | OTLP HTTP metrics 端点 | 回退到OTEL_EXPORTER_OTLP_METRICS_ENDPOINT |
METRICS_EXPORT_INTERVAL | 导出周期(毫秒) | 30000(同时兼容标准OTEL_METRIC_EXPORT_INTERVAL) |
OTEL_METRICS_EXPORTER | 标准导出器选择,为otlp时自动启用 | — |
底层实现(src/metrics/client.ts)使用MeterProvider+PeriodicExportingMetricReader,按exportIntervalMillis周期批量导出;URL 会自动规范化:不以/v1/metrics结尾时自动追加该路径。默认 meter 名为fastgpt,可通过defaultMeterName覆盖。
4.2 FastGPT service 层的指标实践
FastGPT service 层已经用该 SDK 埋了两个典型指标集:
- 运行时指标(packages/service/common/metrics/runtime.ts):在
fastgpt.runtimemeter 下注册了fastgpt.runtime.process.memory.{rss,heap_used,heap_total,external,array_buffers}、fastgpt.runtime.process.cpu.{user,system,utilization}、fastgpt.runtime.process.uptime等 ObservableGauge,CPU 利用率由两次采样间的 CPU 时间差与核数计算得到; - Redis 连接指标(packages/service/common/metrics/redis.ts):在
fastgpt.redis.runtimemeter 下注册fastgpt.redis.connections.active/created/errors、fastgpt.redis.health.checks、fastgpt.redis.health.duration、fastgpt.redis.shutdown.duration,DAL 层只注入观测回调,不直接依赖 OTel,测试时可以注入内存 recorder 而不初始化 exporter。
service 层封装configureMetrics()(packages/service/common/metrics/client.ts)在初始化 OTel 后自动启动运行时指标采集,disposeMetrics()时同步停止。
五、链路追踪(tracing)能力
5.1 专属环境变量与标准 OTEL 回退
tracing 的环境变量解析在 src/tracing/env.ts,README 列出的专属变量与回退逻辑如下:
专属变量(优先级最高):
| 环境变量 | 作用 |
|---|---|
TRACING_ENABLE_OTEL | 是否开启 tracing,true时启用 |
TRACING_OTEL_SERVICE_NAME | 追踪服务名 |
TRACING_OTEL_URL | OTLP HTTP traces 端点 |
TRACING_OTEL_SAMPLE_RATIO | 采样率(0~1,越界会被 clamp) |
标准 OTEL fallback(当专属变量缺失时按优先级读取):
| 标准变量 | 作用 |
|---|---|
OTEL_SERVICE_NAME | 服务名兜底 |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT | traces 端点兜底 |
OTEL_EXPORTER_OTLP_ENDPOINT | 通用 OTLP 端点兜底(会自动追加/v1/traces) |
OTEL_TRACES_EXPORTER | 值为otlp时自动启用 tracing |
OTEL_TRACES_SAMPLER | 采样器选择,支持always_on、always_off、traceidratio及其parentbased_*变体 |
OTEL_TRACES_SAMPLER_ARG | 采样器参数(用于 ratio 型采样器) |
采样率的解析规则(src/tracing/env.ts):always_off/parentbased_always_off映射为 0,always_on/parentbased_always_on映射为 1,traceidratio/parentbased_traceidratio读取OTEL_TRACES_SAMPLER_ARG,其余情况回退默认值。
5.2 采样与端点实现的底层细节
- 默认采样率为 1(全量采样),经
Math.max(0, Math.min(1, value))归一化; - 底层使用
NodeTracerProvider,采样器为ParentBasedSampler({ root: new TraceIdRatioBasedSampler(ratio) })——即根 span 按比率采样,子 span 跟随父 span 决策,见 src/tracing/client.ts; - span 通过
BatchSpanProcessor+OTLPTraceExporter批量导出,端点缺失时只注册 provider 不导出(span 仍可在进程内使用); getCurrentSpanContext()可读取当前活跃 span 的traceId/spanId,用于把链路 ID 拼进日志,实现 trace 与 log 关联。
5.3 service 层的 tracing 封装
FastGPT service 层基于该 SDK 提供了三个生产级封装(packages/service/common/tracing/client.ts):
- 默认采样策略:
TRACING_OTEL_SAMPLE_RATIO未配置时,NODE_ENV === 'production'使用 0.01(1% 采样控制成本),非生产环境使用 1(全量采样); withActiveSpan(name, callback):自动创建 span、注入 attributes、异常时setSpanError(记录 exception 并置 ERROR 状态)、finally中结束 span;getTraceLogContext():返回{ traceId, spanId }供日志上下文关联。
实际调用链可参考两处:HTTP 入口在 packages/service/common/http/entry.ts 中用withContext({ requestId })包裹withActiveSpan({ name: 'http.request', tracerName: 'fastgpt.http' }),把 method、route、body size 写入 span attributes;工作流调度在 packages/service/core/workflow/dispatch/index.ts 与同文件第 1612 行对节点执行也包了withActiveSpan。这些是追踪能力在真实业务中的直接落地样本。
六、迁移思路与在 FastGPT 中的落地现状
README 给出了明确的三阶段迁移路径:
- 收拢初始化入口:先把 logger/metrics/tracing 的初始化从多个 SDK 入口收到
@fastgpt-sdk/otel(或@fastgpt-sdk/otel/*子入口); - 替换 import:把业务代码中的
logger/metricsimport 逐步改成otel子路径; - 补充 traces:最后按业务需要补充 span。
从仓库现状看,FastGPT 的 service 层已经完成了第一阶段,甚至进入了第二阶段:
@fastgpt-sdk/otel已通过workspace:*加入 service 依赖(packages/service/package.json);- logger 封装从
@fastgpt-sdk/otel/logger导入(packages/service/common/logger/client.ts),metrics 封装从@fastgpt-sdk/otel/metrics导入,tracing 封装从@fastgpt-sdk/otel/tracing导入; - 测试与 Vitest 配置分别用
paths/resolve.alias将三个子路径映射到sdk/otel/src下的入口文件(见 packages/service/test/tsconfig.json 与 packages/service/vitest.config.ts),说明子路径导出在开发与测试环境均可直接指向源码。
service 层环境变量的生产配置集中在 packages/service/env.ts,这些默认值可作为实际部署参考:
| 变量 | service 层默认值 |
|---|---|
LOG_ENABLE_CONSOLE | true |
LOG_CONSOLE_LEVEL | debug |
LOG_ENABLE_OTEL | false |
LOG_OTEL_LEVEL | info |
LOG_OTEL_SERVICE_NAME | fastgpt-client |
LOG_OTEL_URL | 可选 |
METRICS_ENABLE_OTEL | false |
METRICS_EXPORT_INTERVAL | 30000(毫秒) |
METRICS_OTEL_SERVICE_NAME | fastgpt-client |
METRICS_OTEL_URL | 可选 |
TRACING_ENABLE_OTEL | false |
TRACING_OTEL_SERVICE_NAME | fastgpt-client |
TRACING_OTEL_URL | 可选 |
TRACING_OTEL_SAMPLE_RATIO | 可选(0~1,未配置时按 NODE_ENV 决定:生产 0.01,否则 1) |
七、小结
@fastgpt-sdk/otel是 FastGPT 可观测性体系的“统一收口层”:
- 一键初始化:
configureOtelFromEnv()并行初始化 logger / metrics / tracing,disposeOtel()并行优雅销毁; - 双入口设计:统一入口适合新代码,子路径入口适合存量代码渐进迁移,两者 API 完全一致;
- 日志结构化:body 承载业务数据、attributes 只留元信息,配合 Map/Set/Error/循环引用等 JS 类型的深度安全归一化,保证导出数据可查询、可检索;
- 标准兼容:专属
*_OTEL_*变量与标准OTEL_*变量双轨并存,采样率、导出端点均有完备回退; - 已在生产路径落地:service 层的 HTTP 入口、工作流调度、运行时与 Redis 指标均基于该 SDK,可直接作为接入范本。
参考阅读:sdk/otel/README.md(包说明)、sdk/otel/src/index.ts(统一入口导出)、sdk/otel/test/logger/otel.test.ts(日志结构化行为测试)、packages/service/common/logger/client.ts(service 层日志封装)、packages/service/env.ts(生产环境变量默认值)。
【免费下载链接】FastGPTFastGPT is a knowledge-based platform built on the LLMs, offers a comprehensive suite of out-of-the-box capabilities such as data processing, RAG retrieval, and visual AI workflow orchestration, letting you easily develop and deploy complex question-answering systems without the need for extensive setup or configuration.项目地址: https://gitcode.com/GitHub_Trending/fa/FastGPT
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考