Skip to main content
On this page

OpenTelemetry

Deno 内置支持 OpenTelemetry

OpenTelemetry 是一组 API、SDK 和工具。使用它来对软件进行仪器化,生成、收集和导出遥测数据(指标、日志和追踪)以帮助您分析软件的性能和行为。

- https://opentelemetry.io/

此集成让您能够使用 OpenTelemetry 的可观察性工具(如日志、指标和追踪)来监控您的 Deno 应用程序。

Deno 提供以下功能:

快速开始 Jump to heading

要启用 OpenTelemetry 集成,请设置环境变量 OTEL_DENO=true

>_
OTEL_DENO=true deno run my_script.ts

这将自动收集并通过 HTTP 的 Protobuf 格式(http/protobuf)将运行时可观察性数据导出到 localhost:4318 的 OpenTelemetry 端点。

Tip

如果您想在不设置收集器的情况下快速查看 OpenTelemetry 输出, 可以使用内置的控制台导出器将跨度、日志和指标 直接以人类可读的格式打印到 stderr:

>_
OTEL_DENO=true OTEL_EXPORTER_OTLP_PROTOCOL=console deno run my_script.ts

示例输出:

SPAN inner span [00000000000000000000000000000001/0000000000000002] Internal 0.495ms
  parent: 0000000000000001
  scope: example-tracer
  key: value
LOG [INFO] 2025-03-14T13:47:07.235Z "hello from inner"
  scope: deno@2.7.5
  trace: 00000000000000000000000000000001/0000000000000002

Tip

如果您想在仪表盘中可视化遥测数据, 可以通过运行以下命令,使用 Docker 中的本地 LGTM 栈 (Loki(日志)、Grafana(仪表盘)、Tempo(追踪)和 Prometheus(指标))开始使用:

>_
docker run --name lgtm -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti \
	-v "$PWD"/lgtm/grafana:/data/grafana \
	-v "$PWD"/lgtm/prometheus:/data/prometheus \
	-v "$PWD"/lgtm/loki:/data/loki \
	-e GF_PATHS_DATA=/data/grafana \
	docker.io/grafana/otel-lgtm:0.8.1

然后,您可以使用用户名 admin 和密码 admin 登录 Grafana 仪表盘,地址为 http://localhost:3000

这将自动收集并导出运行时的可观察性数据,例如 console.log、HTTP 请求的追踪和 Deno 运行时指标。 了解更多关于自动仪器化的信息

您也可以使用 npm:@opentelemetry/api 包创建自己的指标、追踪和日志。 了解更多关于用户定义的指标

自动仪器化 Jump to heading

Deno 会自动收集并将部分可观察性数据导出到 OTLP 端点。

这些数据通过名为 deno 的内置仪器化范围导出,该范围名称为 deno,版本即 Deno 运行时的版本。例如,deno:2.1.4

追踪 Jump to heading

Deno 会自动为多种操作创建跨度,例如:

  • 使用 Deno.serve 处理的入站 HTTP 请求。
  • 使用 fetch 发出的出站 HTTP 请求。

Deno.serve Jump to heading

当您使用 Deno.serve 创建 HTTP 服务器时, 会为每个传入请求创建一个跨度。响应头发送后,跨度会自动结束 (而不是在响应主体发送完成时)。

创建的跨度名称为 ${method},跨度类型为 server

创建跨度时自动添加如下属性:

  • http.request.method:请求的 HTTP 方法。
  • url.full:请求的完整 URL(等同于 req.url)。
  • url.scheme:请求 URL 的协议(例如 httphttps)。
  • url.path:请求 URL 的路径部分。
  • url.query:请求 URL 的查询字符串。

请求处理完成后,会添加下列属性:

  • http.response.status_code:响应的状态码。

