Introdução à Observabilidade de Aplicações
O que é a Observabilidade de Aplicações?
Observabilidade de Aplicações permite entender um sistema externamente, possibilitando fazer perguntas sobre ele sem precisar conhecer seu funcionamento interno. Além disso, facilita a solução de problemas desconhecidos e imprevistos . A observabilidade ajuda a responder à pergunta: “Por que isso está acontecendo?”
Para fazer perguntas sobre um sistema, é necessário que sua aplicação esteja devidamente instrumentada. Isso significa que o código da aplicação ou algum componente intimamente ligado à aplicação como o Application Server, máquina virtual da linguagem da aplicação ou intepretador de comandos da linguagem devem emitir sinais, como rastreamentos (traces), métricas e logs. Uma aplicação está bem instrumentada quando os desenvolvedores não precisam adicionar mais instrumentação para investigar problemas, pois já possuem todas as informações necessárias.
O Priax, além de possuir mecanismos de instrumentação próprios pode é compatível com mecanismos consagrados do mercado como OpenTelemetry, Jaeger ou Opensearch APM. Todos esses mecanismos são usados para instrumentar o código da aplicação e tornar um sistema observável.
Telemetria, métricas e confiabilidade de Aplicações
Telemetria se refere aos dados emitidos por um sistema e seu comportamento. Esses dados podem ser apresentados na forma de rastreamentos, métricas e logs.
A confiabilidade responde à pergunta: “O serviço está fazendo o que os usuários esperam que ele faça?”. Por exemplo, um sistema pode estar disponível 100% do tempo, mas, se ao clicar em "Adicionar ao Carrinho" para incluir um par de sapatos pretos, o sistema não adicionar sempre os sapatos pretos, ele é considerado não confiável.
Métricas são agregações ao longo do tempo de dados numéricos sobre sua infraestrutura ou aplicação. Exemplos incluem:
- Taxa de erro do sistema
- Uso da CPU
- Taxa de solicitações de um serviço
SLI (Service Level Indicator) é uma medida do comportamento de um serviço. Um bom SLI mede o serviço do ponto de vista dos usuários. Um exemplo de SLI é a velocidade de carregamento de uma página web.
SLO (Service Level Objective) é a forma como a confiabilidade é comunicada dentro da organização ou para outras equipes, vinculando um ou mais SLIs ao valor de negócio.
Entendendo o Rastreamento Distribuído
O rastreamento distribuído permite observar solicitações conforme elas se propagam por sistemas distribuídos e complexos. Ele melhora a visibilidade sobre a saúde de uma aplicação ou sistema e ajuda a depurar comportamentos difíceis de reproduzir localmente.
O rastreamento distribuído é essencial para sistemas distribuídos, que frequentemente apresentam problemas não determinísticos ou complexos demais para serem reproduzidos em um ambiente local.
Para entender o rastreamento distribuído, é importante conhecer seus principais componentes: logs, spans e traces.
Logs
Um log é uma mensagem com carimbo de tempo emitida por serviços ou componentes. Diferente dos rastreamentos, os logs não estão necessariamente associados a uma solicitação ou transação específica. Eles estão presentes em praticamente todo software e foram amplamente utilizados por desenvolvedores e operadores para entender o comportamento dos sistemas.
Exemplo de log:
I, [2021-02-23T13:26:23.505892 #22473] INFO -- : [6459ffe1-ea53-4044-aaa3-bf902868f730] Started GET "/" for ::1 at 2021-02-23 13:26:23 -0800
No entanto, os logs não são suficientes para rastrear a execução do código, pois geralmente carecem de informações contextuais, como o local de origem da chamada.
Os logs se tornam muito mais úteis quando são incluídos como parte de um span ou quando estão correlacionados a um trace e um span.
Spans
Um span representa uma unidade de trabalho ou uma operação. Ele rastreia operações específicas de uma solicitação, fornecendo uma visão detalhada do que ocorreu durante sua execução.
Um span inclui:
- Nome
- Dados temporais
- Mensagens de log estruturadas
- Metadados (Atributos) que fornecem mais informações sobre a operação rastreada.
Atributos de Span
Os atributos são metadados associados a um span.
Chave | Valor |
http.request.method | GET |
network.protocol.version | 1.1 |
url.path | /webshop/articles/4 |
url.query | ?s=1 |
server.address | example.com |
server.port | 8080 |
url.scheme | https |
http.route | /webshop/articles/:article_id |
http.response.status_code | 200 |
client.address | 192.0.2.4 |
client.socket.address | 192.0.2.5 (o cliente passa por um proxy) |
user_agent.original | Mozilla/5.0 (Windows NT 10.0; Win64; ...) |
Rastreamentos Distribuídos
Um rastreamento distribuído (trace) registra os caminhos percorridos por solicitações (feitas por uma aplicação ou usuário final) enquanto atravessam arquiteturas multi-serviço, como aplicações baseadas em microsserviços ou serverless.
Um trace é composto por um ou mais spans:
- O span raiz representa o início e o fim de uma solicitação.
- Os spans filhos fornecem um contexto detalhado sobre o que ocorre durante a solicitação.
Sem rastreamento, identificar a causa raiz de problemas de desempenho em sistemas distribuídos pode ser desafiador. O rastreamento simplifica a depuração e facilita o entendimento de sistemas complexos, detalhando o que acontece com uma solicitação enquanto ela se propaga pelo sistema.
Muitos backends de observabilidade visualizam os traces como diagramas de cascata (waterfall diagrams), que demonstram a relação entre spans pai e filhos, representando relações hierárquicas e aninhadas.
Propagação de Contexto
Entenda o conceito que viabiliza o Rastreamento Distribuído.
Com a propagação de contexto, os sinais podem ser correlacionados entre si, independentemente de onde são gerados. Embora não se limite apenas ao rastreamento, a propagação de contexto permite que os rastreamentos construam informações causais sobre um sistema que está distribuído de forma arbitrária entre processos e limites de rede.
Para entender a propagação de contexto, é necessário conhecer dois conceitos principais: contexto e propagação.
Contexto
O contexto é um objeto que contém as informações necessárias para que os serviços emissores e receptores, ou unidades de execução, possam correlacionar um sinal com outro.
Por exemplo: se o serviço A chama o serviço B, um span do serviço A, cujo ID está presente no contexto, será utilizado como span pai para o próximo span criado no serviço B. O ID do rastreamento (trace ID) também será incluído no contexto e utilizado para o próximo span criado no serviço B. Isso significa que esse novo span fará parte do mesmo rastreamento que o span do serviço A.
Propagação
A propagação é o mecanismo responsável por mover o contexto entre serviços e processos. Ela serializa ou desserializa o objeto de contexto e fornece as informações relevantes para que o contexto seja transferido de um serviço para outro.
Geralmente, a propagação é gerenciada automaticamente pelas bibliotecas de instrumentação e é transparente para o usuário. No entanto, caso seja necessário realizar a propagação de contexto manualmente, você pode utilizar a API de Propagadores (Propagators API) de cada biblioteca de instrumentação.
Sinais
Conheça as categorias de telemetria suportadas pelo Priax e bibliotecas de instrumentação compatíveis:
O objetivo de uma biblioteca de instrumentação é coletar, processar e exportar sinais. Sinais são saídas do sistema que descrevem a atividade subjacente do sistema operacional e das aplicações em execução em uma plataforma. Um sinal pode representar algo que você deseja medir em um ponto específico no tempo, como a temperatura ou o uso de memória, ou um evento que percorre os componentes de um sistema distribuído e que você gostaria de rastrear.
Com o Priax você pode agrupar diferentes sinais para observar o funcionamento interno de uma mesma tecnologia sob diferentes perspectivas.
Atualmente, o Priax é capaz de exibir, detalhar, quantificar e detalhar as seguintes categorias de sinais:
- Rastreamentos (traces)
- Métricas
- Logs
- Baggage
Rastreamentos (Traces)
O caminho de uma solicitação através da sua aplicação.
Os rastreamentos nos dão uma visão geral do que acontece quando uma solicitação é feita a uma aplicação. Seja sua aplicação um monólito com um único banco de dados ou uma complexa malha de serviços, os rastreamentos são essenciais para entender o “caminho” completo que uma solicitação percorre na sua aplicação.
Vamos explorar isso com três unidades de trabalho representadas como Spans (faixas de execução):
Observação
Os exemplos de JSON a seguir não representam um formato específico, especialmente o OTLP/JSON, que é mais detalhado.
1. Span Hello:
{
"name": "hello",
"context": {
"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "051581bf3cb55c13"
},
"parent_id": null,
"start_time": "2022-04-29T18:52:58.114201Z",
"end_time": "2022-04-29T18:52:58.114687Z",
"attributes": {
"http.route": "some_route1"
},
"events": [
{
"name": "Guten Tag!",
"timestamp": "2022-04-29T18:52:58.114561Z",
"attributes": { "event_attributes": 1 }
}
]
}
Este é o span raiz, indicando o início e o fim da operação inteira. Observe que ele tem um campo trace_id, mas não tem parent_id, o que o caracteriza como a raiz do rastreamento.
2. Span hello-greetings:
{
"name": "hello-greetings",
"context": {
"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "5fb397be34d26b51"
},
"parent_id": "051581bf3cb55c13",
"start_time": "2022-04-29T18:52:58.114304Z",
"end_time": "2022-04-29T22:52:58.114561Z",
"attributes": {
"http.route": "some_route2"
},
"events": [
{
"name": "hey there!",
"timestamp": "2022-04-29T18:52:58.114561Z",
"attributes": {
"event_attributes": 1
}
},
{
"name": "bye now!",
"timestamp": "2022-04-29T18:52:58.114585Z",
"attributes": {
"event_attributes": 1
}
}
]
}
Este span encapsula tarefas específicas, como exibir saudações, e seu parent_id é o ID do span raiz hello. Ele compartilha o mesmo trace_id, indicando que faz parte do mesmo rastreamento.
3. Span hello-salutations:
{
"name": "hello-salutations",
"context": {
"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "93564f51e1abe1c2"
},
"parent_id": "051581bf3cb55c13",
"start_time": "2022-04-29T18:52:58.114492Z",
"end_time": "2022-04-29T18:52:58.114631Z",
"attributes": {
"http.route": "some_route3"
},
"events": [
{
"name": "hey there!",
"timestamp": "2022-04-29T18:52:58.114561Z"
}
]
}
Este span representa a terceira operação neste rastreamento e, assim como o anterior, é um filho do span raiz (hello). Isso também o torna um irmão do span hello-greetings
Esses três blocos de JSON compartilham o mesmo trace_id e utilizam o parent_id para representar a hierarquia. Isso cria o rastreamento completo!
Uma observação importante é que cada Span se assemelha a um log estruturado, com contexto, correlação e hierarquia embutidos. Contudo, esses “logs estruturados” podem vir de diferentes processos, serviços, VMs ou datacenters, permitindo que o rastreamento represente uma visão ponta a ponta de qualquer sistema.
Detalhamento de uma span
Uma span representa uma unidade de trabalho ou operação e inclui:
- Nome
- ID do Span pai (vazio para spans raiz)
- Timestamps de início e fim
- Contexto do Span
- Atributos
- Eventos
- Links
- Status do Span
Exemplo de span:
{
"name": "/v1/sys/health",
"context": {
"trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d",
"span_id": "086e83747d0e381e"
},
"parent_id": "",
"start_time": "2021-10-22 16:04:01.209458162 +0000 UTC",
"end_time": "2021-10-22 16:04:01.209514132 +0000 UTC",
"status_code": "STATUS_CODE_OK",
"status_message": "",
"attributes": {
"net.transport": "IP.TCP",
"net.peer.ip": "172.17.0.1",
"net.peer.port": "51820",
"net.host.ip": "10.177.2.152",
"net.host.port": "26040",
"http.method": "GET",
"http.target": "/v1/sys/health",
"http.server_name": "mortar-gateway",
"http.route": "/v1/sys/health",
"http.user_agent": "Consul Health Check",
"http.scheme": "http",
"http.host": "10.177.2.152:26040",
"http.flavor": "1.1"
},
"events": [
{
"name": "",
"message": "OK",
"timestamp": "2021-10-22 16:04:01.209512872 +0000 UTC"
}
]
}
Contexto do Span
Os spans podem ser aninhados, como é indicado pela presença de um ID de span pai: os spans filhos representam suboperações. Isso permite que os spans capturem com mais precisão o trabalho realizado em uma aplicação. O span pai terá um trace_Id idêntico ao seu filho indicando portanto que atende a uma mesma requisição ao sistema.
O contexto do span é um objeto imutável presente em todos os spans que contém as seguintes informações:
- Trace ID: identifica o trace ao qual o span pertence.
- Span ID: identifica o próprio span.
- Trace Flags: uma codificação binária com informações sobre o trace.
- Trace State: uma lista de pares chave-valor que podem conter informações específicas de fornecedores.
Atributos
Os atributos são pares chave-valor usados para adicionar metadados a um span, permitindo capturar informações sobre a operação monitorada.
Por exemplo, se um span acompanha a operação de adicionar um item ao carrinho de compras em um sistema de e-commerce, você pode registrar o ID do usuário, o ID do item e o ID do carrinho.
- Você pode adicionar atributos durante ou após a criação de um span.
- É recomendável adicionar os atributos no momento da criação para que possam ser aproveitados pelo SDK na amostragem. Caso precise adicionar valores posteriormente, atualize o span.
Regras para atributos:
- As chaves devem ser strings não nulas.
- Os valores devem ser uma string, booleano, número (ponto flutuante ou inteiro) ou um array desses tipos.
Além disso, existem Atributos Semânticos, que são convenções padronizadas para nomes de atributos comuns. Utilizar nomes semânticos facilita a padronização de metadados entre sistemas.
Eventos do Span
Um evento do span pode ser visto como uma mensagem de log estruturada (ou anotação) associada a um span. Geralmente é usado para marcar um ponto singular e significativo no tempo durante a duração de um span.
Exemplo prático em um navegador web:
- Carregamento da página → O span é mais apropriado, pois acompanha uma operação com início e fim.
- Página se torna interativa → Um evento do span é mais adequado, pois representa um ponto específico no tempo.
Quando usar eventos do span versus atributos?
Se o timestamp específico for relevante, use um evento do span. Caso contrário, use atributos.
Links do Span
Os links permitem associar um span a um ou mais spans, indicando uma relação causal.
Por exemplo, em um sistema distribuído, algumas operações são rastreadas por um trace. Em resposta a essas operações, uma nova tarefa pode ser enfileirada para execução assíncrona. Para associar o trace da operação subsequente ao trace original, criamos um link de span.
Links são opcionais, mas são uma maneira eficaz de relacionar spans de diferentes traces.
Status do Span
Cada span possui um status com três valores possíveis:
- Unset: (padrão) indica que a operação foi concluída sem erros.
- Error: indica que ocorreu algum erro, como um HTTP 500 em um servidor.
- Ok: explicitamente definido pelo desenvolvedor para marcar o span como bem-sucedido.
Nota: Quando não houver um status Ok para spans que concluíram sem erros, considera-se um evento bem sucedido, pois o valor padrão Unset já cobre esse caso. Ok é usado para deixar explícito que um span é considerado bem-sucedido, sem ambiguidades.
Tipo do Span (Span Kind)
Ao criar um span, ele pode ser classificado como Client, Server, Internal, Producer ou Consumer. O tipo de span informa ao backend de rastreamento como o trace deve ser montado.
- Client: Representa uma chamada remota síncrona de saída, como uma solicitação HTTP ou consulta ao banco de dados.
- Server: Representa uma chamada remota síncrona recebida, como uma solicitação HTTP ou RPC.
- Internal: Representa operações que não cruzam limites de processo (ex: chamadas de funções locais ou middlewares).
- Producer: Representa a criação de uma tarefa a ser processada de forma assíncrona (ex: inserção em uma fila).
- Consumer: Representa o processamento de uma tarefa criada por um producer. Pode ser iniciado muito tempo após o término do span do producer.
Se o tipo não for especificado, ele é considerado Internal.