Construindo Métricas Personalizadas
Vamos então construir uma métrica personalizada. O código estará disponível na branch personal_metrics.
Uma vez definido o nosso instrumentador vemos que estamos usando um retorno que entrega meter instanciado
// Inicia o SDK - começa a coletar telemetria
sdk.start();
return { meter, meterProvider } // <<<<<
No código do todo-service.ts podemos aproveitar essas instâncias.
// start('todo-service')
const { meter } = start('todo-service');
E a partir disso podemos começar. Só para esclarecer app.use() no Express serve como um sistema de interceptação e processamento de requisições antes que elas cheguem às rotas finais. É muito usado para interceptar as requisições HTTP, processar ou modificar uma requisição antes dela chegar na rota final, executar ações comuns para multiplas rotas, adicionar uma camada de processamento, etc.
Vamos instanciar tempo de entrada antes de inciar a requisição e outro ao final e diminuir para ter o tempo de processamento.
// Criando uma métrica do tipo Histogram com o nome latency-call
const latencyCall = meter.createHistogram('latency_call', {
description: 'Latency of /todos endpoint calls in milliseconds'
});
app.use((req, res, next) => {
// Captura o tempo de início da requisição
const startTime = Date.now();
// Adiciona um listener para quando a requisição terminar
req.on('end', () => {
// Captura o tempo de fim da requisição
const endTime = Date.now();
// Registra as métricas da chamada
latencyCall.record(endTime - startTime, {
route: req.route?.path, // Rota que foi acessada
status: res.statusCode, // Código de status da resposta
method: req.method // Método HTTP usado (GET, POST, etc)
})
})
// Continua o processamento da requisição
next();
})
Só com isso já será possível ter uma medida do tempo de processamento de cada requisição.
Mas vamos criar também um contador para o número de vezes que esse endpoint foi acessado.
const requestCounter = meter.createCounter('todos_requests_total', {
description: 'Total number of requests to /todos endpoint'
});
E vamos incrementar toda vez que entrar em /todos.
//... Código anterior...
app.get('/todos', async (req, res) => {
requestCounter.add(1); //Incrementando
const user = await axios.get('http://auth:8080/auth');
//... Restante do código
Para simular vamos passar dois parametros. Se fail for passado então vamos retornar 500 e se slow for passado vamos fazer dormir 1 segundo.
Então temos o nosso código completo.
import start from './instrumentation';
// start('todo-service')
const { meter } = start('todo-service');
import express from 'express';
import axios from 'axios';
import IORedis from 'ioredis';
const app = express();
const redis = new IORedis({ host: 'redis' });
// Nossas duas métricas
const latencyCall = meter.createHistogram('latency_call', {
description: 'Latency of /todos endpoint calls in milliseconds'
});
const requestCounter = meter.createCounter('todos_requests_total', {
description: 'Total number of requests to /todos endpoint'
});
//Sempre será executado antes e ao final para pegar o tempo.
app.use((req,res,next)=>{
const startTime = Date.now();
req.on('end',()=>{
const endTime = Date.now();
latencyCall.record(endTime-startTime,{
route: req.route?.path,
status: res.statusCode,
method: req.method
})
})
next();
})
// Definimos uma função sleep só para ser usada no endpoint todos.
const sleep = (time:number)=>{return new Promise((resolve)=>{setTimeout(resolve,time)})};
app.get('/todos', async (req, res) => {
requestCounter.add(1); // Increntamos o contador
const user = await axios.get('http://auth:8080/auth');
const todoKeys = await redis.keys('todo:*');
const todos: any[] = [];
for (const key of todoKeys) {
const todoItem = await redis.get(key);
if (todoItem) {
todos.push(JSON.parse(todoItem));
}
}
// Se tiver a query slow dorme 1 segundos
if(req.query['slow']){
await sleep(1000);
}
// Se tiver a query fail para tudo e devolver 500
if(req.query['fail']){
await console.error('Really Bad error!');
res.sendStatus(500);
return
}
res.json({ todos, user: user.data });
});
app.listen(8080, () => {
console.log('Todo service is running on port 8080');
});
// Inicializa alguns todos para teste
async function init() {
await Promise.all([
redis.set('todo:1', JSON.stringify({ name: 'Configurar OpenTelemetry' })),
redis.set('todo:2', JSON.stringify({ name: 'Implementar tracing' })),
redis.set('todo:3', JSON.stringify({ name: 'Adicionar métricas' })),
redis.set('todo:4', JSON.stringify({ name: 'Configurar exporters' }))
]);
}
init().catch(console.error);
Vamos executar.
docker compose up --build
E fazer umas requisições
curl http://localhost:8081/todos
{"todos":[{"name":"Adicionar métricas"},{"name":"Configurar OpenTelemetry"},{"name":"Implementar tracing"},{"name":"Configurar exporters"}],"user":{"username":"David Prata"}}
curl http://localhost:8081/todos
{"todos":[{"name":"Adicionar métricas"},{"name":"Configurar OpenTelemetry"},{"name":"Implementar tracing"},{"name":"Configurar exporters"}],"user":{"username":"David Prata"}}
curl http://localhost:8081/todos
{"todos":[{"name":"Adicionar métricas"},{"name":"Configurar OpenTelemetry"},{"name":"Implementar tracing"},{"name":"Configurar exporters"}],"user":{"username":"David Prata"}}
curl http://localhost:8081/todos
{"todos":[{"name":"Adicionar métricas"},{"name":"Configurar OpenTelemetry"},{"name":"Implementar tracing"},{"name":"Configurar exporters"}],"user":{"username":"David Prata"}}
curl http://localhost:8081/todos\?slow
{"todos":[{"name":"Adicionar métricas"},{"name":"Configurar OpenTelemetry"},{"name":"Implementar tracing"},{"name":"Configurar exporters"}],"user":{"username":"David Prata"}}
curl http://localhost:8081/todos\?slow\=1
{"todos":[{"name":"Adicionar métricas"},{"name":"Configurar OpenTelemetry"},{"name":"Implementar tracing"},
{"name":"Configurarexporters"}],"user":{"username":"David Prata"}}
curl http://localhost:8081/todos\?fail
{"todos":[{"name":"Adicionar métricas"},{"name":"Configurar OpenTelemetry"},{"name":"Implementar tracing"},{"name":"Configurar exporters"}],"user":{"username":"David Prata"}}
curl http://localhost:8081/todos\?fail\=1
Internal Server Error
Fizemos 7 requisições então temos no Prometheus localhost:9090

E para ver a latência?
