deno.com
本页内容

OpenTelemetry

注意

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

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(追踪)和 Mimir(指标))来开始使用

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 访问 https://127.0.0.1:3000 上的 Grafana 仪表板。

这将自动收集和导出运行时可观测性数据,例如 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.status_code:响应的状态代码。

Deno 不会自动将 http.route 属性添加到 span,因为路由不是运行时已知的,而是由用户处理函数中的路由逻辑确定的。如果您想将 http.route 属性添加到 span,您可以使用 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 运行时退出的任何错误(来自用户代码和运行时本身)。

从 JavaScript 代码引发的日志将在相关的 span 上下文中导出,如果日志发生在活动 span 内。

可以使用 OTEL_DENO_CONSOLE 环境变量配置 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 });

属性的值可以是字符串、数字(浮点数)、bigint(钳制为 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 的引用。

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

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

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

在选项包之后,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 创建一个 instrument,并用于记录值

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 创建多种类型的 instrument

  • 计数器 (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]

还有几种类型的可观察 instrument。这些 instrument 没有同步记录方法,而是返回一个回调,可以调用该回调来记录值。当 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" });
});

有三种类型的可观察 instrument

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

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

上下文传播 跳转到标题

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

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

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

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

这意味着异步上下文传播本质上就像一个全局变量,其作用域限定为当前异步任务,并自动复制到从此当前任务启动的任何新异步任务。

npm:@opentelemetry/api@1 中的 context 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 集成。

可以使用 OTEL_EXPORTER_OTLP_ENDPOINTOTEL_EXPORTER_OTLP_PROTOCOL 环境变量配置 OTLP 导出器的端点和协议。

如果端点需要身份验证,可以使用 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 运行时的版本,加上 Deno 使用的 opentelemetry Rust crate 的版本,以 - 分隔。

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

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

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

局限性 跳转到标题

虽然 Deno 的 OpenTelemetry 集成正在开发中,但有一些局限性需要注意

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

您找到所需的信息了吗?

隐私政策