OpenTelemetry Node.js追踪问题的故障排除清单

我将尽力简短地表达。您可能在这里是因为您在Node.js应用程序中安装了OpenTelemetry,但没有看到任何跟踪,或者一些预期的span丢失了。

造成这种情况可能有很多原因,但有些比其他原因更常见。在本文中,我将尝试列举一些常见的原因,并提供一些诊断方法和技巧。

要求

我假设您已经对OpenTelemetry是什么以及它的工作原理有基本的了解,并且已经尝试在Node.js应用程序中设置它。

启用日志记录

OpenTelemetry JS默认不会将任何内容记录到其诊断记录器中。当启用记录器时,大多数以下的SDK问题很容易检测到。

您可以通过在您的服务中尽早添加以下代码来将所有内容记录到控制台:

// tracing.ts或main index.ts
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
// 其它OpenTelemetry初始化代码

这对于调试很有用。但在生产环境中将所有内容记录到控制台并不是一个好主意,所以在解决问题之后请记住删除或禁用它。

专业提示:您可以使用OTEL_LOG_LEVEL环境变量来设置DiagLogLevel,这样我们可以方便地打开或关闭它。

自动仪表库

许多用户选择使用自动仪表库,这些库可以自动为常用且广泛使用的包(DB驱动程序、HTTP框架、云服务SDK等)中的有趣操作创建span。

某些初始化模式和配置选项可能会导致您的服务无法创建span。

为了排除自动仪表库的问题,首先尝试创建一个手动span。如果您能看到手动span但却没有看到安装的自动仪表库的span,请继续阅读本节。

import { trace } from '@opentelemetry/api';
trace
  .getTracerProvider()
  .getTracer('debug')
  .startSpan('test manual span')
  .end();

安装和启用

要在服务中使用自动仪表库,您需要:

  1. 安装它:npm install @opentelemetry/instrumentation-foo。您可以搜索OpenTelemetry注册表以查找可用的仪表化工具
  2. 创建仪表化对象:new FooInstrumentation(config)
  3. 确保启用仪表化:调用registerInstrumentations(...)
  4. 验证您正在使用正确的TracerProvider

对于大多数用户,以下内容应该足够了:

// 首先运行:npm install @opentelemetry/instrumentation-foo @opentelemetry/instrumentation-bar
// 将foo和bar替换为您需要仪表化的实际包(HTTP/mySQL/Redis等)
import { FooInstrumentation } from '@opentelemetry/instrumentation-foo';
import { BarInstrumentation } from '@opentelemetry/instrumentation-bar';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
// 创建TracerProvider、SpanProcessors和SpanExporters
registerInstrumentations({
  instrumentations: [new FooInstrumentation(), new BarInstrumentation()],
});

对于高级用户选择使用低级API而不是调用registerInstrumentations的方式,确保您的仪表化设置为使用正确的跟踪器提供程序,并在适当的情况下调用enable()

在引入之前启用

所有的仪表化都设计成您首先需要启用它们,然后再引入经仪表化的包。一个常见的错误是在启用这些仪表库之前引入包

下面是一个不好的例子:

import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import {
  SimpleSpanProcessor,
  ConsoleSpanExporter,
} from '@opentelemetry/sdk-trace-base';
import http from 'http'; // ⇐ 错误 - 在这一点上,仪表库还没有注册
const provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
provider.register();
registerInstrumentations({ instrumentations: [new HttpInstrumentation()] });
// 使用http的应用程序代码

在大多数情况下,仪表化代码位于不同的文件或包中,这使得很难发现。一些框架(如无服务器框架)可能会在仪表化代码有机会运行之前导入包,这可能很容易被忽视。

要诊断此问题,启用日志记录并验证是否看到正在加载您的仪表化包。例如:

@opentelemetry/instrumentation-http 对https@12.22.9应用了修补程序

如果没有看到这些信息,很可能您的自动仪表化库没有被应用。

库配置

一些自动仪表化库包括自定义配置,用于控制什么时候跳过仪表化。例如,HTTP仪表化有一些选项,如ignoreIncomingRequestHookrequireParentforOutgoingSpans

在特定情况下,有些库默认不进行仪表化,您需要特别选择才能创建span。例如,需要使用requireParentSpan=trueioredis进行配置,以便为没有父span的内部操作创建span。

如果您不见对于某个库的span,请检查是否需要调整配置以使它们出现。

经过仪表化的库版本

自动仪表化库通常不支持其仪表化的所有库版本。如果您使用的版本过旧或非常新,可能不受支持,因此不会创建任何span。

请查阅您使用的库的文档,以验证您的版本是否兼容。这些信息通常可以在仪表化的README中找到,例如查看Redis README

无记录和非采样的Span

并非所有在应用程序中创建的span都会被导出。span可以被标记为“非采样”或“非记录”,在这种情况下,您将无法在后端中看到它们。

为了排除这些问题,您可以添加一个“debug span处理器”,它只会打印采样决策。如果在控制台打印出“span sampled: false”,请继续阅读本节。

import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
import { trace, Span, Context, TraceFlags } from '@opentelemetry/api';
const provider = new NodeTracerProvider();
provider.addSpanProcessor({
  forceFlush: async () => {},
  onStart: (_span: Span, _parentContext: Context) => {},
  onEnd: (span: ReadableSpan) => {
    const sampled = !!(span.spanContext().traceFlags & TraceFlags.SAMPLED);
    console.log(`span sampled: ${sampled}`);
  },
  shutdown: async () => {},
});
provider.register();

NoopTracerProvider

如果您没有创建和注册有效的TracerProvider,您的应用程序将使用默认的TracerProvider,它会将应用程序中的所有span作为非记录span启动。

