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 收集器,可以使用以下命令快速启动 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 发出请求时,系统会为该请求创建一个跨度,且在收到响应头时自动结束。

创建的跨度名称为 ${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

一个直方图,记录传入 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

一个直方图,记录传入 HTTP 请求的响应体大小,单位为字节。直方图桶边界同请求体大小指标。

记录属性:

  • 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,
});

Spans can also have events and links added to them. Events are points in time that are associated with the span. Links are references to other spans.

// 向跨度添加事件
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_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 的 fetchDeno.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,其他导出器未支持。
  • OTLP 仅支持 http/protobufhttp/json 协议,不支持 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 方法不会在 http.request.method 跨度属性中标准化为 _OTHER,与 OpenTelemetry 语义约定不符。
  • Deno.serve 的 HTTP 服务器跨度在处理程序抛出(调用 onError)时不会设置错误状态,错误也不通过事件附加到跨度。
  • fetch 的 HTTP 客户端跨度中无机制添加 http.route 属性或更新跨度名称以包含路由信息。

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

编辑此页面
隐私政策