← 文章 / 编程开发
signoz 2小时前 · 2026-10-05 16:04:26 · 7 阅读

Bun 与 ElysiaJS 的 OpenTelemetry 监控指南

本指南将介绍如何为 Bun 应用接入 OpenTelemetry,并把链路和指标数据发送到 SigNoz。主线方案使用专为 Bun 设计的 Web 框架 ElysiaJS。如果你的应用直接使用 Bun.serve,请参考不使用 Elysia 的 Bun 接入方案。 使用自托管 SigNoz?

大部分步骤都一样。只需按照从 Cloud 迁移到 Self-Hosted的说明,修改 endpoint 地址并移除 ingestion key 请求头即可。

前提条件

  • 已安装 Bun
  • 一个 SigNoz 实例(Cloud 或 Self-Hosted 均可)
  • 一个 Elysia 应用。如果想从零开始,运行 bun create elysia my-app,该命令会在 src/index.ts 创建应用入口文件。

已在 Bun v1.4.2、Elysia 1.4.30 和 @elysia/opentelemetry 1.4.12 上测试通过。

从 Elysia 应用发送遥测数据

第 1 步:安装依赖包

在项目根目录运行以下命令:

复制
bun add @elysia/opentelemetry \
  @opentelemetry/sdk-trace-node \
  @opentelemetry/sdk-metrics \
  @opentelemetry/exporter-trace-otlp-proto \
  @opentelemetry/exporter-metrics-otlp-proto \
  @opentelemetry/instrumentation-runtime-node \
  @opentelemetry/instrumentation-host-metrics
请使用 @elysia/opentelemetry,而不是 @elysiajs/opentelemetry

旧包 @elysiajs/opentelemetry(最高到 1.4.11 版本)无法记录 HTTP 请求指标。请使用 @elysia/opentelemetry。

第 2 步:在应用中接入 OpenTelemetry

将插件添加到你的 Elysia 应用中。exporter 会通过 OTLP/HTTP 发送数据,并从环境变量中读取 endpoint 和 ingestion key——环境变量将在下一步设置。

src/index.ts
import { Elysia } from 'elysia'
import { opentelemetry } from '@elysia/opentelemetry'
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto'
import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node'
import { HostMetricsInstrumentation } from '@opentelemetry/instrumentation-host-metrics'
 
const app = new Elysia()
  .use(
    opentelemetry({
      serviceName: process.env.OTEL_SERVICE_NAME ?? 'bun-elysia-app',
      spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())],
      metricReader: new PeriodicExportingMetricReader({
        exporter: new OTLPMetricExporter(),
        exportIntervalMillis: 15000,
      }),
      instrumentations: [
        new RuntimeNodeInstrumentation(),
        new HostMetricsInstrumentation(),
      ],
    })
  )
  .get('/', () => 'Hello from Elysia')
  .listen(3000)

在添加路由之前先调用 .use(opentelemetry(...))。Elysia 不会追踪在插件之前添加的路由。

第三步:设置环境变量并运行

VMKubernetesDockerWindows

在启动应用所在环境的 shell 中设置以下变量,然后运行应用:

Copy
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_SERVICE_NAME="<service-name>"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=<environment>"
 
bun run src/index.ts

在部署清单的容器配置中加入这些环境变量:

Copy
env:
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: "https://ingest.<region>.signoz.cloud:443"
  - name: OTEL_EXPORTER_OTLP_HEADERS
    value: "signoz-ingestion-key=<your-ingestion-key>"
  - name: OTEL_SERVICE_NAME
    value: "<service-name>"
  - name: OTEL_RESOURCE_ATTRIBUTES
    value: "deployment.environment.name=<environment>"

如需从本地机器发送测试请求,请将本地端口转发到该部署。把 <deployment-name> 替换为你的部署名称:

Copy
kubectl port-forward deploy/<deployment-name> 3000:3000

然后在另一个终端中运行 curl http://localhost:3000/。

在项目根目录创建一个 Dockerfile:

Dockerfile
FROM oven/bun:1
 
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
 
CMD ["bun", "run", "src/index.ts"]

构建镜像,并在运行容器时传入环境变量:

Copy
docker build -t bun-elysia-app .
 
docker run \
  -e OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443" \
  -e OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>" \
  -e OTEL_SERVICE_NAME="<service-name>" \
  -e OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=<environment>" \
  -p 3000:3000 \
  bun-elysia-app

在 PowerShell 中设置变量,然后运行应用:

Copy
$env:OTEL_EXPORTER_OTLP_ENDPOINT = "https://ingest.<region>.signoz.cloud:443"
$env:OTEL_EXPORTER_OTLP_HEADERS = "signoz-ingestion-key=<your-ingestion-key>"
$env:OTEL_SERVICE_NAME = "<service-name>"
$env:OTEL_RESOURCE_ATTRIBUTES = "deployment.environment.name=<environment>"
 
