Inserindo Observabilidade nos seus códigos Typescript
Nesse artigo irei apresentar funcionalidades básicas de observabilidade para inserir nos seus projetos em Nest Js, para que o gerenciamento da sua aplicação e tratamento de erros se torne algo cada vez mais simples no seu desenvolvimento e a correção de incidentes, mais rápida.
O que é o Opentelemetry
OpenTelemetry é uma ferramenta para observabilidade utilizada para criação e gerenciamento de traços, métricas e logs de uma aplicação. É uma funcionalidade open source com integração disponível a diversos sistemas de APM (Jaeger, Grafana, Dynatrace e Prometheus).
Jaeger
Jaeger é uma plataforma open-source para gerenciamento de trace
. Por ser simples, utilizaremos para monitoramento da nossa aplicação em ambiente local.
1 - Organize seu docker compose
Para conseguir testar em ambiente de desenvolvimento, utilizaremos uma ferramenta para gerenciamento de traces open source. Jaeger
Crie um arquivo chamado docker-compose.yaml
e insira o seguinte texto:
version: '3.0'
services:
jaeger:
platform: linux/x86_64
image: jaegertracing/all-in-one:1.53
environment:
- COLLECTOR_ZIPKIN_HTTP_PORT=9411
ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "4317:4317"
- "4318:4318"
- "16686:16686"
- "14250:14250"
- "14268:14268"
- "14269:14269"
- "9411:9411"
rode um
docker compose up -d
E o Jaeger já estará instrumentado.
Para testar se a instrumentação está correta abra a página
http://localhost:16686/
2 - Instrumentando os traces no Jaeger
As nossas métricas da aplicação, traces e logs de sistema serão instrumentados a partir de 'exporters' que são configurados a partir do SDK do NodeJS.
Para instrumentar o sdk do OpenTelemetry é necessário instalar a biblioteca necessária para implantação dessa classe
yarn add @opentelemetry/sdk-node
crie um arquivo para inicializar o sdk, inserindo o seguinte código:
import { NodeSDK } from "@opentelemetry/sdk-node";
const sdk = new NodeSDK({
})
sdk.start();
Após a criação do sdk, precisamos dizer ao OpenTelemetry para onde queremos exportar todos os logs que traços de sistema que ele gerar. Para facilitar esse trabalho, dentro do objeto de configuração do NodeSDK, podemos definir qual exporter
iremos utilizar. O próprio OpenTelemetry Protocol (OTLP)
nos fornece uma boa experiência e facilidade para exportar esses dados.
Há varios protocolos disponíveis para envio dessas informações aos coletores, por exemplo, JSON, protobuf ou gRPC.
Iremos utilizar utilizar o protocolo JSON, mas fique a vontade para utilizar o protocolo que quiser, todos irão funcionar bem.
Instale as dependencias abaixo para gerenciar os exporters
yarn add @opentelemetry/exporter-trace-otlp-http \
@opentelemetry/exporter-metrics-otlp-http \
@opentelemetry/sdk-metrics
Atualizando nossa instancia do SDK, ficaremos com o código da seguinte forma:
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { NodeSDK } from "@opentelemetry/sdk-node";
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({})
})
})
sdk.start();
OBS: Dentro de cada coletor, podemos definir URL's de destino da aplicação. Exemplo, ao utilizar o Dynatrace, temos que definir uma URL de destino dos traces
que criamos. Para realizar essa modificação. Dentro do objeto de OTLPTraceExporter
e OTLPMetricExporter
existe um campo, chamado url
, onde definimos o destino da informação
Exemplo:
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { NodeSDK } from "@opentelemetry/sdk-node";
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces'
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: 'http://localhost:4318/v1/metrics'
})
})
})
sdk.start();
Antes de finalizar a configuração do NodeSDK. Precisamos dizer ao NodeSDK do OpenTelemetry os dados da nossa aplicação, para conseguirmos identificar os traces de maneira mais rápida e como serão as instrumentações automáticas.
Para definir dados da aplicação para o OpenTelemetry, precisamos instalar a biblioteca @opentelemetry/resources
e @opentelemetry/semantic-conventions
yarn add @opentelemetry/resources \
@opentelemetry/semantic-conventions
Onde o código ficará da seguinte forma:
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: 'http://localhost:4318/v1/metrics',
}),
}),
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAMESPACE]: 'NOME DO SERVICO',
[SemanticResourceAttributes.SERVICE_VERSION]: 'VERSAO',
}),
});
sdk.start();
Para finalizar, a instrumentação automática, é criada a partir da @opentelemetry/auto-instrumentations-node
Então
yarn add @opentelemetry/auto-instrumentations-node
E modificamos o código, deixando-o da seguinte forma:
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: 'http://localhost:4318/v1/metrics',
}),
}),
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAMESPACE]: 'NOME',
[SemanticResourceAttributes.SERVICE_VERSION]: '0.2',
[SemanticResourceAttributes.SERVICE_NAME]: 'Nome Identificador da aplicação',
}),
instrumentations: [getNodeAutoInstrumentations()]
});
sdk.start();
3 - Criando Traces Manuais
Para criarmos os nossos traços na aplicação de forma manual, utilizaremos decorators que vão nos facilitar a não ficar repetindo código na aplicação inteira, para pegar todo o caminho de uma requisição ou lógica.
Criaremos um decorator, que por enquanto apenas executará o método sem modificá-lo em nada
export const Trace = (value?: string) => {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
const method = descriptor.value
const args = arguments;
descriptor.value = function() {
const that = this
return method?.apply(that, args)
}
};
};
Para iniciarmos a criação dos tracers, é necessário instalar mais uma biblioteca chamada @opentelemetry/api
yarn add @opentelemetry/api
Modificamos o decorator, para ficar do seguinte formato:
import { Span, trace } from '@opentelemetry/api';
export const Trace = (value?: string) => {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
const method = descriptor.value;
const args = arguments;
const tracer = trace.getTracer('');
descriptor.value = function () {
const that = this;
return tracer.startActiveSpan(
value ?? `${target.constructor.name}.${propertyKey}`,
(span: Span) => {
console.log(propertyKey)
const result = method?.apply(that, args);
span.end();
return result
}
);
};
};
};
Dessa forma conseguiremos todos os traços dos métodos que inserirem esse decorator
Criaremos também uma função de traceError para conseguir ter mapeados todos os erros que acontecerem na aplicação.
import { Exception, SpanStatusCode, trace } from "@opentelemetry/api";
export function traceError(error: any) {
const activeSpan = trace.getActiveSpan();
activeSpan?.recordException(error as Exception);
activeSpan?.setAttribute(
"exception.response",
JSON.stringify(
{
data: "Erro AXIOS",
},
null,
2
)
);
activeSpan?.setStatus({
code: SpanStatusCode.ERROR,
message: `Error: ${error.message}`,
});
}
Top comments (0)