通过OpenTelemetry上报Node.js应用数据

通过OpenTelemetry为应用埋点并上报链路数据至可观测链路 OpenTelemetry 版后,可观测链路 OpenTelemetry 版即可开始监控应用,您可以查看应用拓扑、调用链路、异常事务、慢事务和SQL分析等一系列监控数据。本文介绍如何使用OpenTelemetry对Node.js Express应用进行自动或手动埋点并上报数据。

前提条件

重要

Node.js版本需要14及以上,其他版本请使用Jaeger接入。Jaeger接入操作,请参见通过Jaeger上报Node.js应用数据

获取接入点信息

新版控制台

  1. 登录可观测链路 OpenTelemetry 版控制台,在左侧导航栏单击接入中心

  2. 开源框架区域单击OpenTelemetry卡片。

  3. 在弹出的OpenTelemetry面板中选择数据需要上报的地域。

    说明

    初次接入的地域将会自动进行资源初始化。

  4. 选择连接方式上报方式,然后复制接入点信息。

    • 连接方式:若您的服务部署在阿里云上,且所属地域与选择的接入地域一致,推荐使用阿里云内网方式,否则选择公网方式。

    • 上报方式:根据客户端支持的协议类型选择HTTP或gRPC协议上报数据。

    image.png

旧版控制台

  1. 登录可观测链路 OpenTelemetry 版控制台

  2. 在左侧导航栏单击集群配置,然后在右侧页面单击接入点信息页签。

  3. 在页面顶部选择需要接入的地域,然后在集群信息区域打开显示Token开关。

  4. 客户端采集工具区域单击OpenTelemetry

    相关信息列中,获取接入点信息。ot旧版中.jpg

    说明

    如果应用部署于阿里云生产环境,则选择阿里云VPC网络接入点,否则选择公网接入点。

背景信息

OpenTelemetry提供了若干自动埋点插件,支持为常见框架自动创建Span,支持的框架列表如下,完整信息请参见OpenTelemetry官方文档

展开查看支持的Node.js框架

示例Demo

示例代码仓库地址:opentelemetry-nodejs-demo

方法一:自动埋点(推荐)

  1. 下载运行项目所需的依赖。

    cd auto-instrumentation
    
    npm init -y
    npm install express
    npm install axios
  2. 下载OpenTelemetry自动埋点所需的依赖。

    npm install --save @opentelemetry/api
    npm install --save @opentelemetry/auto-instrumentations-node
  3. 编写应用代码。

    以下代码是通过Express实现的简单应用示例:

    "use strict";
    
    const axios = require("axios").default;
    const express = require("express");
    const app = express();
    
    app.get("/", async (req, res) => {
      const result = await axios.get("http://localhost:7001/hello");
      return res.status(201).send(result.data);
    });
    
    app.get("/hello", async (req, res) => {
      console.log("hello world!")
      res.json({ code: 200, msg: "success" });
    });
    
    app.use(express.json());
    
    app.listen(7001, () => {
      console.log("Listening on http://localhost:7001");
    });
  4. 通过环境变量设置OpenTelemetry参数并运行应用。

    请将${httpEndpoint}替换为前提条件中获取的HTTP接入点,请将${serviceName}替换为您的应用名。

    export OTEL_TRACES_EXPORTER="otlp"
    export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="${httpEndpoint}"
    export OTEL_NODE_RESOURCE_DETECTORS="env,host,os"
    export OTEL_SERVICE_NAME="${serviceName}"
    export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
    node main.js
    说明

    有关OpenTelemetry环境变量的说明,请参见OpenTelemetry Node.js自动埋点配置

  5. 访问应用。

    通过以下命令访问应用,或者直接在浏览器中访问该地址,即可生成调用链并上报至可观测链路 OpenTelemetry 版

    curl localhost:7001/hello

