通过OpenTelemetry接入Node.js Trace数据

本文介绍通过opentelemetry-js SDK将Node.js应用的Trace数据接入到日志服务的操作步骤。

前提条件

  • 已创建Trace实例。更多信息,请参见创建Trace实例
  • 已安装Node.js v8.5.0及以上版本的开发环境。

(推荐)方案一:半自动接入

Node.js支持在http、https、grpc、express、mysql、mongodb、redis等框架中通过引入依赖包的方式自动上传Trace数据。详细的框架列表请参见opentelemetry-node-js-contrib。此处以express为例,介绍半自动接入方案。更多示例请参见examples

  1. 安装依赖包。

    npm install --save @opentelemetry/api
    npm install --save @opentelemetry/node
    npm install --save @opentelemetry/tracing
    npm install --save @opentelemetry/exporter-collector-grpc
    npm install --save @opentelemetry/instrumentation
    npm install --save @opentelemetry/instrumentation-express
    npm install --save @opentelemetry/instrumentation-http
    npm install --save @grpc/grpc-js
    npm install --save @opentelemetry/sdk-trace-node
  2. 初始化Tracer并启动express。

    如下代码中的变量需根据实际情况替换。关于变量的详细说明,请参见变量说明

    const opentelemetry = require("@opentelemetry/api");
    const { registerInstrumentations } = require("@opentelemetry/instrumentation");
    const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
    const { Resource } = require("@opentelemetry/resources");
    const {
      SemanticResourceAttributes,
    } = require("@opentelemetry/semantic-conventions");
    const {
      SimpleSpanProcessor,
      ConsoleSpanExporter,
    } = require("@opentelemetry/tracing");
    const grpc = require("@grpc/grpc-js");
    const {
      CollectorTraceExporter,
    } = require("@opentelemetry/exporter-collector-grpc");
    
    const {
      ExpressInstrumentation,
    } = require("@opentelemetry/instrumentation-express");
    const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");
    
    var os = require("os");
    var hostname = os.hostname();
    
    const provider = new NodeTracerProvider({
      resource: new Resource({
        [SemanticResourceAttributes.SERVICE_NAME]: "${service}",
          [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: "${environment}",
        [SemanticResourceAttributes.SERVICE_VERSION]: "${version}",
        [SemanticResourceAttributes.SERVICE_NAMESPACE]: "${service.namespace}",
        [SemanticResourceAttributes.HOST_NAME]: hostname,
      }),
    });
    provider.register();
    
    registerInstrumentations({
      instrumentations: [
        new HttpInstrumentation(),
        new ExpressInstrumentation({
          ignoreLayersType: [new RegExp("middleware.*")],
        }),
      ],
      tracerProvider: provider,
    });
    
    var url = "${endpoint}";
    
    var logStdout = false;
    if (url == "stdout") {
      logStdout = true;
    }
    var meta = new grpc.Metadata();
    meta.add("x-sls-otel-project", "${project}");
    meta.add("x-sls-otel-instance-id", "${instance}");
    meta.add("x-sls-otel-ak-id", "${access-key-id}");
    meta.add("x-sls-otel-ak-secret", "${access-key-secret}");
    const collectorOptions = {
      url: url,
      credentials: grpc.credentials.createSsl(),
      metadata: meta,
    };
    const exporter = new CollectorTraceExporter(collectorOptions);
    
    if (!logStdout) {
      provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
    } else {
      var stdexporter = new ConsoleSpanExporter();
      provider.addSpanProcessor(new SimpleSpanProcessor(stdexporter));
    }
    provider.register();
    var tracer = opentelemetry.trace.getTracer("${service}");
    
    var express = require("express");
    var app = express();
    
    app.get("/hello", function (req, res, next) {
      res.send("success");
    });
    
    var server = app.listen(8079, function () {
      var port = server.address().port;
      console.log("App now running in %s mode on port %d", app.get("env"), port);
    });

    表 1. 变量说明

    变量

    说明

    示例

    ${endpoint}

    日志服务Project的接入地址,格式为${project}.${region-endpoint}:Port,其中:

    • ${project}:日志服务Project名称。

    • ${region-endpoint}:日志服务Project所在地域的访问域名,支持公网和阿里云内网(经典网络、VPC)。更多信息,请参见服务入口

    • Port:网络端口,固定为10010。

    test-project.cn-hangzhou.log.aliyuncs.com:10010

    ${project}

    日志服务Project名称。

    test-project

    ${instance}

    Trace服务实例ID。更多信息,请参见创建Trace实例

    test-traces

    ${access-key-id}

    阿里云账号AccessKey ID。

    建议您使用只具备日志服务Project写入权限的RAM用户的AccessKey(包括AccessKey ID和AccessKey Secret)。授予RAM用户向指定Project写入数据权限的具体操作,请参见授权。如何获取AccessKey的具体操作,请参见访问密钥

    ${access-key-secret}

    阿里云账号AccessKey Secret。

    建议您使用只具备日志服务Project写入权限的RAM用户的AccessKey。

    ${service}

    服务名。根据您的实际场景取值。

    payment

    ${version}

    服务版本号。建议按照va.b.c格式定义。

    v0.1.2

    ${service.namespace}

    服务归属的命名空间。

    order

    ${environment}

    服务部署环境。例如测试环境、预发环境、正式环境。

    pre

  3. 访问服务,触发Trace数据生成并发送。

    127.0.0.1:8079/hello

方案二:手动构造Trace数据并发送

如果您使用的是自建框架或有其他需求,可以手动构造Trace数据并发送到日志服务。更多信息,请参见opentelemetry-js

  1. 安装依赖包。

    npm install --save @opentelemetry/api
    npm install --save @opentelemetry/node
    npm install --save @opentelemetry/tracing
    npm install --save @opentelemetry/exporter-collector-grpc
  2. 初始化Tracer并启动express。

    如下代码中的变量需根据实际情况替换。更多信息,请参见变量说明

    const opentelemetry = require("@opentelemetry/api");
    const { registerInstrumentations } = require("@opentelemetry/instrumentation");
    const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
    const { Resource } = require("@opentelemetry/resources");
    const {
      SemanticResourceAttributes,
    } = require("@opentelemetry/semantic-conventions");
    const {
      SimpleSpanProcessor,
      ConsoleSpanExporter,
    } = require("@opentelemetry/tracing");
    const grpc = require("@grpc/grpc-js");
    const {
      CollectorTraceExporter,
    } = require("@opentelemetry/exporter-collector-grpc");
    
    const {
      ExpressInstrumentation,
    } = require("@opentelemetry/instrumentation-express");
    const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");
    
    var os = require("os");
    var hostname = os.hostname();
    
    const provider = new NodeTracerProvider({
      resource: new Resource({
        [SemanticResourceAttributes.SERVICE_NAME]: "${service}",
          [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: "${environment}",
        [SemanticResourceAttributes.SERVICE_VERSION]: "${version}",
        [SemanticResourceAttributes.SERVICE_NAMESPACE]: "${service.namespace}",
        [SemanticResourceAttributes.HOST_NAME]: hostname,
      }),
    });
    provider.register();
    
    registerInstrumentations({
      instrumentations: [
        new HttpInstrumentation(),
        new ExpressInstrumentation({
          ignoreLayersType: [new RegExp("middleware.*")],
        }),
      ],
      tracerProvider: provider,
    });
    
    var url = "${endpoint}";
    
    var logStdout = false;
    if (url == "stdout") {
      logStdout = true;
    }
    var meta = new grpc.Metadata();
    meta.add("x-sls-otel-project", "${project}");
    meta.add("x-sls-otel-instance-id", "${instance}");
    meta.add("x-sls-otel-ak-id", "${access-key-id}");
    meta.add("x-sls-otel-ak-secret", "${access-key-secret}");
    const collectorOptions = {
      url: url,
      credentials: grpc.credentials.createSsl(),
      metadata: meta,
    };
    const exporter = new CollectorTraceExporter(collectorOptions);
    
    if (!logStdout) {
      provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
    } else {
      var stdexporter = new ConsoleSpanExporter();
      provider.addSpanProcessor(new SimpleSpanProcessor(stdexporter));
    }
    provider.register();
    var tracer = opentelemetry.trace.getTracer("${service}");
    
    var express = require('express');
    
    var app = express()
    
    app.get('/hello', function (req, res, next) {
        const span = tracer.startSpan('hello');
        span.setAttribute('name', 'toma');
        span.setAttribute('age', '26');
        span.addEvent('invoking doWork');
    
        res.send("success");
    
        span.end();
    });
    
    var server = app.listen(8079, function () {
      var port = server.address().port;
      console.log("App now running in %s mode on port %d", app.get("env"), port);
    });
  3. 访问服务,触发Trace数据生成并发送。

    127.0.0.1:8079/hello

后续步骤