OpenTelemetry com ExpressJS

Hoje vamos falar um pouco sobre como realizar a instrumentação do OpenTelemetry numa aplicação Node utilizando o ExpressJS para exibição de dados no Instana.

Cansado de ver exemplos que não me serviram, resolvi postar aqui como realizamos esse processo numa aplicação BFF (back-end for front-end) da equipe.

Dito isso, o objetivo era instrumentar a app de modo que as requisições externas e as chamadas ao Redis fossem monitoradas.

Dados da aplicação

Abaixo estão alguns dados relevantes da aplicação onde o processo foi realizado:

Primeiro passo

Alterar o comando que roda a aplicação no package.json, no meu caso era o npm start:

"start": "NODE_ENV=production node dist/index.js",

Vamos adicionar ao comando acima a parte que executará um arquivo chamado tracing.js e iniciará o OpenTelemetry:

"start": "NODE_ENV=production node --require ./dist/utils/tracing.js dist/index.js",

Preste atenção em que pasta colocará seu arquivo tracing.js, no exemplo acima ele está na pasta dist/utils mas você pode criá-lo onde quiser.
Se você criá-lo na raíz e utilizar um Dockerfile, lembre-se de incluí-lo na imagem.

O arquivo tracing.js

Abaixo nós temos o código completo do nosso arquivo e farei alguns comentários.

const { Resource } = require("@opentelemetry/resources");
const {
  SemanticResourceAttributes,
} = require("@opentelemetry/semantic-conventions");
const { registerInstrumentations } = require("@opentelemetry/instrumentation");
const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
const {
  ExpressInstrumentation,
} = require("@opentelemetry/instrumentation-express");
const {
  RedisInstrumentation,
} = require("@opentelemetry/instrumentation-redis");
const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");
const {
  OTLPTraceExporter,
} = require("@opentelemetry/exporter-trace-otlp-grpc");
const { diag, DiagConsoleLogger, DiagLogLevel } = require("@opentelemetry/api");

const logLevel = "DEBUG"; // também pode receber o valor ERROR. indico após a implementação estar correta
const serviceName = "<nome-da-sua-aplicacao>";

try {
  diag.setLogger(new DiagConsoleLogger(), DiagLogLevel[String(logLevel)]);

  const exporter = new OTLPTraceExporter();

  const provider = new NodeTracerProvider({
    resource: new Resource({
      [SemanticResourceAttributes.SERVICE_NAME]: serviceName,
    }),
  });

  provider.addSpanProcessor(new BatchSpanProcessor(exporter));

  registerInstrumentations({
    tracerProvider: provider,
    instrumentations: [
      new HttpInstrumentation({
        responseHook: (span) => {
          const { attributes: attrs = {} } = span;
          span.updateName(`${attrs["http.method"]} ${attrs["http.target"]}`);
          span.setAttribute("functions.route", attrs["http.route"]);
          span.setAttribute("functions.url", attrs["http.url"]);
        },
        ignoreIncomingPaths: [/.*\/healthcheck/],
      }),
      new ExpressInstrumentation(),
      new RedisInstrumentation(),
    ],
  });

  provider.register();
} catch (error) {
  console.error("tracing.js catch", { error });
}

Dependências

Escolha seu terminal preferido e vamos lá:

npm install --save @grpc/grpc-js @opentelemetry/api @opentelemetry/exporter-trace-otlp-grpc @opentelemetry/instrumentation @opentelemetry/instrumentation-express @opentelemetry/instrumentation-http @opentelemetry/instrumentation-redis @opentelemetry/resources @opentelemetry/sdk-node @opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-node @opentelemetry/semantic-conventions

Sobre auto instrumentação

Testei libs como @opentelemetry/auto-instrumentations-node e @opentelemetry/auto-instrumentations-web que prometem reconhecer automaticamente telemetria de diversos tipos de bibliotecas e frameworks populares e tive uma péssima experiência.

Um dos erros que enfrentei e não consegui resolver utilizando essas libs foi:

Module @grpc/grpc-js has been loaded before @opentelemetry/instrumentation-grpc so it might not work, please initialize it before requiring @grpc/grpc-js

Caí neste tópico do StackOverflow: Adding opentelemetry tracing to Node.js app breaks require(“fs”).realpathSync.native, tentei os modos indicados, também cavoquei mais fundo a web e nada. Desencorajo o uso.

Se você precisa instrumentar algo que não está no exemplo, aconselho: