deno.com
本页内容

Deno 中的分布式追踪与上下文传播

现代应用程序通常构建为由多个相互通信的服务组成的分布式系统。在调试这些系统中的问题或优化性能时,能够追踪请求在不同服务中的流向至关重要。这就是分布式追踪的作用。

自 Deno 2.3 起,运行时现在可以自动在服务边界之间保留追踪上下文,从而使分布式系统中的端到端追踪更简单、更强大。这意味着当一个服务向另一个服务发出请求时,追踪上下文会自动传播,让您能够将整个请求流视为一个单一的追踪。

设置分布式系统 跳到标题

我们的示例系统将由两部分组成

  1. 提供 API 端点的服务器
  2. 向服务器发出请求的客户端

服务器 跳到标题

我们将设置一个简单的 HTTP 服务器,它会响应带有 JSON 消息的 GET 请求

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

const tracer = trace.getTracer("api-server", "1.0.0");

// Create a simple API server with Deno.serve
Deno.serve({ port: 8000 }, (req) => {
  return tracer.startActiveSpan("process-api-request", async (span) => {
    // Add attributes to the span for better context
    span.setAttribute("http.route", "/");
    span.updateName("GET /");

    // Add a span event to see in traces
    span.addEvent("processing_request", {
      request_id: crypto.randomUUID(),
      timestamp: Date.now(),
    });

    // Simulate processing time
    await new Promise((resolve) => setTimeout(resolve, 50));

    console.log("Server: Processing request in trace context");

    // End the span when we're done
    span.end();

    return new Response(JSON.stringify({ message: "Hello from server!" }), {
      headers: { "Content-Type": "application/json" },
    });
  });
});

客户端 跳到标题

现在,我们来创建一个向服务器发出请求的客户端

client.ts
import { SpanStatusCode, trace } from "npm:@opentelemetry/api@1";

const tracer = trace.getTracer("api-client", "1.0.0");

// Create a parent span for the client operation
await tracer.startActiveSpan("call-api", async (parentSpan) => {
  try {
    console.log("Client: Starting API call");

    // The fetch call inside this span will automatically:
    // 1. Create a child span for the fetch operation
    // 2. Inject the trace context into the outgoing request headers
    const response = await fetch("http://localhost:8000/");
    const data = await response.json();

    console.log(`Client: Received response: ${JSON.stringify(data)}`);

    parentSpan.addEvent("received_response", {
      status: response.status,
      timestamp: Date.now(),
    });
  } catch (error) {
    console.error("Error calling API:", error);
    if (error instanceof Error) {
      parentSpan.recordException(error);
    }
    parentSpan.setStatus({
      code: SpanStatusCode.ERROR,
      message: error instanceof Error ? error.message : String(error),
    });
  } finally {
    parentSpan.end();
  }
});

使用 OpenTelemetry 进行追踪 跳到标题

客户端和服务器代码都已包含基本的 OpenTelemetry 检测

  1. 创建追踪器 — 两个文件都使用 trace.getTracer() 并指定名称和版本来创建追踪器。

  2. 创建 Span — 我们使用 startActiveSpan() 来创建表示操作的 Span。

  3. 添加上下文 — 我们向 Span 添加属性和事件以提供更多上下文。

  4. 结束 Span — 我们确保在操作完成时结束 Span。

自动上下文传播 跳到标题

当客户端向服务器发出请求时,神奇的事情就发生了。在客户端代码中有一个对服务器的 fetch 调用

const response = await fetch("http://localhost:8000/");

由于此 fetch 调用发生在活动 Span 内部,Deno 会自动为 fetch 操作创建一个子 Span,并将追踪上下文注入到传出请求头中。

当服务器收到此请求时,Deno 从请求头中提取追踪上下文,并将服务器 Span 建立为客户端 Span 的子级。

运行示例 跳到标题

要运行此示例,首先启动服务器,并为您的 otel 服务命名

OTEL_DENO=true OTEL_SERVICE_NAME=server deno run --unstable-otel --allow-net server.ts

然后,在另一个终端中,运行客户端,并为客户端指定一个不同的服务名称,以便更清晰地观察传播过程

OTEL_DENO=true OTEL_SERVICE_NAME=client deno run --unstable-otel --allow-net client.ts

您应该会看到

  1. 客户端日志显示“Client: Starting API call”
  2. 服务器日志显示“Server: Processing request in trace context”
  3. 客户端日志显示从服务器收到的响应

查看追踪 跳到标题

要实际查看追踪,您需要一个 OpenTelemetry 收集器和一个可视化工具,例如 Grafana Tempo

当您可视化追踪时,您会看到

  1. 来自客户端的父 Span
  2. 连接到 HTTP 请求的子 Span
  3. 连接到来自服务器的 Span
  4. 所有这些都作为单个追踪的一部分!

例如,在 Grafana 中,追踪可视化可能如下所示

Viewing expanded traces in Grafana

🦕 现在您已经了解了 Deno 中的分布式追踪,您可以将其扩展到具有多个服务和异步操作的更复杂系统。

借助 Deno 的自动上下文传播,在您的应用程序中实现分布式追踪从未如此简单!

您找到所需内容了吗?

隐私政策