方法二:手动埋点

  1. package.json中配置对OpenTelemetry的依赖。

     "dependencies": {
        "@opentelemetry/api": "^1.0.4",
        "@opentelemetry/exporter-trace-otlp-grpc": "^0.27.0",
        "@opentelemetry/instrumentation": "^0.27.0",
        "@opentelemetry/instrumentation-express": "^0.27.0",
        "@opentelemetry/instrumentation-http": "^0.27.0",
        "@opentelemetry/resources": "^1.0.1",
        "@opentelemetry/sdk-trace-base": "^1.0.1",
        "@opentelemetry/sdk-trace-node": "^1.0.1"
      }
  2. 创建Provider。

    const { Resource } = require("@opentelemetry/resources");
    const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
    const {
      SemanticResourceAttributes,
    } = require("@opentelemetry/semantic-conventions");
    
    const provider = new NodeTracerProvider({
      resource: new Resource({
        [SemanticResourceAttributes.HOST_NAME]: require("os").hostname(),
        [SemanticResourceAttributes.SERVICE_NAME]: "opentelemetry-express",    //opentelemetry-express可替换为任意名称。
      }),
    });
  3. 通过Provider注册HTTP和Express框架,自动监测并拦截HTTP和Express。

    说明

    如需监测其他框架下的Node.js应用,请参见OpenTelemetry官方文档

    const { registerInstrumentations } = require("@opentelemetry/instrumentation");
    const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");
    const {
      ExpressInstrumentation,
    } = require("@opentelemetry/instrumentation-express");
    
    registerInstrumentations({
      tracerProvider: provider,
      instrumentations: [new HttpInstrumentation(), ExpressInstrumentation],
    });
  4. 配置Exporter,导出数据到可观测链路 OpenTelemetry 版

    请将下面代码中的<ENDPOINT><AUTHENTICATION>替换成前提条件中获取的Endpoint和Authentication。

    const metadata = new grpc.Metadata();
    metadata.set("Authentication", "<AUTHENTICATION>");
    
    const exporter = new OTLPTraceExporter({ url: "<ENDPOINT>", metadata });
    provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
    provider.register();
  5. 可选:添加自定义事件和属性。

    说明

    OpenTelemetry API的使用方法,请参见OpenTelemetry官方文档

    const api = require("@opentelemetry/api");
    const currentSpan = api.trace.getSpan(api.context.active());
    currentSpan.addEvent("timestamp", { value: Date.now() });
    currentSpan.setAttribute("tagKey-01", "tagValue-01");

在控制台查看Trace

可观测链路 OpenTelemetry 版控制台应用列表页面选择目标应用,查看链路数据。

基于Express框架的Node.js应用完整示例

"use strict";

const { Resource } = require("@opentelemetry/resources");
const {
  OTLPTraceExporter,
} = require("@opentelemetry/exporter-trace-otlp-grpc");
const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
const { SimpleSpanProcessor } = require("@opentelemetry/sdk-trace-base");
const {
  ExpressInstrumentation,
} = require("@opentelemetry/instrumentation-express");
const { registerInstrumentations } = require("@opentelemetry/instrumentation");
const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");
const {
  SemanticResourceAttributes,
} = require("@opentelemetry/semantic-conventions");
const grpc = require("@grpc/grpc-js");

const provider = new NodeTracerProvider({
  resource: new Resource({
    [SemanticResourceAttributes.HOST_NAME]: require("os").hostname(),
    [SemanticResourceAttributes.SERVICE_NAME]: "opentelemetry-express",
  }),
});

registerInstrumentations({
  tracerProvider: provider,
  instrumentations: [new HttpInstrumentation(), ExpressInstrumentation],
});

const metadata = new grpc.Metadata();
metadata.set("Authentication", "<AUTHENTICATION>");

const exporter = new OTLPTraceExporter({ url: "<ENDPOINT>", metadata });
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
provider.register();

// 应用代码
const api = require("@opentelemetry/api");
const axios = require("axios").default;
const express = require("express");
const app = express();

app.get("/", async (req, res) => {
  const result = await axios.get("http://localhost:7001/api");
  return res.status(201).send(result.data);
});

app.get("/api", async (req, res) => {
  const currentSpan = api.trace.getSpan(api.context.active());
  currentSpan.addEvent("timestamp", { value: Date.now() });
  currentSpan.setAttribute("tagKey-01", "tagValue-01");
  res.json({ code: 200, msg: "success" });
});

app.use(express.json());

app.listen(7001, () => {
  console.log("Listening on http://localhost:7001");
});