bun run src/index.ts

请核对以下参数值:

  • <region>:您的 SigNoz Cloud 区域。
  • <your-ingestion-key>:您的 SigNoz 摄入密钥(ingestion key)。
  • <service-name>:SigNoz 中显示的服务名称,例如 bun-elysia-app。
  • <environment>:部署环境,例如 production。SigNoz 仪表盘会根据此字段进行过滤。

向应用发送几次请求,例如执行 curl http://localhost:3000/。

不依赖 Elysia 为 Bun 插桩

Bun.serve 不支持自动插桩,因此需要在 fetch 处理程序中为每个请求手动创建一个 span。您可以获取调用链和运行时指标,但无法获得由 Elysia 插件生成的 http.server.request.duration 指标。

安装以下包:

Copy
bun add @opentelemetry/api \
  @opentelemetry/sdk-node \
  @opentelemetry/sdk-metrics \
  @opentelemetry/exporter-trace-otlp-proto \
  @opentelemetry/exporter-metrics-otlp-proto \
  @opentelemetry/instrumentation-runtime-node \
  @opentelemetry/instrumentation-host-metrics

在项目根目录创建 index.ts。它会启动 SDK,并为每个请求包一层 span。设置与第 3 步相同的环境变量,然后运行 bun run index.ts。

index.ts
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto'
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node'
import { HostMetricsInstrumentation } from '@opentelemetry/instrumentation-host-metrics'
import { SpanKind, SpanStatusCode, context, propagation, trace } from '@opentelemetry/api'
 
const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter(),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter(),
    exportIntervalMillis: 15000,
  }),
  instrumentations: [
    new RuntimeNodeInstrumentation(),
    new HostMetricsInstrumentation(),
  ],
})
sdk.start()
 
const tracer = trace.getTracer('bun-serve-app')
 
Bun.serve({
  port: 3000,
  fetch(req) {
    const url = new URL(req.url)
    const parentContext = propagation.extract(context.active(), req.headers, {
      get: (headers, key) => headers.get(key) ?? undefined,
      keys: (headers) => [...headers.keys()],
    })
    return tracer.startActiveSpan(`${req.method} ${url.pathname}`, { kind: SpanKind.SERVER }, parentContext, (span) => {
      span.setAttribute('http.request.method', req.method)
      span.setAttribute('url.path', url.pathname)
      try {
        const res = new Response('Hello from Bun')
        span.setAttribute('http.response.status_code', res.status)
        return res
      } catch (err) {
        span.recordException(err as Error)
        span.setStatus({ code: SpanStatusCode.ERROR })
        span.setAttribute('http.response.status_code', 500)
        return new Response('Internal Server Error', { status: 500 })
      } finally {
        span.end()
      }
    })
  },
})

调用 propagation.extract 会读取传入的 traceparent 头部信息,使当前 span 加入调用方服务的 trace。若处理函数为 async,请将回调设为 async,并在 await 响应完成后调用 span.end()。由于 span 名称直接使用原始 URL 路径,带 ID 的路由会产生大量 span 名称。建议改用路由器中的路由模式。

验证

运行插桩后的应用,确认 traces 和 metrics 已到达 SigNoz:

  1. 向应用发送若干请求。
  2. 打开 Services 选项卡,查找在 OTEL_SERVICE_NAME 中设置的服务名称。
  3. 打开 Metrics Explorer,查看 http.server.request.duration(仅限 Elysia)和 v8js.memory.heap.used。
SigNoz Traces Explorer 列表视图,显示按方法和路由命名的 Elysia 请求 spans 及其 Handle 子 spans
Traces Explorer 中的 Elysia 请求 spans
SigNoz Metrics Explorer 摘要,列出 Bun 服务的 http.server.request.duration.bucket 和运行时指标
Metrics Explorer 中的 Bun 和 Elysia 指标

局限性

以下限制适用于 @elysia/opentelemetry 版本 1.4.12。

  • http.server.request.duration 缺少 http.route 属性。需查询 traces 以按路由细分流量。
  • http.server.request.duration 记录的持续时间远小于实际请求时间,因为该插件在路由处理函数完成前就进行了记录。请以 traces 中的 span 持续时间作为延迟参考。
  • Bun 始终将 nodejs.eventloop.utilization 报告为 0。nodejs.eventloop.delay.* 指标正常可用。
  • Bun 不报告 process.memory.usage。内存监控请使用 v8js.memory.heap.used。

故障排除

还看不到数据?请参见排查缺失的链路、日志与指标。

链路可见但缺少http.server.request.duration

  • 可能的原因:项目使用了@elysiajs/opentelemetry,该包本身不包含 HTTP 指标,或者插件中未配置metricReader。若没有<
原始来源: signoz

评论 (0)