Deno 不会自动向跨度添加 http.route 属性,因为运行时无法知道路由细节,路由由用户处理函数中的逻辑决定。如需添加 http.route,请在处理函数中使用 npm:@opentelemetry/api 设置该属性,同时建议更新跨度名称以包含路由信息。

import { trace } from "npm:@opentelemetry/api@1";

const INDEX_ROUTE = new URLPattern({ pathname: "/" });
const BOOK_ROUTE = new URLPattern({ pathname: "/book/:id" });

Deno.serve(async (req) => {
  const span = trace.getActiveSpan();
  if (INDEX_ROUTE.test(req.url)) {
    span.setAttribute("http.route", "/");
    span.updateName(`${req.method} /`);

    // 处理首页路由
  } else if (BOOK_ROUTE.test(req.url)) {
    span.setAttribute("http.route", "/book/:id");
    span.updateName(`${req.method} /book/:id`);

    // 处理图书路由
  } else {
    return new Response("未找到", { status: 404 });
  }
});

fetch Jump to heading

当您使用 fetch 发起 HTTP 请求时, 会为该请求创建一个跨度。响应头收到后,跨度会自动结束。

创建的跨度名称为 ${method},跨度类型为 client

创建跨度时自动添加如下属性:

  • http.request.method:请求的 HTTP 方法。
  • url.full:请求的完整 URL。
  • url.scheme:请求 URL 的协议。
  • url.path:请求 URL 的路径部分。
  • url.query:请求 URL 的查询字符串。

收到响应后,添加以下属性:

  • http.status_code:响应的状态码。

指标 Jump to heading

自动收集和导出的指标包括:

Deno.serve / Deno.serveHttp Jump to heading

http.server.request.duration Jump to heading

使用 Deno.serveDeno.serveHttp 处理的传入 HTTP 请求持续时间的直方图。 测量时间从收到请求开始,到发送响应头为止。 不包括发送响应主体所需的时间。该指标的单位为秒。 直方图桶为 [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0]

该指标记录以下属性:

  • http.request.method:请求的 HTTP 方法。
  • url.scheme:请求 URL 的协议。
  • network.protocol.version:请求所使用的 HTTP 协议版本(例如 1.12)。
  • server.address:服务器监听的地址。
  • server.port:服务器监听的端口。
  • http.response.status_code:响应状态码(请求无致命错误时)。
  • error.type:发生的错误类型(请求处理出现错误时)。
http.server.active_requests Jump to heading

在任意时刻,由 Deno.serveDeno.serveHttp 处理的活动请求数量的仪表。 这指的是已经收到但尚未响应的请求数量(即响应头尚未发送)。 该指标记录以下属性:

  • http.request.method:请求的 HTTP 方法。
  • url.scheme:请求 URL 的协议。
  • server.address:服务器监听的地址。
  • server.port:服务器监听的端口。
http.server.request.body.size Jump to heading

使用 Deno.serveDeno.serveHttp 处理的传入 HTTP 请求请求体大小的直方图。 该指标的单位为字节。直方图桶为 [0, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]

记录属性:

  • http.request.method:请求的 HTTP 方法。
  • url.scheme:请求 URL 的协议。
  • network.protocol.version:请求所使用的 HTTP 协议版本。
  • server.address:服务器监听的地址。
  • server.port:服务器监听的端口。
  • http.response.status_code:响应状态码(无致命错误时)。
  • error.type:错误类型(处理异常时)。
http.server.response.body.size Jump to heading

使用 Deno.serveDeno.serveHttp 处理的传入 HTTP 请求响应体大小的直方图。 该指标的单位为字节。直方图桶为 [0, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000]

记录属性:

  • http.request.method:请求的 HTTP 方法。
  • url.scheme:请求 URL 的协议。
  • network.protocol.version:请求所使用的 HTTP 协议版本。
  • server.address:服务器监听的地址。
  • server.port:服务器监听的端口。
  • http.response.status_code:响应状态码(无致命错误时)。
  • error.type:错误类型(处理异常时)。

