Distributed Tracing com plugins do OpenTelemetry, Node e Jaeger
Tags: opentelemetry, node, jaeger, express
Published: 2023-01-09 00:49:24

[No dia 30 de março](https://medium.com/opentelemetry/opentelemetry-is-officially-in-beta-352fa859db10) foi anunciada a fase beta do OpenTelemetry. O objetivo do projeto é facilitar o rastreamento distribuído através de uma série de ferramentas integradas como descrito no meu [primeiro artigo](todo:link) sobre o assunto. Entre as metas estabelecidas para o lançamento do beta estava a integração de plugins que permitem instrumentação automática das aplicações, diminuindo de forma significativa o trabalho necessário para implementação. Os repositórios das bibliotecas já funcionais e em desenvolvimento podem ser encontrados na [página do projeto no github](https://github.com/open-telemetry).

Esse artigo trás um exemplo de Distributed Tracing de aplicações Node usando http, [Express](link) e [IORedis](link) para simular o monitoramento de uma arquitetura de micro serviços usando a API javascript do OpenTelemetry e os plugins de instrumentação automática de cada módulo. Vamos explorar brevemente pedaços de código para ilustrar o funcionamento do sistema e o que é necessário ser feito para ter as informações de execução disponíveis no Jaeger como na imagem: 

![Imgur](https://i.imgur.com/2mZ2RMj.png)
 <figcaption id="fig1">
Visualização dos spans de um trace na interface do Jaeger
</figcaption>

Nossa implementação de exemplo consiste em dois servidores, o primeiro simula uma API que serve de primeiro ponto de contato na comunicação e repassa as requisições para o segundo, que é responsável por buscar os dados necessários acessando o [redis](link). Recomendo muito dar uma olhada no código completo [aqui](link) e para rodar basta fazer `docker-compose up` ou seguir as instruções para cada serviço. No compose temos os containers de cada serviço e o [Jaeger](link) que vai servir como backend e interface para análise dos spans coletados.

Como uma aplicação de exemplo, o funcionamento é bem simplificado. Ao acessar `localhost:3000` pela primeira vez um resultado vazio é retornado.

![Imgur](https://i.imgur.com/Ulffjf3.png)

Essa rota acessa o middleware e pede os valores para as keys `key1`, `key2` e `key3`.

```javascript
// api.js
...
const values = ['key1', 'key2', 'key3']

app.get('/', async (_, res) => {
  console.log('get /', middleware_url);
  const resultList = await Promise.all(
    values.map(v => httpRequest(middleware_url, 4000, '/' + v))
  )

  console.log({ resultList });
  res.status(200).send(resultList.toString());
})
...
```

Usamos a rota `/set-values` para popular esses valores:
```javascript
// api.js
...
  app.get('/set-values', async (_, res) => {
    const testItens = [
      { key: 'key1', value: 'value1' },
      { key: 'key2', value: 'value2' },
      { key: 'key3', value: 'value3' },
    ]

    await Promise.all(
      testItens.map(i => httpRequest(middleware_url, 4000, `/set?key=${i.key}&value=${i.value}`))
    ).then((result) => {
      res.status(200).send(result.toString());
    })
  })
...
```

e agora ao acessar `localhost:3000` obtemos os valores para as chaves solicitadas:

![Imgur](https://i.imgur.com/JOqLS4F.png)

Se você rodou o código a partir do repositório completo e fez esses 3 passos já pode visualizar os traces gerados na interface do Jaeger em: http://localhost:16686 selecionando o Service `api` e clicando em _find traces_.

![Imgur](https://i.imgur.com/5eE4GKZ.png)

Isso é possível graças aos plugins do OpenTelemetry. Com a instrumentação automática não é preciso mudar cada parte do código para adicionar o tracing. Os plugins fazem patch dos módulos e escutam as chamadas internas adicionando a passagem de contexto onde é necessário.

No topo dos arquivos `api.js` e `middleware.js` você verá: 
```javascript
const { createTracer } = require('./tracer');
createTracer('api');
```

O tracer importa as bibliotecas do OpenTelemetry e inicializa o [`NodeTracerProvider`](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-node#usage) que precisa ser definido antes do import dos [módulos suportados](https://github.com/open-telemetry/opentelemetry-js#plugins). Aqui também é configurado o exporter do Jaeger.

```javascript
// tracer.js
const opentelemetry = require('@opentelemetry/api');
const { NodeTracerProvider } = require('@opentelemetry/node');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const { SimpleSpanProcessor } = require('@opentelemetry/tracing');

const createTracer = (serviceName) => {
  const provider = new NodeTracerProvider();
  exporter = new JaegerExporter({
    serviceName,
    host: 'jaeger'
  });
  provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
  provider.register();

  return opentelemetry.trace.getTracer(serviceName)
}


module.exports = {
  createTracer,
}
```