deno.com
本页内容

OpenTelemetry

注意

Deno 的 OpenTelemetry 集成仍在开发中,可能会有变化。要使用它,您必须向 Deno 传递 --unstable-otel 标志。

Deno 内置支持 OpenTelemetry

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

- https://opentelemetry.io/

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

Deno 提供以下功能

快速入门 跳转到标题

要启用 OpenTelemetry 集成,请使用 --unstable-otel 标志运行 Deno 脚本并设置环境变量 OTEL_DENO=true

OTEL_DENO=true deno run --unstable-otel my_script.ts

这将自动收集运行时可观测性数据并将其导出到 localhost:4318 的 OpenTelemetry 端点,使用基于 HTTP 的 Protobuf (http/protobuf)。

提示

如果您尚未设置 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 包创建自己的指标、追踪和日志。了解更多关于用户自定义指标的信息

自动埋点 跳转到标题

Deno 自动收集并导出一些可观测性数据到 OTLP 端点。

这些数据在 Deno 运行时的内置埋点范围中导出。该范围的名称为 deno。Deno 运行时的版本即为 deno 埋点范围的版本(例如 deno:2.1.4)。

追踪 跳转到标题

Deno 自动为各种操作创建 span,例如

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

Deno.serve 跳转到标题

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

创建的 span 的名称是 ${method}。span 类型是 server

创建时自动向 span 添加以下属性

  • http.request.method: 请求的 HTTP 方法。
  • url.full: 请求的完整 URL(如同 req.url 报告的那样)。
  • url.scheme: 请求 URL 的方案(例如 httphttps)。
  • url.path: 请求 URL 的路径。
  • url.query: 请求 URL 的查询字符串。

请求处理后,添加以下属性

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