日志 Jump to heading

自动收集并导出的日志包括:

  • 使用 console.* 方法(如 console.logconsole.error)产生的任意日志。
  • 由 Deno 运行时产生的日志,如调试日志、下载日志等。
  • 导致 Deno 运行时退出的任何错误(无论来自用户代码还是运行时本身)。

在 JavaScript 代码中发生的日志会携带相关的跨度上下文(如果日志发生在活跃跨度内)。

console 自动仪器化可以通过环境变量 OTEL_DENO_CONSOLE 配置:

  • capture:日志既会输出到 stdout/stderr,也会被导出到 OpenTelemetry。(默认)
  • replace:日志不输出到 stdout/stderr,只有导出到 OpenTelemetry。
  • ignore:日志只输出到 stdout/stderr,不导出到 OpenTelemetry。

用户指标 Jump to heading

除自动收集的遥测数据外,您还可以使用 npm:@opentelemetry/api 包创建自己的指标和追踪。

您无需特别配置 npm:@opentelemetry/api 包以便在 Deno 中使用,Deno 会自动完成配置。无需调用 metrics.setGlobalMeterProvider()trace.setGlobalTracerProvider()context.setGlobalContextManager()。所有资源配置、导出设置等均通过环境变量实现。

Deno 与版本 1.xnpm:@opentelemetry/api 包兼容。您既可以直接从 npm:@opentelemetry/api@1 导入,也可以使用 deno add 本地安装该包后由 @opentelemetry/api 导入。

>_
deno add npm:@opentelemetry/api@1

在追踪和指标中,您需要为跟踪器和仪表分别命名。如果您为库做仪器化,应使用库名(如 my-awesome-lib);如果为应用程序做仪器化,应使用应用名(如 my-app)。版本应设置为库或应用的版本。

追踪 Jump to heading

要创建新跨度,先从 npm:@opentelemetry/api 导入 trace 并获取一个跟踪器:

import { trace } from "npm:@opentelemetry/api@1";

const tracer = trace.getTracer("my-app", "1.0.0");

之后使用 tracer.startActiveSpan 创建一个新的跨度并传入回调。需在回调中手动调用 span.end() 结束该跨度。

function myFunction() {
  return tracer.startActiveSpan("myFunction", (span) => {
    try {
      // 执行 myFunction 的工作
    } catch (error) {
      span.recordException(error);
      span.setStatus({
        code: trace.SpanStatusCode.ERROR,
        message: (error as Error).message,
      });
      throw error;
    } finally {
      span.end();
    }
  });
}

应在 finally 中调用 span.end() 以保证跨度结束,无论是否发生异常。异常时调用 span.recordExceptionspan.setStatus 以便于记录。

在回调内部,创建的跨度是“活动跨度”,可使用 trace.getActiveSpan() 访问。此“活动跨度”在回调及其调用函数内使用时会作为父跨度。 了解更多关于上下文传播的信息

startActiveSpan 返回回调函数的返回值。

在跨度生命周期内,您可以调用 setAttributesetAttributes 添加属性,即结构化元数据键值对。

span.setAttribute("key", "value");
span.setAttributes({ success: true, "bar.count": 42n, "foo.duration": 123.45 });

属性值支持字符串、数字(浮点)、大整数(限制为 u64)、布尔值,或者这些类型的数组。其它类型会被忽略。

可以用 updateName 方法更改跨度名称:

span.updateName("new name");

且可用 setStatus 设置跨度状态。recordException 用于记录生命周期内的异常,创建带堆栈跟踪和名称的事件附加到跨度。recordException 不会设置状态为 ERROR,需手动调用 setStatus

import { SpanStatusCode } from "npm:@opentelemetry/api@1";

span.setStatus({
  code: SpanStatusCode.ERROR,
  message: "发生了一个错误",
});
span.recordException(new Error("发生了一个错误"));

// 或

