Primeiras Métricas
Agora vamos instrumentar as métricas. Até o presente momento fizemos só a parte de trace.
Instalamos todos esses pacotes, mas nem usamos todos ainda.
npm install @opentelemetry/sdk-node \
@opentelemetry/api \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/sdk-metrics \ ###ESSE
@opentelemetry/sdk-trace-node
Se estamos falando de métricas temos que ter uma ferramenta para guardar as métricas, então vamos adicionar na nossa stack aqui o prometheus.
O prometheus quem faz o scrape da métrica que a aplicação mostra no endpoint /metrics. Então temos que configurá-lo apontando quais os endpoints ele irá buscar e precisamos passar os endpoints das aplicações.
Vamos criar uma pasta prometheus que será mapeada para o container do prometheus em /etc/prometheus e colocar arquivo prometheus.yaml com a configuração do Prometheus dentro que o prometheus suba com essa configuração.
global:
# Default é 1 minuto só vamos diminuir para ficar rápido
scrape_interval: "5s"
scrape_configs:
- job_name: 'opentelemetry'
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- todo:9464
- auth:9464
E ficamos assim no projeto
.
├── docker
│ ├── Dockerfile.auth
│ └── Dockerfile.todo
├── docker-compose.yml
├── package-lock.json
├── package.json
├── prometheus
│ └── prometheus.yaml
├── src
│ └── services
│ ├── auth-service.ts
│ ├── instrumentation.ts
│ └── todo-service.ts
└── tsconfig.json
5 directories, 10 files
Nosso docker-compose ficará assim.
version: '3.8'
services:
auth:
build:
context: .
dockerfile: docker/Dockerfile.auth
volumes:
- .:/usr/src/app:rw
- /usr/src/app/node_modules
ports:
- 8080:8080
# Para acessar o /metrics que vamos criar
- 9464:9464
todo:
build:
context: .
dockerfile: docker/Dockerfile.todo
volumes:
- .:/usr/src/app:rw
- /usr/src/app/node_modules
ports:
- 8081:8080
# Para acessar o /metrics que vamos criar
- 9465:9464
depends_on:
- redis
redis:
image: redis:alpine
ports:
- '6380:6379'
jaeger:
image: jaegertracing/all-in-one
ports:
- 4318
- 16686:16686
environment:
COLLECTOR_OTLP_ENABLED: true
prometheus:
image: prom/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yaml'
ports:
# Porta do prometheus
- 9090:9090
volumes:
- ./prometheus/:/etc/prometheus/
E agora precisamos fazer as aplicações expor o endpoint /metrics com várias métricas diferentes.
Vamos entender um pouco as coisas antes de continuar.
-
O meterProvider é o uma espécie de hub de métricas. As métricas dentro dele podem ser amostradas em vários lugares ao mesmo tempo. Só para enfatizar, vamos exportar as métricas ao mesmo tempo no endpoint /metrics gerado pelo prometheusExporter mas também no console da aplicação. Existe também o OTLPMetricExporter para enviar métricas para outros sistemas que suportam o protocolo OTLP, como:
- OpenTelemetry Collector
- New Relic
- Datadog
- Dynatrace
- Grafana Agent
-
As métricas do sistema operacional (CPU, memória, etc.) precisam de um tratamento especial. Por necessitarem de permissões para acessar recursos do sistema, são coletadas por um processo separado (HostMetrics). Essas métricas são configuradas para usar o mesmo meterProvider como destino das métricas.
/*instrumentation.ts*/
// Importações principais do OpenTelemetry
import { NodeSDK } from '@opentelemetry/sdk-node'; // SDK base para Node.js
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; // Instrumentação automática
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; // Exportador de traces para Jaeger
import { metrics } from '@opentelemetry/api';
// Recursos para identificação do serviço
import { Resource } from '@opentelemetry/resources'; // Irá funcionar como uma etiqueta
import {
ATTR_SERVICE_NAME,
} from '@opentelemetry/semantic-conventions';
// Componentes para métricas do Prometheus
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'
import {
MeterProvider,
ConsoleMetricExporter,
PeriodicExportingMetricReader
} from '@opentelemetry/sdk-metrics';
import { HostMetrics} from '@opentelemetry/host-metrics';
function start(serviceName: string) {
// Define um resource com o nome do serviço - será usado tanto para traces quanto métricas
const resource = new Resource({
[ATTR_SERVICE_NAME]: serviceName,
});
// Configura o exportador do Prometheus
// - Cria um endpoint /metrics na porta 9464
// - Este endpoint será usado pelo Prometheus para coletar métricas
const prometheusExporter = new PrometheusExporter({
port: PrometheusExporter.DEFAULT_OPTIONS.port,
endpoint: PrometheusExporter.DEFAULT_OPTIONS.endpoint,
}, () => {
console.log(
`prometheus scrape endpoint: http://localhost:${PrometheusExporter.DEFAULT_OPTIONS.port}${PrometheusExporter.DEFAULT_OPTIONS.endpoint}`,
);
});
// Criamos o ConsoleMetricExporter, mas para ele ser usado no meterProvider é necessário estar em um consoleReader
const consoleExporter = new ConsoleMetricExporter();
const consoleReader = new PeriodicExportingMetricReader({
exporter: consoleExporter,
exportIntervalMillis: 10000, // Iré mostrar no console a cada 10 segundos
});
// Configuramos o MeterProvider com ambos exporters
const meterProvider = new MeterProvider({
resource,
readers: [prometheusExporter, consoleReader]
});
const hostMetrics = new HostMetrics({
meterProvider: meterProvider,
name: `${serviceName}-host-metrics`,
});
hostMetrics.start();
// Cria um meter específico para este serviço
const meter = meterProvider.getMeter(serviceName);
// Define o MeterProvider global
metrics.setGlobalMeterProvider(meterProvider);
// Instrumentações de aplicação
const instrumentations = [
getNodeAutoInstrumentations({
//Cada um desses ainda tem suas opções
'@opentelemetry/instrumentation-express': { },
'@opentelemetry/instrumentation-ioredis': { },
'@opentelemetry/instrumentation-http': { },
'@opentelemetry/instrumentation-dns': { },
'@opentelemetry/instrumentation-net': { },
'@opentelemetry/instrumentation-fs': { }
//
})
];
// Configura o exportador de traces para enviar ao Jaeger
const traceExporter = new OTLPTraceExporter({
url: 'http://jaeger:4318/v1/traces',
});
const sdk = new NodeSDK({
resource,
traceExporter,
instrumentations
});
// Inicia o SDK - começa a coletar telemetria
sdk.start();
// Na saída vamos entregar o meter e o meterProvider que será utilizado posteriormente para criarmos métricas personalizadas.
return { meter, meterProvider }
}
export default start
Não vamos mudar nada ainda nos serviços, isso é tarefa para mais tarde.
Aqui uma amostra do /metrics para a aplicação TODO.