Deno 不会自动向 span 添加 http.route 属性,因为运行时不知道路由,而是由用户处理函数中的路由逻辑确定。如果您想向 span 添加 http.route 属性,可以使用 npm:@opentelemetry/api 在处理函数中进行。在这种情况下,您还应该更新 span 名称以包含路由。

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} /`);

    // handle index route
  } else if (BOOK_ROUTE.test(req.url)) {
    span.setAttribute("http.route", "/book/:id");
    span.updateName(`${req.method} /book/:id`);

    // handle book route
  } else {
    return new Response("Not found", { status: 404 });
  }
});

fetch 跳转到标题

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

创建的 span 的名称是 ${method}。span 类型是 client

创建时自动向 span 添加以下属性

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

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

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

指标 跳转到标题

自动收集并导出以下指标

Deno.serve / Deno.serveHttp 跳转到标题

http.server.request.duration 跳转到标题

使用 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 跳转到标题

在任何给定时间,由 Deno.serveDeno.serveHttp 处理的活动请求数的度量值。这是已接收但尚未响应(响应头尚未发送)的请求数。此指标使用以下属性记录

  • http.request.method: 请求的 HTTP 方法。
  • url.scheme: 请求 URL 的方案。
  • server.address: 服务器正在监听的地址。
  • server.port: 服务器正在监听的端口。
http.server.request.body.size 跳转到标题

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

此指标使用以下属性记录

  • 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.response.body.size 跳转到标题

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

此指标使用以下属性记录

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

日志 跳转到标题

自动收集并导出以下日志

  • 使用 console.* 方法(如 console.logconsole.error)创建的任何日志。
  • Deno 运行时创建的任何日志,例如调试日志、Downloading 日志及类似日志。
  • 导致 Deno 运行时退出的任何错误(无论是来自用户代码还是运行时本身)。

如果日志发生在活动 span 内部,则从 JavaScript 代码发出的日志将与相关的 span 上下文一起导出。

console 自动埋点可以使用 OTEL_DENO_CONSOLE 环境变量进行配置

  • capture: 日志输出到 stdout/stderr,并同时使用 OpenTelemetry 导出。(默认)
  • replace: 日志仅使用 OpenTelemetry 导出,不输出到 stdout/stderr。
  • ignore: 日志仅输出到 stdout/stderr,不会使用 OpenTelemetry 导出。

用户自定义指标 跳转到标题

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

您无需配置 npm:@opentelemetry/api 包即可将其与 Deno 一起使用。当传递 --unstable-otel 标志时,Deno 会自动设置 npm:@opentelemetry/api 包。无需调用 metrics.setGlobalMeterProvider()trace.setGlobalTracerProvider()context.setGlobalContextManager()。所有资源、导出器设置等的配置都通过环境变量完成。

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

deno add npm:@opentelemetry/api@1

对于追踪和指标,您需要分别为 tracer 和 meter 定义名称。如果您正在对库进行埋点,则应以库的名称命名 tracer 或 meter(例如 my-awesome-lib)。如果您正在对应用程序进行埋点,则应以应用程序的名称命名 tracer 或 meter(例如 my-app)。tracer 或 meter 的版本应设置为库或应用程序的版本。

追踪 跳转到标题

要创建新的 span,首先从 npm:@opentelemetry/api 导入 trace 对象并创建一个新的 tracer

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

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

然后,使用 tracer.startActiveSpan 方法创建一个新的 span 并向其传递一个回调函数。您必须通过调用 startActiveSpan 返回的 span 对象的 end 方法来手动结束 span。

function myFunction() {
  return tracer.startActiveSpan("myFunction", (span) => {
    try {
      // do myFunction's work
    } catch (error) {
      span.recordException(error);
      span.setStatus({
        code: trace.SpanStatusCode.ERROR,
        message: (error as Error).message,
      });
      throw error;
    } finally {
      span.end();
    }
  });
}

span.end() 应该在 finally 块中调用,以确保即使发生错误也能结束 span。span.recordExceptionspan.setStatus 也应该在 catch 块中调用,以记录发生的任何错误。

在回调函数内部,创建的 span 是“活动 span”。您可以使用 trace.getActiveSpan() 获取活动 span。“活动 span”将用作在回调函数内部(或从回调函数调用的任何函数内部)创建的任何 span(手动或由运行时自动创建)的父 span。了解更多关于上下文传播的信息

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

Span 在其生命周期内可以添加属性。属性是表示 span 结构化元数据的键值对。可以使用 span 对象的 setAttributesetAttributes 方法添加属性。

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

属性的值可以是字符串、数字(浮点数)、大整数(钳制到 u64)、布尔值或这些类型中的任何数组。如果属性值不是这些类型之一,它将被忽略。

可以使用 span 对象的 updateName 方法更新 span 的名称。

span.updateName("new name");

可以使用 span 对象的 setStatus 方法设置 span 的状态。recordException 方法可用于记录 span 生命周期内发生的异常。recordException 会创建一个带有异常堆栈跟踪和名称的事件并将其附加到 span。recordException 不会将 span 状态设置为 ERROR,您必须手动执行此操作。

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

span.setStatus({
  code: SpanStatusCode.ERROR,
  message: "An error occurred",
});
span.recordException(new Error("An error occurred"));

// or

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

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

// Add an event to the span
span.addEvent("button_clicked", {
  id: "submit-button",
  action: "submit",
});

// Add an event with a timestamp
span.addEvent("process_completed", { status: "success" }, Date.now());

事件可以包含与 span 类似的可选属性。它们对于标记 span 生命周期内的重要时刻而无需创建单独的 span 很有用。

Span 也可以使用 tracer.startSpan 手动创建,该方法返回一个 span 对象。此方法不会将创建的 span 设置为活动 span,因此它不会自动用作以后创建的任何 span 或任何 console.log 调用的父 span。可以使用上下文传播 API 手动将 span 设置为回调的活动 span。

tracer.startActiveSpantracer.startSpan 都可以接受一个可选的 options 包,其中包含以下任何属性

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

在 options 包之后,tracer.startActiveSpantracer.startSpan 都可以接受来自上下文传播 APIcontext 对象。

OpenTelemetry JS API 文档中了解有关完整追踪 API 的更多信息。

指标 跳转到标题

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

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

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

然后,可以从 meter 创建一个仪表,并用于记录值

const counter = meter.createCounter("my_counter", {
  description: "A simple counter",
  unit: "1",
});

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

每个记录也可以有相关的属性

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

提示

在 OpenTelemetry 中,指标属性通常应具有低基数。这意味着属性值的唯一组合不应过多。例如,为用户所在的洲设置属性可能没问题,但为用户的精确纬度和经度设置属性则基数过高。高基数属性可能会导致指标存储和导出出现问题,应避免使用。对于高基数数据,请使用 span 和日志。

可以通过 meter 创建几种类型的仪表

  • Counter(计数器):计数器是单调递增的值。计数器只能是正数。它们可用于始终增加的值,例如处理的请求数。

  • UpDownCounter(增减计数器):增减计数器是可增加和减少的值。增减计数器可用于可增加和减少的值,例如活动连接数或进行中的请求数。

  • Gauge(度量计):度量计是可以设置为任何值的值。它们用于不随时间“累积”而是具有在任何给定时间的特定值的值,例如当前温度。

  • Histogram(直方图):直方图是作为值分布记录的值。直方图可用于不只是单个数字而是数字分布的值,例如请求的响应时间(毫秒)。直方图可用于计算百分位数、平均值和其他统计数据。它们具有一组预定义的边界,用于定义值放置的桶。默认情况下,边界为 [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: "A simple counter",
  unit: "1",
});
counter.addCallback((res) => {
  res.observe(1);
  // or
  res.observe(1, { color: "red" });
});

有三种类型的可观测仪表

  • ObservableCounter(可观测计数器):可观测计数器是可以异步观测的计数器。它可用于始终增加的值,例如处理的请求数。
  • ObservableUpDownCounter(可观测增减计数器):可观测增减计数器是可增加和减少的值,并且可以异步观测。增减计数器可用于可增加和减少的值,例如活动连接数或进行中的请求数。
  • ObservableGauge(可观测度量计):可观测度量计是可以设置为任何值的值,并且可以异步观测。它们用于不随时间“累积”而是具有在任何给定时间的特定值的值,例如当前温度。

OpenTelemetry JS API 文档中了解有关完整指标 API 的更多信息。

实际示例 跳转到标题

有关在 Deno 应用程序中实现 OpenTelemetry 的实际示例,请参阅我们的教程

上下文传播 跳转到标题

在 OpenTelemetry 中,上下文传播是将一些上下文信息(例如当前 span)从应用程序的一个部分传递到另一个部分的过程,而无需将其显式作为参数传递给每个函数。

在 Deno 中,上下文传播是使用 AsyncContext 的规则完成的,这是 TC39 关于异步上下文传播的提案。AsyncContext API 尚未在 Deno 中向用户公开,但它在内部用于跨异步边界传播活动 span 和其他上下文信息。

AsyncContext 传播工作原理的快速概述

  • 当一个新的异步任务启动时(例如 promise 或计时器),当前上下文被保存。
  • 然后,一些其他代码可以与异步任务同时执行,在不同的上下文中。
  • 当异步任务完成时,保存的上下文将恢复。

这意味着异步上下文传播本质上表现得像一个全局变量,它作用于当前的异步任务,并自动复制到从此当前任务启动的任何新的异步任务。

来自 npm:@opentelemetry/api@1context API 将此功能暴露给用户。它的工作方式如下

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

// Get the currently active context
const currentContext = context.active();

// You can add create a new context with a value added to it
const newContext = currentContext.setValue("id", 1);

// The current context is not changed by calling setValue
console.log(currentContext.getValue("id")); // undefined

// You can run a function inside a new context
context.with(newContext, () => {
  // Any code in this block will run with the new context
  console.log(context.active().getValue("id")); // 1

  // The context is also available in any functions called from this block
  function myFunction() {
    return context.active().getValue("id");
  }
  console.log(myFunction()); // 1

  // And it is also available in any asynchronous callbacks scheduled from here
  setTimeout(() => {
    console.log(context.active().getValue("id")); // 1
  }, 10);
});

// Outside, the context is still the same
console.log(context.active().getValue("id")); // undefined

上下文 API 也与 span 集成。例如,要在特定 span 的上下文中运行函数,可以将 span 添加到上下文中,然后可以在该上下文中运行函数

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

// Don't forget to end the span!
span.end();

OpenTelemetry JS API 文档中了解有关完整上下文 API 的更多信息。

配置 跳转到标题

可以通过设置 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.name: deno
  • process.runtime.version: Deno 运行时版本。
  • telemetry.sdk.name: deno-opentelemetry
  • telemetry.sdk.language: deno-rust
  • telemetry.sdk.version: Deno 运行时版本,加上 Deno 使用的 opentelemetry Rust crate 版本,由 - 分隔。

传播器可以使用 OTEL_PROPAGATORS 环境变量进行配置。默认值为 tracecontext,baggage。可以通过逗号分隔指定多个传播器。目前支持的传播器有

  • tracecontext: W3C 追踪上下文传播格式
  • baggage: W3C Baggage 传播格式

指标收集频率可以使用 OTEL_METRIC_EXPORT_INTERVAL 环境变量进行配置。默认值为 60000 毫秒(60 秒)。

Span 导出器批处理可以使用 OpenTelemetry 规范中描述的批处理 span 处理器环境变量进行配置。

日志导出器批处理可以使用 OpenTelemetry 规范中描述的批处理日志记录处理器环境变量进行配置。

传播器 跳转到标题

Deno 支持上下文传播器,它能够自动在分布式追踪的进程边界之间传播追踪上下文,从而允许您跟踪请求在不同服务中的流向。

传播器负责将上下文信息(如追踪和 span ID)编码和解码到载体格式(如 HTTP 请求头)中。这使得追踪上下文能够在服务边界之间保持。

默认情况下,Deno 支持以下传播器

  • tracecontext: W3C Trace Context 传播格式,这是通过 HTTP 请求头传播追踪上下文的标准方式。
  • baggage: W3C Baggage 传播格式,允许在服务边界之间传递键值对。

注意

这些传播器会自动与 Deno 的 fetch API 和 Deno.serve 配合使用,无需手动上下文管理即可实现 HTTP 请求的端到端追踪。

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

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

// Extract context from incoming headers
function extractContextFromHeaders(headers: Headers) {
  const ctx = context.active();
  return propagation.extract(ctx, headers);
}

// Inject context into outgoing headers
function injectContextIntoHeaders(headers: Headers) {
  const ctx = context.active();
  propagation.inject(ctx, headers);
  return headers;
}

// Example: Making a fetch request that propagates trace context
async function tracedFetch(url: string) {
  const headers = new Headers();
  injectContextIntoHeaders(headers);

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

限制 跳转到标题

Deno 的 OpenTelemetry 集成仍在开发中,因此存在一些限制需要注意

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

您找到所需内容了吗?

隐私政策