span.setStatus({
  code: SpanStatusCode.OK,
});

跨度还可以添加 事件链接。 事件是与跨度关联的时间点。 链接是对其他跨度的引用。

// 向跨度添加事件
span.addEvent("button_clicked", {
  id: "submit-button",
  action: "submit",
});

// 带时间戳的事件
span.addEvent("process_completed", { status: "success" }, Date.now());

事件可包含类似于跨度的可选属性。它们用于标记跨度生命周期内重要时刻,无需创建额外跨度。

您也可以用 tracer.startSpan 手动创建跨度,该方法返回跨度对象。此方法不会设置创建的跨度为活动跨度,故后续创建的跨度或 console.log 不会自动继承它。可配合 上下文传播 API 手动将此跨度设置为活动跨度。

tracer.startActiveSpantracer.startSpan 可接受含以下任意属性的选项参数:

  • kind: 跨度类型。可为 SpanKind.CLIENTSpanKind.SERVERSpanKind.PRODUCERSpanKind.CONSUMERSpanKind.INTERNAL。默认值为 SpanKind.INTERNAL
  • startTime:表示跨度开始时间的 Date 对象,或自 Unix 纪元起的毫秒数。如果未提供,则使用当前时间。
  • attributes: 要添加到跨度的属性对象。
  • links: 要添加到跨度的链接数组。
  • root: 布尔值,表示跨度是否为根跨度。如果为 true,则该跨度没有父跨度(即使存在活动跨度)。

在选项参数之后,tracer.startActiveSpantracer.startSpan 还可以接收来自 上下文传播 APIcontext 对象。

了解完整追踪 API 请参考 OpenTelemetry JS API 文档

指标 Jump to heading

要创建指标,首先从 npm:@opentelemetry/api 导入 metrics 对象并创建一个仪表:

import { metrics } from "npm:@opentelemetry/api@1";

const meter = metrics.getMeter("my-app", "1.0.0");

然后用仪表创建仪器,用于记录数值:

const counter = meter.createCounter("my_counter", {
  description: "一个简单的计数器",
  unit: "1",
});

counter.add(1);
counter.add(2);

记录时也可附带属性:

counter.add(1, { color: "red" });
counter.add(2, { color: "blue" });

Tip

OpenTelemetry 中建议指标属性保持低基数,即属性值的唯一组合数量不宜过多。例如,用户所属洲的属性是低基数,但用户的精确经纬度则属于高基数。高基数属性可能导致指标存储与导出问题,推荐通过跨度和日志处理高基数数据。

常见仪器类型:

  • 计数器:单调递增的数值,只能增加。适合计数处理请求数等。
  • 上下计数器:可增可减,适合表示活动连接数、进行中请求数。
  • 仪表:可以任意设置值,适合非累积值,如当前温度。
  • 直方图:记录数值分布。例如请求响应时间(毫秒),用于计算百分位、平均等统计。预定义桶边界默认为 [0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0, 2500.0, 5000.0, 7500.0, 10000.0]

还有几类可观察(异步)仪器,没有同步记录方法,而是提供回调用于异步上报值,OpenTelemetry SDK 在导出前调用回调。

const counter = meter.createObservableCounter("my_counter", {
  description: "一个简单的计数器",
  unit: "1",
});
counter.addCallback((res) => {
  res.observe(1);
  // 或
  res.observe(1, { color: "red" });
});

存在三种可观察仪器类型:

  • ObservableCounter:异步可观察的计数器。用于常增值,如处理的请求数。
  • ObservableUpDownCounter:异步可观察的上下计数器。值能增减,如活动连接数或进行中请求数。
  • ObservableGauge:异步可观察的仪表。用于任意值,如当前温度。

了解完整指标 API 请参考 OpenTelemetry JS API 文档

实际示例 Jump to heading

有关在 Deno 应用中实现 OpenTelemetry 的实际示例,请参见教程:

