On this page
OpenTelemetry
Deno 内置支持 OpenTelemetry。
OpenTelemetry 是一组 API、SDK 和工具。使用它来对软件进行仪器化,生成、收集和导出遥测数据(指标、日志和追踪)以帮助您分析软件的性能和行为。
此集成让您能够使用 OpenTelemetry 的可观察性工具(如日志、指标和追踪)来监控您的 Deno 应用程序。
Deno 提供以下功能:
- 使用 OpenTelemetry 协议,将收集的指标、追踪和日志导出到服务器。
- 对 Deno 运行时进行自动仪器化,采集 OpenTelemetry 的指标、追踪和日志。
- 收集使用
npm:@opentelemetry/api包创建的用户自定义指标、追踪和日志。
快速开始 Jump to heading
要启用 OpenTelemetry 集成,请设置环境变量 OTEL_DENO=true:
OTEL_DENO=true deno run my_script.ts
这将自动收集并通过 HTTP 的 Protobuf 格式(http/protobuf)将运行时可观察性数据导出到 localhost:4318 的 OpenTelemetry 端点。
如果您还未搭建 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 的协议(例如http或https)。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.serve 或 Deno.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.1或2)。server.address:服务器监听的地址。server.port:服务器监听的端口。http.response.status_code:响应状态码(请求无致命错误时)。error.type:发生的错误类型(请求处理出现错误时)。
http.server.active_requests Jump to heading
一个量表,统计任意时刻由 Deno.serve 或 Deno.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.log、console.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.x 的 npm:@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.recordException 和 span.setStatus 以便于记录。
在回调内部,创建的跨度是“活动跨度”,可使用 trace.getActiveSpan() 访问。此“活动跨度”在回调及其调用函数内使用时会作为父跨度。
了解更多关于上下文传播的信息。
startActiveSpan 返回回调函数的返回值。
在跨度生命周期内,您可以调用 setAttribute 和 setAttributes 添加属性,即结构化元数据键值对。
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.startActiveSpan 和 tracer.startSpan 可接受含以下任意属性的选项参数:
kind: 跨度类型。可为SpanKind.CLIENT、SpanKind.SERVER、SpanKind.PRODUCER、SpanKind.CONSUMER或SpanKind.INTERNAL。默认值为SpanKind.INTERNAL。startTime:表示跨度开始时间的Date对象,或自 Unix 纪元起的毫秒数。如果未提供,则使用当前时间。attributes: 要添加到跨度的属性对象。links: 要添加到跨度的链接数组。root: 布尔值,表示跨度是否为根跨度。如果为true,则该跨度没有父跨度(即使存在活动跨度)。
在选项参数之后,tracer.startActiveSpan 和 tracer.startSpan 还可以接收来自
上下文传播 API 的 context 对象。
了解完整追踪 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" });
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 的实际示例,请参见教程:
- 基础 OpenTelemetry 教程 - 一个带有自定义指标和追踪的简单 HTTP 服务器
- 分布式追踪教程 - 跨服务边界追踪的高级技巧
上下文传播 Jump to heading
在 OpenTelemetry 中,上下文传播是指将一些上下文信息(如当前跨度)从应用的一个部分传递到另一个部分,而无需手动将其作为参数传递给每个函数。
在 Deno 中,上下文传播遵循 TC39 提案的 AsyncContext 规则。AsyncContext API 尚未向用户公开,但内部用于在异步边界上传播活动跨度和其他上下文信息。
简要说明 AsyncContext 传播工作方式:
- 当启动一个新的异步任务(例如 Promise 或定时器)时,当前上下文会被保存。
- 其他代码可以在不同上下文中并发执行。
- 当异步任务完成时,保存的上下文被恢复。
这意味着异步上下文传播类似于一个全局变量,其作用域限定于当前异步任务,并自动拷贝到由当前任务启动的新异步任务中。
来自 npm:@opentelemetry/api@1 的 context 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_ENDPOINT 和 OTEL_EXPORTER_OTLP_PROTOCOL 配置。
若端点需要身份验证,可使用环境变量 OTEL_EXPORTER_OTLP_HEADERS 设置请求头。
指标、追踪和日志导出的端点也可用以下专用变量分别覆盖,例如:
OTEL_EXPORTER_OTLP_METRICS_ENDPOINTOTEL_EXPORTER_OTLP_TRACES_ENDPOINTOTEL_EXPORTER_OTLP_LOGS_ENDPOINT
有关 OTLP 头部配置详情,请参考 OpenTelemetry 官网。
遥测数据关联的资源属性通过环境变量 OTEL_SERVICE_NAME 和 OTEL_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 版本与使用的opentelemetryRust 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 格式,允许跨服务传递键值对。
这些传播器会自动与 Deno 的 fetch 和 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.log和console.error日志)。 - 唯一支持的导出器是 OTLP,其他导出器未支持。
- OTLP 仅支持
http/protobuf和http/json协议,不支持grpc等。 - 可观察(异步)仪表采集的指标在进程退出或崩溃时不会被采集,故最后指标可能未导出。同步指标可正常导出。
- 追踪跨度不遵守环境变量中指定的
OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT、OTEL_ATTRIBUTE_COUNT_LIMIT、OTEL_SPAN_EVENT_COUNT_LIMIT、OTEL_SPAN_LINK_COUNT_LIMIT、OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT和OTEL_LINK_ATTRIBUTE_COUNT_LIMIT限制。 - 不尊重环境变量
OTEL_METRIC_EXPORT_TIMEOUT。 - 未知的 HTTP 方法不会在
http.request.method跨度属性中标准化为_OTHER,与 OpenTelemetry 语义约定不符。 Deno.serve的 HTTP 服务器跨度在处理程序抛出(调用onError)时不会设置错误状态,错误也不通过事件附加到跨度。fetch的 HTTP 客户端跨度中无机制添加http.route属性或更新跨度名称以包含路由信息。