Skip to content
Elogs
Esc
navigateopen⌘Jpreview
On this page

OpenTelemetry

把日志跟活动 trace 和 span 关联起来

Elogs 提供子路径 @eastgold15/elogs/otel 把 OpenTelemetry 活动 span ID 合并到请求 context 包,access log 自动带上 trace_id / span_id

安装

@opentelemetry/api 是可选 peer dep —— 装了就有 trace context,没装函数静默返回 undefined

bun add @opentelemetry/api

基本用法

@eastgold15/elogs/otel 子路径导入 injectTraceContext,在 Elysia 的 .request() 钩子里调用一次,后续所有 hook 都能看到 trace_id / span_id:

import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'
import { injectTraceContext } from '@eastgold15/elogs/otel'

const app = new Elysia()
  .use(createElogs())
  .request(({ request, store }) => {
    injectTraceContext(store.logger, request)
  })
  .get('/', () => 'ok')

当存在 active span 时,trace_idspan_id 合并到请求 context 包,出现在 access log:

🦊 2025-12-21 10:00:00.225 INFO  12.34ms GET / 200
└─ trace: { trace_id: '8c2f…', span_id: '4a1d…' }

Elogs 启动 OpenTelemetry SDK —— 你需要自己装 tracer provider 和 instrumentation,injectTraceContext 才找得到活动 span。

API

function injectTraceContext(
  logger: Pick<Logger, 'mergeContext'>,
  request: Request
): Promise<TraceContextFields | undefined>

interface TraceContextFields {
  trace_id: string
  span_id: string
}
  • injectTraceContext 是 async 的,因为它要动态 import('@opentelemetry/api') 来保持 @opentelemetry/api 的可选 peer dep 地位 —— 只有调用这个函数时才会发生 import。
  • 模块没装时,函数返回 undefined,什么都不输出。
  • 没有 active span 时,函数同样返回 undefined,logger.mergeContext 不会被调用。

工作机制

  1. 函数从 OpenTelemetry context API 查当前 active span(trace.getSpan(context.active()))。
  2. 如果有 span,读 spanContext().traceIdspanContext().spanId
  3. 这一对字段合并到请求 context 的 trace: { trace_id, span_id }(通过 logger.mergeContext(request, { trace: { trace_id, span_id } }))。
  4. 合并后的 context 出现在最终 access log,跟用户合并的 context(比如 context.userId)并列。

完整 setup

最小的 OpenTelemetry setup 长这样:

// tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter(),
  instrumentations: [getNodeAutoInstrumentations()],
})

sdk.start()

然后在 Elysia app 里:

// app.ts
import { Elysia } from 'elysia'
import { createElogs } from '@eastgold15/elogs'
import { injectTraceContext } from '@eastgold15/elogs/otel'
import './tracing' // 先启动 OTel SDK

const app = new Elysia()
  .use(createElogs())
  .request(({ request, store }) => {
    injectTraceContext(store.logger, request)
  })
  .get('/', () => 'ok')
  .listen(3000)

跟 request ID 配合

request ID 和 trace ID 是独立的关联键:

  • requestId 是你的应用层关联 ID(X-Request-Id header)。在纯文本日志里好 grep。
  • trace_id 是 OpenTelemetry 的 trace ID。在分布式追踪 UI(Jaeger、Honeycomb 等)里用。

两个都放进 customLogFormat:

import { createElogs } from '@eastgold15/elogs'

createElogs({
  config: {
    requestId: true,
    customLogFormat:
      '{now} {level} {method} {pathname} {status} {duration}ms req={requestId} trace={context.trace.trace_id}',
  },
})

或者不设 customLogFormat,让 showContextTree 把整个 context 对象作为树形行打印。

相关 API

Last updated on August 15, 2026

Was this page helpful?