上下文传播 Jump to heading

在 OpenTelemetry 中,上下文传播是指将一些上下文信息(如当前跨度)从应用的一个部分传递到另一个部分,而无需手动将其作为参数传递给每个函数。

在 Deno 中,上下文传播遵循 TC39 提案的 AsyncContext 规则。AsyncContext API 尚未向用户公开,但内部用于在异步边界上传播活动跨度和其他上下文信息。

简要说明 AsyncContext 传播工作方式:

  • 当启动一个新的异步任务(例如 Promise 或定时器)时,当前上下文会被保存。
  • 其他代码可以在不同上下文中并发执行。
  • 当异步任务完成时,保存的上下文被恢复。

这意味着异步上下文传播类似于一个全局变量,其作用域限定于当前异步任务,并自动拷贝到由当前任务启动的新异步任务中。

来自 npm:@opentelemetry/api@1context API 向用户暴露此功能。用法如下:

import { context } from "npm:@opentelemetry/api@1";

// 获取当前活动上下文
const currentContext = context.active();

// 创建新的上下文并设值
const newContext = currentContext.setValue("id", 1);

// 设值不改变当前上下文
console.log(currentContext.getValue("id")); // undefined

// 在新上下文内执行
context.with(newContext, () => {
  console.log(context.active().getValue("id")); // 1

  function myFunction() {
    return context.active().getValue("id");
  }
  console.log(myFunction()); // 1

  setTimeout(() => {
    console.log(context.active().getValue("id")); // 1
  }, 10);
});

// 外部上下文未变
console.log(context.active().getValue("id")); // undefined

上下文 API 与跨度集成,您可以将跨度装入上下文并在上下文中执行函数:

import { context, trace } from "npm:@opentelemetry/api@1";

const tracer = trace.getTracer("my-app", "1.0.0");

const span = tracer.startSpan("myFunction");
const contextWithSpan = trace.setSpan(context.active(), span);

context.with(contextWithSpan, () => {
  const activeSpan = trace.getActiveSpan();
  console.log(activeSpan === span); // true
});

// 记得结束跨度!
span.end();

了解完整上下文 API 请参考 OpenTelemetry JS API 文档

配置 Jump to heading

通过设置环境变量 OTEL_DENO=true 启用 OpenTelemetry 集成。

OTLP 导出器的端点和协议可通过 OTEL_EXPORTER_OTLP_ENDPOINTOTEL_EXPORTER_OTLP_PROTOCOL 环境 变量配置。OTEL_EXPORTER_OTLP_PROTOCOL 支持的值为:

  • http/protobuf(默认):通过 HTTP 使用 Protobuf 导出到配置的 端点。
  • http/json:通过 HTTP 使用 JSON 导出到配置的端点。
  • console:以人类可读的文本格式将跨度、日志和指标打印到 stderr。 这对于在不运行 OTLP 收集器的情况下调试和测试埋点很有用。使用 console 时,不需要配置端点。

若端点需要身份验证,可使用环境变量 OTEL_EXPORTER_OTLP_HEADERS 设置请求头。

指标、追踪和日志导出的端点也可用以下专用变量分别覆盖,例如:

  • OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
  • OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
  • OTEL_EXPORTER_OTLP_LOGS_ENDPOINT

有关 OTLP 头部配置详情,请参考 OpenTelemetry 官网

遥测数据关联的资源属性通过环境变量 OTEL_SERVICE_NAMEOTEL_RESOURCE_ATTRIBUTES 配置。除 OTEL_RESOURCE_ATTRIBUTES 设置的属性外,自动添加:

  • service.name:未设置 OTEL_SERVICE_NAME 时值为 <unknown_service>
  • process.runtime.namedeno
  • process.runtime.version:Deno 运行时版本。
  • telemetry.sdk.namedeno-opentelemetry
  • telemetry.sdk.languagedeno-rust
  • telemetry.sdk.version:Deno 版本与使用的 opentelemetry Rust crate 版本,通过 - 连接。

