Skip to content
Our Sponsors
Open in Anthropic

title: OpenTelemetry 插件 - ElysiaJS head: - - meta - property: 'og:title' content: OpenTelemetry 插件 - ElysiaJS

- - meta
  - name: 'description'
    content: 一个为 Elysia 添加 OpenTelemetry 支持的插件。通过运行 "bun add @elysiajs/opentelemetry" 开始安装该插件。

- - meta
  - name: 'og:description'
    content: 一个为 Elysia 添加 OpenTelemetry 支持的插件。通过运行 "bun add @elysiajs/opentelemetry" 开始安装该插件。

OpenTelemetry

要开始使用 OpenTelemetry,请安装 @elysiajs/opentelemetry 并将插件应用于任何实例。

typescript
import { Elysia } from 'elysia'
import { opentelemetry } from '@elysiajs/opentelemetry'

new Elysia()
	.use(opentelemetry())

OpenTelemetry visualize via Axiom

为什么在 Elysia 中使用 OpenTelemetry?

  • 1 行配置
  • Span 名称即为函数名
  • 将相关的生命周期分组在一起
  • 包装代码以记录特定部分
  • 支持 Server-Sent Event 和响应流
  • 兼容任何兼容 OpenTelemetry 的库

您可以将遥测数据导出到 Jaeger、Zipkin、New Relic、Axiom 或任何其他兼容 OpenTelemetry 的后端。

导出 OpenTelemetry 数据

我们可以将 OpenTelemetry 数据导出到任何支持 OpenTelemetry 协议的后端。

以下是将遥测数据导出到 Axiom 的示例:

typescript
import { Elysia } from 'elysia'
import { opentelemetry } from '@elysiajs/opentelemetry'

import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'

new Elysia().use(
	opentelemetry({
		spanProcessors: [
			new BatchSpanProcessor(
				new OTLPTraceExporter({
					url: 'https://api.axiom.co/v1/traces', 
					headers: {
						Authorization: `Bearer ${Bun.env.AXIOM_TOKEN}`, 
						'X-Axiom-Dataset': Bun.env.AXIOM_DATASET
					} 
				})
			)
		]
	})
)

OpenTelemetry SDK

Elysia OpenTelemetry 仅用于将 OpenTelemetry 应用于 Elysia 服务器。

您可以正常使用 OpenTelemetry SDK,如果 span 在 Elysia 的请求 span 下运行,它将自动出现在 Elysia 的追踪中。

但是,我们也提供了 getTracerrecord 工具函数,用于从应用程序的任何部分收集 span。

typescript
import { Elysia } from 'elysia'
import { record } from '@elysiajs/opentelemetry'

export const plugin = new Elysia().get('', () => {
	return record('数据库查询', () => {
		return db.query('SELECT * FROM users')
	})
})

record 工具函数

record 等同于 OpenTelemetry 的 startActiveSpan,但它会自动处理关闭和捕获异常。

您可以将 record 视为代码的标签,它将在追踪中显示。

准备您的代码库以实现可观测性

Elysia OpenTelemetry 将对生命周期进行分组,并将每个钩子的函数名读取为 span 的名称。

现在是为您的函数命名的好时机。

如果您的钩子处理器是箭头函数,您可以将其重构为命名函数以便更好地理解追踪,否则您的追踪 span 将被命名为 anonymous

typescript
const bad = new Elysia()
	// ⚠️ span 名称将是 anonymous
	.derive(async ({ cookie: { session } }) => {
		return {
			user: await getProfile(session)
		}
	})

const good = new Elysia()
	// ✅ span 名称将是 getProfile
	.derive(async function getProfile({ cookie: { session } }) {
		return {
			user: await getProfile(session)
		}
	})

getCurrentSpan

当您在处理器之外时,getCurrentSpan 是一个用于获取当前请求的当前 span 的工具函数。

typescript
import { getCurrentSpan } from '@elysiajs/opentelemetry'

function utility() {
	const span = getCurrentSpan()
	span.setAttributes({
		'custom.attribute': 'value'
	})
}

它通过从 AsyncLocalStorage 检索当前 span 来在处理器之外工作。

setAttributes

setAttributes 是一个用于为当前 span 设置属性的工具函数。

typescript
import { setAttributes } from '@elysiajs/opentelemetry'

function utility() {
	setAttributes({
		'custom.attribute': 'value'
	})
}

这是 getCurrentSpan().setAttributes 的语法糖。

配置

有关配置选项和定义,请参阅 opentelemetry 插件

Instrumentations 高级概念

许多 instrumentation 库要求 SDK 必须在导入模块之前运行。

例如,要使用 PgInstrumentationOpenTelemetry SDK 必须在导入 pg 模块之前运行。

要在 Bun 中实现这一点,我们可以:

  1. 将 OpenTelemetry 设置分离到另一个文件中
  2. 创建 bunfig.toml 来预加载 OpenTelemetry 设置文件

让我们在 src/instrumentation.ts 中创建一个新文件:

ts
import { opentelemetry } from '@elysiajs/opentelemetry'
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg'

export const instrumentation = opentelemetry({
	instrumentations: [new PgInstrumentation()]
})

然后我们可以将这个 instrumentaiton 插件应用到 src/index.ts 中的主实例:

ts
import { Elysia } from 'elysia'
import { instrumentation } from './instrumentation.ts'

new Elysia().use(instrumentation).listen(3000)

接着创建一个包含以下内容的 bunfig.toml

toml
preload = ["./src/instrumentation.ts"]

这将告诉 Bun 在运行 src/index.ts 之前加载并设置 instrumentation,从而允许 OpenTelemetry 根据需要进行设置。

部署到生产环境 高级概念

如果您正在使用 bun build 或其他打包器。

由于 OpenTelemetry 依赖于对 node_modules/<library> 的 monkey-patching。为了确保 instrumentations 正常工作,我们需要指定要被 instrument 的库是一个外部模块,以将其排除在打包之外。

例如,如果您正在使用 @opentelemetry/instrumentation-pg 来 instrument pg 库。我们需要排除 pg 不被打包,并确保它从 node_modules/pg 导入。

为了实现这一点,我们可以使用 --external pgpg 指定为外部模块:

bash
bun build --compile --external pg --outfile server src/index.ts

这告诉 bun 不要将 pg 打包到最终的输出文件中,并将在运行时从 node_modules 目录导入。因此,在生产服务器上,您也必须保留 node_modules 目录。

建议将在生产服务器中需要的包在 package.json 中指定为 dependencies,并使用 bun install --production 来仅安装生产依赖。

json
{
	"dependencies": {
		"pg": "^8.15.6"
	},
	"devDependencies": {
		"@elysiajs/opentelemetry": "^1.2.0",
		"@opentelemetry/instrumentation-pg": "^0.52.0",
		"@types/pg": "^8.11.14",
		"elysia": "^1.2.25"
	}
}

然后,在运行构建命令后,在生产服务器上:

bash
bun install --production

如果 node_modules 目录仍然包含开发依赖,您可以删除 node_modules 目录并重新安装生产依赖。