E aqui saindo no terminal também para mostrar que o meterProvider esta publicando em dois lugares ao mesmo tempo. Obviamente que em produção não vamos fazer isso, nem faz sentido.

Aqui no prometheus temos as métricas foram exportadas.

Claro que vamos instrumentar a aplicação de acordo com a steck de tecnologia usada. Só de curiosidade temos várias coisas que poderíamos querer usar.
Sobre essas instrumentações que estamos definindo abaixo, elas são instrumentações automáticas para o node. Dentro de cada bloco dessas instrumentações temos opções extras que precisam ser estudadas parar saber o que queremos habilitar e desatabilitar, no nosso caso mantemos tudo default.
// Aqui
const instrumentations = [
getNodeAutoInstrumentations({
//Cada um desses ainda tem suas opções
'@opentelemetry/instrumentation-express': { },
'@opentelemetry/instrumentation-ioredis': { },
'@opentelemetry/instrumentation-http': { },
'@opentelemetry/instrumentation-dns': { },
'@opentelemetry/instrumentation-net': { },
'@opentelemetry/instrumentation-fs': { }
//
})
];
// ...
const sdk = new NodeSDK({
resource,
traceExporter,
instrumentations // E injetando aqui
});
Se uma métricas não é útil para você, não use.
Muitas vezes é mais interessante criar nossas próprias métricas e é isso que vamos fazer, mas antes vamos entender sobre os tipos de métricas.
O código se encontra na branch otel_com_metric do projeto.
Se quiser entender um pouco mais antecipadamente confira em Configurando o OTel SDK.