传播器可通过 OTEL_PROPAGATORS 环境变量配置,默认值为 tracecontext,baggage,用逗号分隔多项,目前支持:

  • tracecontext:W3C Trace Context 格式。
  • baggage:W3C Baggage 格式。

指标收集频率使用 OTEL_METRIC_EXPORT_INTERVAL 配置,默认 60000 毫秒(60 秒)。

跨度导出批处理配置参考 OpenTelemetry 规范

日志导出批处理配置参考 OpenTelemetry 规范

传播器 Jump to heading

Deno 支持上下文传播器,用以自动跨进程边界传播追踪上下文,实现分布式追踪,跟踪请求在服务链中流转。

传播器负责在载体中编码(如 HTTP 头)并解码上下文信息(如追踪与跨度 ID),从而实现服务间追踪上下文传递。

默认支持以下传播器:

  • tracecontext:W3C Trace Context 传播格式,是 HTTP 头传播追踪上下文的标准方式。
  • baggage:W3C Baggage 格式,允许跨服务传递键值对。

Note

这些传播器会自动与 Deno 的 fetch API 以及 Deno.serve 协同工作,无需手动管理上下文即可实现跨 HTTP 请求的端到端追踪。

您可以通过 @opentelemetry/api 包访问传播 API:

import { context, propagation, trace } from "npm:@opentelemetry/api@1";

// 从传入 HTTP 头提取上下文
function extractContextFromHeaders(headers: Headers) {
  const ctx = context.active();
  return propagation.extract(ctx, headers);
}

// 向传出 HTTP 头注入上下文
function injectContextIntoHeaders(headers: Headers) {
  const ctx = context.active();
  propagation.inject(ctx, headers);
  return headers;
}

// 示例:进行带有传播追踪上下文的 fetch 请求
async function tracedFetch(url: string) {
  const headers = new Headers();
  injectContextIntoHeaders(headers);

  return await fetch(url, { headers });
}

限制 Jump to heading

Deno 的 OpenTelemetry 集成仍在开发中,存在以下限制:

  • 跟踪总是采样的(即 OTEL_TRACE_SAMPLER=parentbased_always_on)。
  • 跟踪仅支持不带属性的链接。
  • 不支持指标样本点。
  • 不支持自定义日志流(例如除 console.logconsole.error 之外的日志)。
  • 支持的导出器为 OTLP(http/protobufhttp/json)和 console。 不支持其他导出器和协议,例如 grpc
  • 进程退出/崩溃时,不会收集可观测(异步)仪表的指标,因此指标的最后值可能不会被导出。同步 指标会在进程退出/崩溃时导出。
  • OTEL_ATTRIBUTE_VALUE_LENGTH_LIMITOTEL_ATTRIBUTE_COUNT_LIMITOTEL_SPAN_EVENT_COUNT_LIMITOTEL_SPAN_LINK_COUNT_LIMITOTEL_EVENT_ATTRIBUTE_COUNT_LIMITOTEL_LINK_ATTRIBUTE_COUNT_LIMIT 环境变量中指定的限制对 跟踪跨度不生效。
  • OTEL_METRIC_EXPORT_TIMEOUT 环境变量不生效。
  • 未知的 HTTP 方法不会按照 OpenTelemetry 语义约定 在 http.request.method 跨度属性中规范化为 _OTHER
  • Deno.serve 的 HTTP 服务端跨度没有设置 OpenTelemetry 状态,并且如果处理器抛出错误(即调用了 onError), 该跨度不会设置错误状态,错误也不会通过事件附加到跨度上。
  • 没有机制可以为 fetch 的 HTTP 客户端跨度添加 http.route 属性,或者将跨度名称更新为包含该路由。

Last updated on

你找到了你需要的东西吗?

编辑此页面
隐私政策