Node.js

¡Obtén telemetría para tu aplicación en menos de 5 minutos!

Esta página te mostrará cómo comenzar con OpenTelemetry en Node.js.

Aprenderás cómo instrumentar tanto trazas como métricas y registrarlas en la consola.

Requisitos previos

Asegúrate de tener instalados localmente las siguientes dependencias:

Aplicación de ejemplo

El siguiente ejemplo utiliza una aplicación básica de Express. Si no estás usando Express, no hay problema, ya que también puedes usar OpenTelemetry JavaScript con otros frameworks web, como Koa y Nest.JS. Para obtener una lista completa de las bibliotecas compatibles con otros frameworks, consulta el registro.

Para ver ejemplos más elaborados, consulta la sección de Ejemplos.

Dependencias

Para comenzar, configura un package.json vacío en un nuevo directorio:

npm init -y

A continuación, instala las dependencias de Express.

npm install typescript \
  ts-node \
  @types/node \
  express \
  @types/express

# Inicializa typescript
npx tsc --init
npm install express

Crea y lanza un servidor HTTP

Crea un archivo llamado app.ts (o app.js si no usas TypeScript) y añade lo siguiente:

/*app.ts*/
import express, { Express } from 'express';

const PORT: number = parseInt(process.env.PORT || '8080');
const app: Express = express();

