跳到正文

日志与遥测

收集安全结构化日志,由宿主持有 OpenTelemetry SDK,并选择独立的 Workers tracing 入口。

日志和遥测是独立的可选集成。@lenso/log 输出 Pino 结构化日志,@lenso/otel 根入口提供官方 OpenTelemetry API。Core、Engine、Tasks 能记录 API span,但不会初始化 SDK;没有宿主 bootstrap 时,API 调用是 no-op。

本页使用已核实发布包 @lenso/log@0.2.0 与 @lenso/otel@0.2.0,并依据 549b987 的 Log 与 OTel 审计接口。按所选入口安装精确包与可选协议 peer。该基线没有内置 Observe query CLI、遥测存储或 collector。

stderr 日志只收集一次

宿主向 startApp 传入 logger;插件通过 context 获得作用域日志,无需依赖 Pino:

import { definePlugin, startApp } from "@lenso/core";import { createLogger } from "@lenso/log";
const greeting = definePlugin({  id: "greeting",  setup(context) {    return {      async greet(name: string) {        context.logger?.info({ operation: "greet" }, "Greeting requested");        return `Hello, ${name}`;      },    };  },});const app = await startApp({  plugins: [greeting],  logger: createLogger({ level: "info" }),});try {  await app.get(greeting).greet("Ada");} finally {  await app.stop();}

默认 JSON 发往 stderr,stdout 留给有限 CLI envelope 和 MCP protocol frame。进程 supervisor 负责收集一次 stderr。@lenso/log 不注册 SDK、不导出 OTel log;另加 log exporter 可能产生重复记录。

pretty: true 适合开发时同步可读输出。createLogger({ logger: existingPino }) 关联 trace,但不关闭借用 logger,transport 和生命周期仍归调用方。不要假设外部 logger 自动拥有默认 redaction 策略。

在应用导入前初始化遥测

Bun 宿主持有一次 SDK bootstrap。在 entry 或 preload 内初始化,先于 CLI/config/business 导入;应用工作 drain 后再 shutdown:

import { bootstrapTelemetry } from "@lenso/otel/bun";import { createORPCInstrumentation } from "@lenso/otel/orpc";
const telemetry = await bootstrapTelemetry({  serviceName: "notes",  instrumentations: [createORPCInstrumentation()],});try {  const { main } = await import("./server.ts");  // 应用提供 main():listener/worker drain 完成后才 resolve。  await main();} finally {  await telemetry.shutdown();}

这里的 main 是应用需要提供的生命周期契约,不是 Notes 示例承诺的导出。不要在插件 setup、config 模块或每个请求中 bootstrap。宿主通过可信部署配置提供授权的 OTLP 目的地/exporter 与凭据。仅导入 Lenso 不会安装 collector、自动 Node instrumentation 或 exception hook。

traceExporter、metricExporter、sampler、contextManager、propagator、instrumentations 接受官方接口。传入 exporter/context manager 默认借用;takeOwnership: true 才转移生命周期。自有默认资源在失败路径也只清理一次。mode: "external" 不修改全局 provider/context,只管理显式传入 instrumentation 的登记;外部 SDK 所有者负责最终 flush/shutdown。

forceFlush() 与 shutdown() 有界等待,timeout/export 失败会 reject。Timeout 不能停止任意自定义 exporter 代码;真实 cleanup 仍可能继续,归属保留到结束。Export 失败不证明业务操作失败。

有限 CLI 命令

源码 greeting 已提供设置 flushOnCliExit: true 的有限命令 preload。构建框架包,并配置授权的本地 OTLP receiver 后:

# 在审查后的源码根目录;endpoint 凭据留在环境中。bun --preload ./examples/greeting/src/telemetry.ts packages/cli/dist/bin.js \  call greeting greet '{"name":"Ada"}' --root examples/greeting --json

CLI 在 finally 结束 span 并等待有界 flush/shutdown。Export 失败输出固定 stderr diagnostic,保留业务 exit code。External SDK 模式仍由外部所有者管理。不要把有限命令 preload 当作 dev 子进程或长期 server 的生命周期方案。

HTTP propagation 只选一个 owner

该源码的可选 /orpc 需要 @orpc/opentelemetry@2.0.0-beta.42,Web 与客户端也使用同一 oRPC beta;组合产物前核对已安装兼容性。

若 Web telemetry.requestLifetime 或其他 Fetch/HTTP instrumentation 已持有 propagation,使用 createORPCInstrumentation({ propagationEnabled: false })。否则 oRPC 默认传播。oRPC HTTP/procedure span 与 Web 可选的 INTERNAL body/work/cleanup span 表示不同生命周期,避免两个 HTTP propagation owner。

官方 oRPC exception instrumentation 可能捕获业务异常消息;消息里也不能包含凭据、body 或签名 URL。

Workers 使用平台 tracing

浏览器安全的 @lenso/otel/workers 需要可选 peer @orpc/cloudflare@2.0.0-beta.42 和启用的 Wrangler traces。现有 Worker entry 只调用一次 bootstrapWorkerTracing(),配置 启用 observability.traces.enabled。

Bun SDK 路径原生 Workers 路径
显式 /bun bootstrap显式 /workers bootstrap
宿主负责 flush/shutdown平台负责 request span 和 export
OTel API context 提供 trace 关联原生 tracing 不桥接 Lenso OTel API span/context
可选 oRPC OTel instrumentation官方 Workers Traces 与 oRPC Cloudflare tracer
Bun Pino 日志入口平台日志或应用提供的 structural logger

不要混用两个 oRPC tracer owner,也不要每个 Worker 请求关闭 provider。原生路径目前没有自定义 OTLP export、可配置的 oRPC propagation 或 stream yielded/enqueued 事件;不会自动启用 Core/Tasks API span 或 Pino trace 关联。Isolate 终止不保证 app cleanup 完成。

关联证据而不泄漏数据

有用字段包括 instance/plugin/operation、jobId、attempt、traceId、spanId。持久 task attempt 创建链接到 producer 的新 root,不要求与 producer 同 trace ID。只有有界 traceparent/tracestate 单独保存;trace context 与 job ID 都不授予权限。高基数 ID 不应作为 metric label。

默认 Log redaction 覆盖常见结构化 secret、body/payload 与 Error 文本/stack,但不是完整 secret 发现。自由文本、特殊深层 key 和自定义 serializer 仍需应用策略。记录固定事件与安全 code,不记录完整配置、payload 或未知异常。

排查证据缺失

症状检查
有日志但没 traceSDK bootstrap、导入顺序、sampling 和实际 exporter/receiver
CLI 最终 span 丢失有限命令 preload、flushOnCliExit,区分 export 与业务失败
HTTP span / 日志重复Propagation owner 和重复 stderr/OTel 收集
Worker 日志没 trace ID原生路径不桥接 OTel context,使用平台关联
查询不到历史事件查实际配置的存储/query backend;OTLP endpoint 本身不等于查询服务

已有保留 stderr 或授权 backend 时使用它们;未配置收集意味着历史证据缺失,不能猜测 Observe 命令。归属测试、CLI export 测试与 Web lifetime 测试验证各自边界。