您需要在应用程序尽早的时间内有类似以下代码的内容:

import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import {
  ConsoleSpanExporter,
  SimpleSpanProcessor,
} from '@opentelemetry/sdk-trace-base';
const provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
provider.register();

远程采样决策

默认的采样行为(也是非常流行的一种行为)是每个span都继承其父span的采样决策。如果调用您的服务的组件配置为不进行采样,那么您的服务中也将看不到来自该组件的span

示例包括:

  • API网关可以配置有采样逻辑或关闭跟踪,这将影响所有下游跟踪(包括您的无辜服务,但需要进行采样)。
  • 调用您服务的外部用户也可以被仪表化并推导出自己的采样决策(您无法控制)。这些采样决策然后传播到您的服务并影响它。
  • 您系统中的其他服务可以根据其本地需求和观点来推测采样决策。可能很容易讲一个上游服务端点配置为不对不重要的端点进行采样,而不意识到它调用了一个非常重要和重要的下游端点(我们确实希望对其进行采样)。

本地采样器

您可以将本地采样器配置为采样一些span或不采样。如果配置是由他人很久以前编写的,或者它是复杂/不直观的 - 那么span被正当地不被采样和导出,这很容易被忽视。

导出问题

服务可能会生成span,但它们可能没有正确的导出到您的后端,或者由于某些原因被抛弃在收集器内部。

为了排除导出问题,尝试添加“ConsoleExporter”。如果您看到span被导出到控制台但在您导出到的后端中没有被看到,请继续阅读本节。

import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import {
  ConsoleSpanExporter,
  SimpleSpanProcessor,
} from '@opentelemetry/sdk-trace-base';
const provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
provider.register();

配置导出器

您的服务应该有类似以下代码的span导出代码:

import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
// 创建TracerProvider
const exporter = new OTLPTraceExporter();
provider.addSpanProcessor(new BatchSpanProcessor(exporter));

在这个例子中,我使用了@opentelemetry/exporter-trace-otlp-proto,但是还有其他可选择的导出器,并且每个导出器都有一些配置选项。一个配置选项的错误会导致导出失败,默认情况下此错误会被忽略。

以下是一些常见的配置错误,在以下小节中进行了说明。

OTLP导出器

  • 格式 - OTLP支持http/jsonhttp/protogrpc格式。您需要选择一个与您的OTLP收集器支持的格式匹配的导出器包。
  • 路径 - 如果您设置了HTTP收集器端点(通过代码或环境变量中的配置),您还必须设置路径http://my-collector-host:4318/v1/traces。如果忘记了路径,导出将失败。对于gRPC,您不应添加路径:“grpc://localhost:4317”。这可能有点令人困惑,在刚开始时很容易弄错。
  • 安全连接 - 检查您的收集器是否需要安全或不安全连接。在HTTP中,这由URL方案(http: / https:)确定。在gRPC中,方案不起作用,连接安全性由凭据参数设置:grpc.credentials.createSsl()grpc.credentials.createInsecure()等。HTTP和gRPC的默认安全性都是不安全的

Jaeger导出器

Jaeger导出器可以以“Agent”模式(通过UDP)和“Collector”模式(通过TCP)工作。用于决定使用哪种模式的逻辑有点令人困惑且缺乏文档。如果在导出器配置中传递了endpoint参数或设置了OTEL_EXPORTER_JAEGER_ENDPOINT环境变量,则导出器将使用“Collector”HTTP发送器。否则,它将使用“Agent”模式通过UDP发送到param中配置的host,或者OTEL_EXPORTER_JAEGER_AGENT_HOSTlocalhost:6832

设置供应商凭据

如果您将供应商作为跟踪后端系统,您可能需要添加附加信息,比如身份验证头。例如,如果您将跟踪发送到Aspecto,您需要将Aspecto令牌作为Authorization头添加,如下所示:

import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
// 创建TracerProvider
const exporter = new OTLPTraceExporter({
  url: 'https://otelcol.aspecto.io/v1/trace',
  headers: {
    Authorization: 'YOUR_API_KEY_HERE',
  },
});
provider.addSpanProcessor(new BatchSpanProcessor(exporter));

如果没有应用这个设置,您将无法在供应商的账户中看到任何数据。

刷新和关闭

当您的服务关闭或您的lambda函数结束时,可能并不是所有的span都已成功导出到您的收集器。您需要在您的跟踪器提供程序上调用shutdown函数并等待返回的Promise,以确保所有数据都已发送。

import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
const provider = new NodeTracerProvider();
provider.register();
// 当您终止服务时,调用provider上的shutdown:
provider.shutdown();

包版本兼容性

一些问题可能是由于不兼容或旧版本的SDK和仪表包导致的。

SDK版本

建议检查您的SDK和API包是否不是过旧的,并且彼此兼容。确保在npm install时没有任何对等依赖警告。

其他APM库

不能保证OpenTelemetry与使用动态方法织入的其他APM库兼容。如果您安装了这样的包,请尝试删除或禁用它,并检查问题是否消失。

下一步该怎么办?

在哪里寻求帮助

如果上述方法都没有解决您的问题,您可以在以下渠道寻求帮助:

资源

我应该使用供应商吗?

另一种选择是使用供应商的OpenTelemetry分发版本。这些分发版本可以节省您的时间和精力:

  • 技术支持
  • 预配置了常见和高级用户的流行特性
  • 使用最新的OpenTelemetry版本
  • 实施最佳实践并避免上述提到的陷阱

有关OpenTelemetry供应商的列表,请参见Vendors

本文的一个版本最初发布在Aspecto博客上。