function getRandomNumber(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

app.get('/rolldice', (req, res) => {
  res.send(getRandomNumber(1, 6).toString());
});

app.listen(PORT, () => {
  console.log(`Escuchando solicitudes en http://localhost:${PORT}`);
});
/*app.js*/
const express = require('express');

const PORT = parseInt(process.env.PORT || '8080');
const app = express();

function getRandomNumber(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

app.get('/rolldice', (req, res) => {
  res.send(getRandomNumber(1, 6).toString());
});

app.listen(PORT, () => {
  console.log(`Escuchando solicitudes en http://localhost:${PORT}`);
});

Ejecuta la aplicación con el siguiente comando y abre http://localhost:8080/rolldice en tu navegador web para asegurarte de que funciona.

$ npx ts-node app.ts
Escuchando solicitudes en http://localhost:8080
$ node app.js
Escuchando solicitudes en http://localhost:8080

Instrumentación

Lo siguiente muestra cómo instalar, inicializar y ejecutar una aplicación instrumentada con OpenTelemetry.

Mas dependencias

Primero, instala el paquete Node SDK y el paquete de autoinstrumentaciones.

El Node SDK te permite inicializar OpenTelemetry con varias configuraciones predeterminadas que son adecuadas para la mayoría de los casos de uso.

El paquete auto-instrumentations-node instala bibliotecas de instrumentación que crearán automáticamente spans correspondientes al código llamado en bibliotecas. En este caso, proporciona instrumentación para Express, lo que permite que la aplicación de ejemplo cree spans automáticamente para cada solicitud entrante.

npm install @opentelemetry/sdk-node \
  @opentelemetry/api \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/sdk-metrics \
  @opentelemetry/sdk-trace-node

Para encontrar todos los módulos de autoinstrumentación, puedes consultar el registro.

Configuración

La configuración e inicialización de la instrumentación debe ejecutarse antes que el código de tu aplicación. Una herramienta comúnmente utilizada para esta tarea es la opción –require.

Crea un archivo llamado instrumentation.ts (o instrumentation.js si no usas TypeScript), que contendrá el código de configuración de tu instrumentación.

/*instrumentation.ts*/
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import {
  PeriodicExportingMetricReader,
  ConsoleMetricExporter,
} from '@opentelemetry/sdk-metrics';

const sdk = new NodeSDK({
  traceExporter: new ConsoleSpanExporter(),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new ConsoleMetricExporter(),
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();
/*instrumentation.js*/
// Require dependencies
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-node');
const {
  getNodeAutoInstrumentations,
} = require('@opentelemetry/auto-instrumentations-node');
const {
  PeriodicExportingMetricReader,
  ConsoleMetricExporter,
} = require('@opentelemetry/sdk-metrics');

const sdk = new NodeSDK({
  traceExporter: new ConsoleSpanExporter(),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new ConsoleMetricExporter(),
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

Ejecutar la aplicación instrumentada

Ahora puedes ejecutar tu aplicación como lo harías normalmente, pero puedes usar la opción --require para cargar la instrumentación antes del código de la aplicación. Asegúrate de no tener otras opciones --require en conflicto, como --require @opentelemetry/auto-instrumentations-node/register en tu variable de entorno NODE_OPTIONS.

$ npx ts-node --require ./instrumentation.ts app.ts
Escuchando solicitudes en http://localhost:8080
$ node --require ./instrumentation.js app.js
Escuchando solicitudes en http://localhost:8080

Abre http://localhost:8080/rolldice en tu navegador web y recarga la página varias veces. Después de un momento, deberías ver los spans impresos en la consola por el ConsoleSpanExporter.

Ver salida de ejemplo
{
  "traceId": "3f1fe6256ea46d19ec3ca97b3409ad6d",
  "parentId": "f0b7b340dd6e08a7",
  "name": "middleware - query",
  "id": "41a27f331c7bfed3",
  "kind": 0,
  "timestamp": 1624982589722992,
  "duration": 417,
  "attributes": {
    "http.route": "/",
    "express.name": "query",
    "express.type": "middleware"
  },
  "status": { "code": 0 },
  "events": []
}
{
  "traceId": "3f1fe6256ea46d19ec3ca97b3409ad6d",
  "parentId": "f0b7b340dd6e08a7",
  "name": "middleware - expressInit",
  "id": "e0ed537a699f652a",
  "kind": 0,
  "timestamp": 1624982589725778,
  "duration": 673,
  "attributes": {
    "http.route": "/",
    "express.name": "expressInit",
    "express.type": "middleware"
  },
  "status": { code: 0 },
  "events": []
}
{
  "traceId": "3f1fe6256ea46d19ec3ca97b3409ad6d",
  "parentId": "f0b7b340dd6e08a7",
  "name": "request handler - /",
  "id": "8614a81e1847b7ef",
  "kind": 0,
  "timestamp": 1624982589726941,
  "duration": 21,
  "attributes": {
    "http.route": "/",
    "express.name": "/",
    "express.type": "request_handler"
  },
  "status": { code: 0 },
  "events": []
}
{
  "traceId": "3f1fe6256ea46d19ec3ca97b3409ad6d",
  "parentId": undefined,
  "name": "GET /",
  "id": "f0b7b340dd6e08a7",
  "kind": 1,
  "timestamp": 1624982589720260,
  "duration": 11380,
  "attributes": {
    "http.url": "http://localhost:8080/",
    "http.host": "localhost:8080",
    "net.host.name": "localhost",
    "http.method": "GET",
    "http.route": "",
    "http.target": "/",
    "http.user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
    "http.flavor": "1.1",
    "net.transport": "ip_tcp",
    "net.host.ip": "::1",
    "net.host.port": 8080,
    "net.peer.ip": "::1",
    "net.peer.port": 61520,
    "http.status_code": 304,
    "http.status_text": "NOT MODIFIED"
  },
  "status": { "code": 1 },
  "events": []
}

El span generado rastrea la duración de una solicitud a la ruta /rolldice.

Envía algunas solicitudes más al endpoint. Después de un momento, verás métricas en la salida de la consola, como las siguientes:

Ver salida de ejemplo
{
  descriptor: {
    name: 'http.server.duration',
    type: 'HISTOGRAM',
    description: 'measures the duration of the inbound HTTP requests',
    unit: 'ms',
    valueType: 1
  },
  dataPointType: 0,
  dataPoints: [
    {
      attributes: [Object],
      startTime: [Array],
      endTime: [Array],
      value: [Object]
    }
  ]
}
{
  descriptor: {
    name: 'http.client.duration',
    type: 'HISTOGRAM',
    description: 'measures the duration of the outbound HTTP requests',
    unit: 'ms',
    valueType: 1
  },
  dataPointType: 0,
  dataPoints: []
}
{
  descriptor: {
    name: 'db.client.connections.usage',
    type: 'UP_DOWN_COUNTER',
    description: 'The number of connections that are currently in the state referenced by the attribute "state".',
    unit: '{connections}',
    valueType: 1
  },
  dataPointType: 3,
  dataPoints: []
}
{
  descriptor: {
    name: 'http.server.duration',
    type: 'HISTOGRAM',
    description: 'measures the duration of the inbound HTTP requests',
    unit: 'ms',
    valueType: 1
  },
  dataPointType: 0,
  dataPoints: [
    {
      attributes: [Object],
      startTime: [Array],
      endTime: [Array],
      value: [Object]
    }
  ]
}
{
  descriptor: {
    name: 'http.client.duration',
    type: 'HISTOGRAM',
    description: 'measures the duration of the outbound HTTP requests',
    unit: 'ms',
    valueType: 1
  },
  dataPointType: 0,
  dataPoints: []
}
{
  descriptor: {
    name: 'db.client.connections.usage',
    type: 'UP_DOWN_COUNTER',
    description: 'The number of connections that are currently in the state referenced by the attribute "state".',
    unit: '{connections}',
    valueType: 1
  },
  dataPointType: 3,
  dataPoints: []
}
{
  descriptor: {
    name: 'http.server.duration',
    type: 'HISTOGRAM',
    description: 'measures the duration of the inbound HTTP requests',
    unit: 'ms',
    valueType: 1
  },
  dataPointType: 0,
  dataPoints: [
    {
      attributes: [Object],
      startTime: [Array],
      endTime: [Array],
      value: [Object]
    }
  ]
}
{
  descriptor: {
    name: 'http.client.duration',
    type: 'HISTOGRAM',
    description: 'measures the duration of the outbound HTTP requests',
    unit: 'ms',
    valueType: 1
  },
  dataPointType: 0,
  dataPoints: []
}
{
  descriptor: {
    name: 'db.client.connections.usage',
    type: 'UP_DOWN_COUNTER',
    description: 'The number of connections that are currently in the state referenced by the attribute "state".',
    unit: '{connections}',
    valueType: 1
  },
  dataPointType: 3,
  dataPoints: []
}

Próximos pasos

Enriquece tu instrumentación generada automáticamente con instrumentación manual de tu propio código. Esto te permitirá obtener datos de observabilidad personalizados.

También querrás configurar un exporter apropiado para enviar tus datos de telemetría a uno o más backends de telemetría.

Si deseas explorar un ejemplo más complejo, revisa la demostración de OpenTelemetry, que incluye el servicio de pagos basado en JavaScript y el servicio de interfaz (frontend) basado en TypeScript.

Solución de problemas

¿Algo salió mal? Puedes habilitar el registro de diagnóstico para verificar que OpenTelemetry se inicializa correctamente:

/*instrumentation.ts*/
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';

// Para la solución de problemas, configura el nivel de registro en DiagLogLevel.DEBUG
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);

// const sdk = new NodeSDK({...
/*instrumentation.js*/
// Require dependencies
const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api');

// For troubleshooting, set the log level to DiagLogLevel.DEBUG
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);

// const sdk = new NodeSDK({...