Share
William Sena
16 16 min read

Como o OpenTelemetry pode salvar seu sistema em produção

Este artigo é pra você que quer entender a fundo aquela aplicação que misteriosamente começa a apresentar problemas... geralmente no fim da tarde de uma sexta-feira. Acertei? 😅

Pegue seu café e bora mergulhar no artigo!
Porque entender o que acontece com sua aplicação antes do caos da sexta à tarde é sempre uma boa ideia. 😃

Guia de bordo

🧱 Os 3 Pilares da Observabilidade

A observabilidade moderna se apoia em três pilares principais:

  • 📊 Métricas: mostram o estado da aplicação ao longo do tempo (como uso de CPU, requisições por segundo, latência etc.).
  • 🧻 Logs: registram eventos e mensagens que ajudam a entender o que aconteceu em determinado momento.
  • 🔍 Traces: acompanham o caminho de uma requisição de ponta a ponta, revelando gargalos e dependências entre serviços.

Juntos, esses três pilares ajudam a responder uma das perguntas mais importantes no dia a dia de quem opera sistemas: “Por que meu sistema está se comportando desse jeito?”

E é exatamente aí que o OpenTelemetry entra em cena — fornecendo as ferramentas necessárias para coletar, padronizar e correlacionar métricas, logs e traces, tudo em um único ecossistema.

O que é OpenTelemetry?

É uma iniciativa de código aberto que tem como objetivo estabelecer um padrão unificado para a coleta, o processamento e a exportação de dados de telemetria. Trata-se de uma solução completa para monitorar o desempenho, o comportamento e a saúde de aplicações e sistemas, oferecendo visibilidade total sobre o que está acontecendo em seu ambiente.

Cloud Native Computing Foundation (CNCF)

Se você já se perdeu no meio de logs, métricas e traces tentando entender o que tá pegando na sua aplicação, respira fundo — você não está sozinho. A boa notícia é que a galera da comunidade open source também sentia essa dor… e decidiu fazer algo a respeito.

Foi daí que nasceu o OpenTelemetry, uma ferramenta que veio ao mundo como resultado da união de dois projetos que já estavam mandando bem: OpenTracing e OpenCensus. Em vez de continuar com iniciativas separadas, a comunidade juntou forças 🔥 pra criar um padrão único e poderoso de observabilidade.

Essa fusão virou realidade e, em outubro de 2022, rolou o lançamento da primeira versão estável do OpenTelemetry. Desde então, o projeto só cresceu e virou queridinho de quem trabalha com sistemas distribuídos, microsserviços e Kubernetes.

E sabe o que é mais legal? O projeto é mantido pela Cloud Native Computing Foundation (CNCF) — a mesma galera por trás do Kubernetes, Prometheus, Envoy e outros pesos pesados do mundo cloud native.

Ou seja, se você quer observabilidade de verdade no seu sistema, sem gambiarra e com suporte da comunidade, o OpenTelemetry é o caminho.

Benefícios do OpenTelemetry

🔍 Visibilidade Aprimorada

Com o OpenTelemetry, você ganha olhos de águia sobre seus sistemas. É possível monitorar desempenho, comportamento e saúde em tempo real, o que significa que nada passa despercebido — de latências inesperadas a gargalos ocultos.

🛠️ Diagnóstico e Resolução de Problemas Sem Dor de Cabeça

Ao oferecer uma telemetria completa, padronizada e consistente, o OpenTelemetry facilita (e muito!) a identificação da raiz dos problemas. Isso se traduz em respostas mais rápidas, menos tempo de inatividade e menos estresse na madrugada.

📊 Decisões Guiadas por Dados Reais

Nada de achismos. Com dados confiáveis em mãos, fica mais fácil tomar decisões estratégicas: otimizar o desempenho da aplicação, identificar padrões de uso e até direcionar recursos com mais inteligência.

🔄 Interoperabilidade e Flexibilidade de Verdade

OpenTelemetry não te prende a nenhuma ferramenta específica. Ele é agnóstico de fornecedor, permitindo integração com diversos backends populares como Prometheus, Jaeger, Zipkin, Datadog, entre outros. Isso dá liberdade pra montar o stack de observabilidade que fizer mais sentido pro seu time

Principais recursos do OpenTelemetry

O OpenTelemetry oferece bibliotecas (SDKs) prontas para você instrumentar suas aplicações de forma simples e eficiente. Com elas, é possível coletar dados de telemetria como métricas, logs e traces, tudo de maneira padronizada.

⚒ Instrumentação

Esses SDKs são compatíveis com as principais linguagens de programação do mercado, incluindo C++, C#, Elixir, Go, Java, Lua, Ruby, Rust, Python e JavaScript, permitindo que você adote observabilidade de ponta a ponta, independentemente da stack que estiver usando.

🗑 Coleta e Processamento Inteligente

O OpenTelemetry conta com um coletor centralizado (o famoso OpenTelemetry Collector) que funciona como um hub de dados de telemetria. Ele recebe informações vindas de diversas fontes — como aplicações, agentes e sidecars — e faz o trabalho pesado: agrega, filtra, transforma e roteia esses dados antes de enviá-los para a ferramenta de análise ou observabilidade da sua escolha.

Com isso, você ganha mais controle, eficiência e flexibilidade, além de aliviar o trabalho das suas aplicações, que não precisam se preocupar com a exportação direta dos dados.

🚚 Exportação para Onde Você Quiser

Com o OpenTelemetry, seus dados não ficam presos em um lugar só. Ele suporta diversos backends de telemetria, permitindo que você exporte métricas, logs e traces para as plataformas de observabilidade e análise que já usa — como Prometheus, Jaeger, Zipkin, Datadog, New Relic, entre outros.

Essa flexibilidade garante que você possa escolher a melhor ferramenta para cada necessidade, sem abrir mão da padronização e consistência na coleta dos dados.

Plataformas de Observabilidade

🧰 Componentes Essenciais do OpenTelemetry

🎯 Instrumentação

As bibliotecas de instrumentação são integradas diretamente às suas aplicações, permitindo a coleta automática (ou manual) de métricas, logs e traces. É com elas que você começa a dar visibilidade ao que acontece "debaixo do capô" dos seus serviços.

🗃️ Coletores (Collectors)

O OpenTelemetry Collector atua como uma central de inteligência: recebe dados de telemetria de múltiplas fontes, processa, transforma e filtra as informações antes de enviá-las aos destinos configurados. Ele alivia o trabalho das aplicações e garante mais controle sobre o fluxo dos dados.

📦 Exportadores (Exporters)

Os exportadores são responsáveis por enviar os dados processados para os backends de observabilidade. O OpenTelemetry já traz suporte a várias ferramentas populares, como Prometheus, Jaeger, Zipkin, Datadog, New Relic, entre outras.

📊 Backends de Telemetria

São as plataformas onde os dados ganham vida: eles armazenam, processam e visualizam as métricas, logs e traces coletados. É aqui que você consegue entender o desempenho, o comportamento e a saúde da sua aplicação com dashboards, alertas e análises avançadas.

🛡️ E os dados sensíveis? Relaxa, dá pra proteger!

Quando estamos lidando com dados sensíveis, como informações de cartão de crédito, CPF/CNPJ, e-mails ou qualquer outro dado pessoal, é essencial garantir que nada disso vaze nos seus traces.

Com o OpenTelemetry, é possível remover ou mascarar esses dados antes mesmo que eles sejam exportados, usando um componente chamado OTel Processor. Esse processador permite filtrar, anonimizar ou transformar campos específicos no pipeline de telemetria, garantindo conformidade com LGPD, GDPR e outras regulamentações de privacidade.

Ou seja, você continua com visibilidade total do sistema, mas sem comprometer a segurança e a privacidade dos seus usuários.

O que o OpenTelemetry coleta?

O OpenTelemetry (OTel) funciona como um verdadeiro detetive digital: ele coleta e exporta tudo o que você precisa saber sobre sua aplicação — sem complicar.

Como mencionamos anteriormente, ele se apoia nos três pilares da observabilidade:
📊 Métricas, 🧻 Logs e 🔍 Traces.

Suporte as linguagens

Linguagem Suporte OpenTelemetry Estágio
Java ✅ Sim Estável
JavaScript (Node.js/Browser) ✅ Sim Estável
Python ✅ Sim Estável
Go ✅ Sim Estável
.NET (C#) ✅ Sim Estável
Ruby ✅ Sim Estável
C++ ✅ Sim Em evolução
PHP ⚠️ Parcial Em desenvolvimento
Rust ⚠️ Parcial Comunidade ativa
Swift ⚠️ Parcial Projeto comunitário
Erlang/Elixir ⚠️ Parcial Projeto comunitário
🔎 Observação: As linguagens com suporte “parcial” ou “em desenvolvimento” ainda estão evoluindo ou são mantidas pela comunidade.

Entregando observabilidade

Diagrama do fluxo OpenTelemetry

No diagrama acima, visualizamos as principais camadas de uma arquitetura de observabilidade. Iniciando pela camada das aplicações, que são as responsáveis por gerar os insumos fundamentais: logs, traces e métricas. Em seguida, entra o OpenTelemetry Collector, que atua como intermediário — ele recebe, processa e transforma esses dados, encaminhando-os para um ou mais exporters, que se encarregam de armazená-los ou redirecioná-los para ferramentas especializadas.

Por fim, temos a camada de apresentação, composta por interfaces de visualização (UIs), onde é possível construir dashboards, configurar alertas e executar queries sobre os dados coletados. Neste exemplo, usamos apenas duas ferramentas, mas a arquitetura é flexível e compatível com diversas soluções comerciais amplamente adotadas no mercado, como Datadog, New Relic, Kibana, entre outras.

Se o objetivo for algo mais simples, especialmente em ambientes de desenvolvimento, também é possível enviar os dados diretamente para o Jaeger, que oferece uma visualização prática e eficiente dos três pilares da observabilidade: métricas, logs e traces.

Chega de Teoria, Vamos Codar!

Há algum tempo, iniciei algumas POCs com o objetivo de descomplicar a instrumentação com OpenTelemetry em aplicações escritas nas diversas linguagens que compunham a stack com a qual eu estava trabalhando.

Durante esse processo, acabei mudando de empresa — mas continuei esse estudo, evoluindo a ideia e consolidando o aprendizado em uma apresentação resumida, que agora serve de base para este artigo.

Como mencionei antes, estamos lidando com várias linguagens, então a proposta aqui é mostrar rapidamente trechos de implementação, só pra dar o gostinho. E claro: o repositório com todos os exemplos está disponível — você pode clonar, testar e entender na prática como a instrumentação com OpenTelemetry funciona em cada caso:

GitHub - williampsena/otel-recipes: This repository has a proof-of-concept that uses the Opentelemetry processor to prevent revealing sensitive data.
This repository has a proof-of-concept that uses the Opentelemetry processor to prevent revealing sensitive data. - williampsena/otel-recipes

Configurando o OpenTelemetry

A configuração do colector (OTel)

  • otel-collector-config.yaml
# OpenTelemetry Collector config that receives OTLP
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: "0.0.0.0:4317"
      http:
        endpoint: "0.0.0.0:4318"

processors:
  batch:
    send_batch_size: 1024
    timeout: 5s

  attributes/scrape:
    actions:
      - key: environment
        value: production
        action: insert

      - key: process.command_line
        action: delete
      - pattern: credit_card
        action: delete
      - pattern: password
        action: delete

      - pattern: email
        action: hash
      - pattern: vatnumber
        action: hash
      - pattern: document
        action: hash
      - pattern: x_secret_key
        action: hash

exporters:
  debug:
      verbosity: detailed

  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true

  loki:
    endpoint: http://loki:3100/loki/api/v1/push

  prometheus:
    endpoint: 0.0.0.0:18888

  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true

extensions:
  zpages: {}

service:
  extensions: [zpages]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [attributes/scrape, batch]
      exporters: [otlp/jaeger, otlp/tempo]
    metrics:
      receivers: [otlp]
      processors: [attributes/scrape, batch]
      exporters: [prometheus]
    logs:
      receivers: [otlp]
      processors: [attributes/scrape, batch]
      exporters: [loki]

Nesta configuração, o OpenTelemetry Collector não se limita apenas à coleta de dados — também configuramos etapas de processamento para demonstrar todo o potencial da ferramenta. O objetivo é mostrar como o coletor pode transformar dados em tempo real, seja inserindo novos campos, removendo informações desnecessárias ou mascarando dados sensíveis para evitar exposições indesejadas.

Abaixo, explicamos os principais blocos da configuração:

  • receivers: definem os endpoints de entrada por onde o coletor recebe os dados. É aqui que especificamos quais protocolos ou fontes o OpenTelemetry Collector deve escutar.
  • processors : responsáveis por transformar os dados recebidos. Podemos, por exemplo, remover informações sensíveis, adicionar marcações personalizadas (como tags de ambiente ou origem), ou filtrar dados irrelevantes, garantindo que apenas informações úteis sejam enviadas para os destinos finais.
  • exporters: definem os destinos para onde os dados de observabilidade serão enviados. Entre as opções mais comuns estão o Jaeger, DataDog, Prometheus, entre outros sistemas de monitoramento e rastreamento.
  • service: é aqui que configuramos as pipelines de execução para cada um dos pilares da observabilidade — logs, traces e métricas. Cada pipeline determina quais são os seus respectivos receivers, processors e exporters, orquestrando o fluxo completo dos dados dentro do coletor.

Os containers

  • docker-compose.yml
name: otel

services:  
  otel:
    profiles: [all, otel]
    networks:
      - otel
    ports:
      - 14317:4317
      - 14318:4318
      - 18888:18888
    image: otel/opentelemetry-collector-contrib:0.123.0
    command: ["--config=/conf/otel-collector-config.yaml"]
    privileged: true
    volumes:
      - "./otel/otel-collector-config.yaml:/conf/otel-collector-config.yaml"
    depends_on:
      - loki

  jaeger:
    profiles: [all, otel]
    networks:
      - otel
    image: jaegertracing/all-in-one:1.68.0
    ports:
      - 16687:16686
    depends_on:
      - otel

  prometheus:
    profiles: [all, otel]
    networks:
      - otel
    image: prom/prometheus:v3.2.1
    ports:
      - "9090:9090"
    volumes:
      - prometheus-data:/prometheus
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    profiles: [all, grafana]
    networks:
      - otel
    image: grafana/grafana:11.6.0
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-data:/var/lib/grafana
    depends_on:
      - loki
      - tempo

  loki:
    profiles: [all, grafana]
    networks:
      - otel
    image: grafana/loki:3
    command: [ "-config.file=/etc/loki/local-config.yaml" ]
    ports:
      - "3100:3100"
    volumes:
      - ./grafana/loki-config.yaml:/etc/loki/local-config.yaml
      - loki-rules-data:/etc/loki/rules/fake

  promtail:
    profiles: [all, grafana]
    networks:
      - otel
    image: grafana/promtail:3
    volumes:
      - ./grafana/promtail-config.yaml:/etc/promtail/config.yml
      - /var/log:/var/log
      - /var/lib/docker/containers:/var/lib/docker/containers
    depends_on:
      - loki
      - prometheus

  tempo:
    profiles: [all, grafana]
    networks:
      - otel
    image: grafana/tempo:latest
    command: [ "-config.file=/etc/tempo/tempo.yaml" ]
    ports:
      - "3200:3200"
    volumes:
      - ./grafana/tempo-config.yaml:/etc/tempo/tempo.yaml
      - tempo-data:/var/tempo

  redis:
    profiles: [all, db]
    networks:
      - otel
    image: bitnami/redis:7.2
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
    ports:
      - 6379:6379

  python:
    profiles: [all, apps]
    networks:
      - otel
    build:
      dockerfile: ./app/python/Containerfile
    environment:
      - PORT=8000
      - OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel:4317
      - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel:4317/v1/traces
      - OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://otel:4317/v1/metrics
      - OTEL_RESOURCE_ATTRIBUTES="service.name=python-otlp,team=dev,cluster-name=local,env=dev"
      - OTEL_SERVICE_NAME=python-otlp
    ports:
      - 8000:8000
    depends_on:
      - otel

  go:
    profiles: [all, apps]
    networks:
      - otel
    build:
      dockerfile: ./app/go/Containerfile
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel:4317
      - OTEL_RESOURCE_ATTRIBUTES="service.name=go-otlp,team=dev,cluster-name=local,env=dev"
      - OTEL_SERVICE_NAME=go-otlp
      - REDIS_URL=redis:6379
    ports:
      - 8001:8001
    depends_on:
      - otel

  ruby:
    profiles: [all, apps]
    networks:
      - otel
    build:
      dockerfile: ./app/ruby/Containerfile
    environment:
      - PORT=8002
      - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel:4318/v1/traces
      - OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://otel:4318/v1/metrics
      - OTEL_RESOURCE_ATTRIBUTES="service.name=ruby-otlp,team=dev,cluster-name=local,env=dev"
      - OTEL_SERVICE_NAME=ruby-otlp
      - OTEL_TRACES_EXPORTER=otlp
    ports:
      - 8002:8002
    depends_on:
      - otel

  node:
    profiles: [all, apps]
    networks:
      - otel
    build:
      dockerfile: ./app/node/Containerfile
    environment:
      - PORT=8003
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel:4318
      - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://otel:4318/v1/traces
      - OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://otel:4318/v1/metrics
      - OTEL_RESOURCE_ATTRIBUTES="service.name=node-otlp,team=dev,cluster-name=local,env=dev"
      - OTEL_SERVICE_NAME=node-otlp
      - OTEL_TRACES_EXPORTER=otlp
    ports:
      - 8003:8003
    depends_on:
      - otel

volumes:
  tempo-data:
  loki-rules-data:
  grafana-data:
  prometheus-data:

networks:
  otel:
    name: "otel"
  • Otel: Abreviação de OpenTelemetry Collector, é o componente responsável por receber, processar e exportar dados de observabilidade como logs, métricas e traces.
  • Jaeger: Ferramenta de distributed tracing que permite rastrear o caminho completo de uma requisição em sistemas distribuídos, sendo muito útil em arquiteturas de microserviços para identificar gargalos e monitorar a performance entre serviços.
  • Prometheus: Solução de monitoramento que coleta e armazena métricas em tempo real. Possui suporte a consultas avançadas e permite a criação de alertas e dashboards personalizados.
  • Grafana: Plataforma web de código aberto para visualização e análise de dados. Suporta múltiplas fontes, como Prometheus, Loki e Tempo, e é amplamente utilizada para criação de dashboards interativos.
  • Loki: Sistema de gerenciamento de logs desenvolvido pela Grafana Labs. Ao contrário de outras soluções, como o Elasticsearch, o Loki é otimizado para trabalhar em conjunto com Prometheus e Grafana, utilizando os mesmos rótulos (labels) e focando em logs estruturados e de fácil correlação com métricas e traces.
  • Promtail: Agente responsável por coletar logs de arquivos locais e enviá-los ao Loki, o sistema de gerenciamento de logs da Grafana.
  • Tempo: Plataforma open-source mantida pela Grafana Labs, voltada para o gerenciamento de distributed tracing. Permite a correlação de traces com métricas e logs em um único ambiente visual.
  • Redis: Sistema de cache em memória amplamente utilizado por aplicações. Pensando em testes do contexto de observabilidade, pode gerar spans de traces relacionados a operações de leitura e escrita no cache.
  • Python, Ruby, Go e Node: Exemplos de containers de aplicação que atuam como fontes de dados para o coletor OpenTelemetry, fornecendo logs, métricas e traces que alimentam todo o pipeline de observabilidade.
Os arquivos de configuração do Loki, Tempo e Promtail estão disponíveis no repositório e devem ser baixados antes da execução. Eles garantem que cada serviço funcione corretamente dentro do pipeline de observabilidade.

Instrumentação

No repositório, você encontrará diversos exemplos de instrumentação. Neste artigo, vamos focar brevemente na implementação em Go. Por ser uma linguagem compilada, o Go exige uma abordagem mais explícita e detalhada na instrumentação. Já em linguagens interpretadas como Node.js, Ruby e Python, esse processo tende a ser mais simples, graças aos recursos de metaprogramação que facilitam a inserção automática de telemetria no código.

Go

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"

	"net/http"

	"github.com/brianvoe/gofakeit/v7"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/redis/go-redis/extra/redisotel/v9"
	"github.com/redis/go-redis/v9"
	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	"go.opentelemetry.io/otel/trace"
)

// Email counter metric
var emailCounter = promauto.NewCounter(prometheus.CounterOpts{
	Name: "email_counter",
	Help: "The total number of email sent",
})

// Shutdown handler is responsible for finishing trace.
type ShutdownHandler func(context.Context) error

// Regular email data
type Email struct {
	From    string `json:"from"`
	To      string `json:"to"`
	Subject string `json:"subject"`
	Body    string `json:"body"`
}

// Regular customer
type Customer struct {
	Id       string // the customer unique id
	Name     string // the customer name
	Document string // the document number
	Email    string // the customer email
}

// This function is responsible for setting up the program before it runs
func init() {
	gofakeit.Seed(0)
}

// Build redis client connection
func setupRedis() *redis.Client {
	rdb := redis.NewClient(&redis.Options{
		Addr:     os.Getenv("REDIS_URL"),
		Password: "",
		DB:       0,
	})

	if err := redisotel.InstrumentTracing(rdb); err != nil {
		panic(err)
	}

	if err := redisotel.InstrumentMetrics(rdb); err != nil {
		panic(err)
	}

	return rdb
}

// Initializes the open telemetry tracer.
func setupTracer(ctx context.Context) (ShutdownHandler, error) {
	exporter, err := otlptracegrpc.New(ctx)
	if err != nil {
		return nil, err
	}

	tp := buildTracer(ctx, exporter)

	otel.SetTracerProvider(tp)

	return tp.Shutdown, nil
}

// Build an open telemetry tracer
func buildTracer(ctx context.Context, exporter *otlptrace.Exporter) *sdktrace.TracerProvider {
	res, err := resource.New(ctx,
		resource.WithAttributes(
			attribute.String("service.name", os.Getenv("OTEL_SERVICE_NAME")),
		),
	)

	if err != nil {
		panic(err)
	}

	return sdktrace.NewTracerProvider(
		sdktrace.WithBatcher(exporter),
		sdktrace.WithResource(res),
	)
}

// Responsible for finalizing trace context.
func doShutdown(ctx context.Context, shutdown ShutdownHandler) {
	func() {
		if err := shutdown(ctx); err != nil {
			log.Fatalf("failed to shut down tracer: %v", err)
		}
	}()

}

// Add span attributes values
func setupSpanValues(span trace.Span) {
	span.SetAttributes(
		attribute.String("customer.id", gofakeit.UUID()),
		attribute.String("customer.email", gofakeit.Email()),
		attribute.String("customer.password", gofakeit.Password(true, true, true, true, true, 10)),
		attribute.String("customer.vatnumber", gofakeit.SSN()),
		attribute.String("customer.credit_card", gofakeit.CreditCard().Number),
		attribute.String("db.user", gofakeit.Username()),
		attribute.String("db.password", gofakeit.Password(true, true, true, true, true, 10)),
		attribute.String("account.email", gofakeit.Email()),
	)
}

// Returns an internal server error
func writeHttpError(span trace.Span, w http.ResponseWriter, errorMessage string) {
	span.AddEvent("error",
		trace.WithAttributes(
			attribute.String("value", errorMessage),
		),
	)
	span.End()

	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte(errorMessage))
}

// Route to generate stats for every request.
func sendEmailRoute(rdb *redis.Client) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		tracer := otel.Tracer("go-tracer")
		_, span := tracer.Start(r.Context(), "send-email")

		message, err := gofakeit.EmailText(&gofakeit.EmailOptions{})

		if err != nil {
			writeHttpError(span, w, fmt.Sprintf("failed to fetch random message: %v", err))
			return
		}

		customer := Customer{
			Id:       gofakeit.UUID(),
			Name:     gofakeit.Name(),
			Email:    gofakeit.Email(),
			Document: gofakeit.SSN(),
		}

		email := Email{
			From:    fmt.Sprintf("no-reply@%v", gofakeit.DomainName()),
			To:      customer.Email,
			Subject: gofakeit.BookTitle(),
			Body:    message,
		}

		span.SetAttributes(
			attribute.String("customer.id", customer.Id),
			attribute.String("customer.email", customer.Email),
			attribute.String("customer.document", customer.Document),
		)

		setupSpanValues(span)

		jsonEmail, err := json.Marshal(email)

		if err != nil {
			writeHttpError(span, w, fmt.Sprintf("failed to parse email message: %v", err))
			return
		}

		err = rdb.SPublish(r.Context(), "email", jsonEmail).Err()

		if err != nil {
			writeHttpError(span, w, fmt.Sprintf("failed to queue email message: %v", err))
			return
		}

		span.AddEvent("email",
			trace.WithAttributes(
				attribute.String("subject", email.Subject),
				attribute.String("content", email.Body),
			),
		)

		emailCounter.Inc()

		response := fmt.Sprintf("📨 The email was queued successfully: %v", email.Subject)

		span.AddEvent("log-message", trace.WithAttributes(attribute.String("message", message)))

		log.Println(message)

		span.End()

		w.WriteHeader(http.StatusCreated)
		w.Write([]byte(response))
	}
}

func main() {
	rdb := setupRedis()
	ctx := context.Background()
	shutdown, err := setupTracer(ctx)

	if err != nil {
		log.Fatalf("failed to initialize open telemetry tracer: %v", err)
	}

	defer doShutdown(ctx, shutdown)

	otelHandler := otelhttp.NewHandler(http.HandlerFunc(sendEmailRoute(rdb)), "SendEmail")

	http.Handle("/metrics", promhttp.Handler())
	http.Handle("/send-email", otelHandler)

	http.ListenAndServe(":8001", nil)
}

Abaixo temos a implementação de uma métrica do tipo contador, responsável por registrar o total de e-mails enviados. Esse tipo de dado pode ser utilizado para gerar gráficos, indicar o funcionamento da aplicação e até mesmo disparar alertas em situações anômalas.

No caso específico da aplicação escrita em Go, optei por utilizar o Prometheus para expor as métricas em vez do próprio OpenTelemetry. Isso porque, durante os testes, apenas a aplicação em Go apresentou dificuldades ao despachar métricas diretamente via OTel.

Felizmente, o Prometheus é totalmente compatível com o OpenTelemetry Collector, o que nos permite integrá-lo de forma transparente ao pipeline de observabilidade.

var emailCounter = promauto.NewCounter(prometheus.CounterOpts{
	Name: "email_counter",
	Help: "The total number of email sent",
})

http.Handle("/metrics", promhttp.Handler())

http.ListenAndServe(":8001", nil)

Aqui estamos enviando por meio do tracer um span (send-email), que representa uma ação ou operação dentro do sistema. Esse rastro pode ser visualizado nas ferramentas de observabilidade para ajudar a entender o comportamento da aplicação em tempo real.

tracer := otel.Tracer("go-tracer")
_, span := tracer.Start(r.Context(), "send-email")

A integração de logs no Go funciona principalmente através de Events adicionados aos spans. No entanto, a integração direta com loggers ainda precisa ser melhor explorada.

Como o ⚠️ SDK do OpenTelemetry para Go ainda está em desenvolvimento ativo, algumas dessas funcionalidades não estão totalmente integradas ou exigem soluções manuais.
span.AddEvent("email",
    trace.WithAttributes(
        attribute.String("subject", email.Subject),
        attribute.String("content", email.Body),
    ),
)

span.AddEvent("error",
    trace.WithAttributes(
        attribute.String("value", errorMessage),
    ),
)

span.End()

Outras instrumentações, como mencionamos anteriormente, são mais simples de configurar. Você pode conferir os detalhes e exemplos completos diretamente no repositório no GitHub.

Como testar?

Preparei um script em Bash (scripts/do-requests.sh) que será responsável por executar múltiplas requisições nas aplicações, com o objetivo de gerar logs, métricas e traces que alimentarão nosso pipeline de observabilidade.

Antes de executar o script, é importante garantir que todos os containers estejam em funcionamento. Para isso, utilize o seguinte comando:

docker compose --profile all up -d
Esse comando irá iniciar todos os serviços definidos no docker-compose.yml em segundo plano. Assim que os containers estiverem ativos, podemos prosseguir com a execução do script de carga para simular o comportamento das aplicações.

Com todos os containers em execução, estamos prontos para gerar os insumos de observabilidade — ou seja, os logs, métricas e traces que serão processados pelo nosso coletor.

Execute o comando abaixo para iniciar o script de carga:

bash scripts/do-requests.sh
Execução do script para requests

Após a execução, já teremos dados disponíveis para visualização direta no Grafana e no Jaeger, que foram definidos como nossos exporters.

Grafana

Grafana UI

O Grafana tem como foco oferecer uma experiência completa em observabilidade. Além de visualizar logs, traces e métricas, ele permite criar dashboards personalizadas e configurar alertas inteligentes, proporcionando uma visão centralizada e em tempo real do comportamento dos sistemas.

No detalhe 🔍

Logs integrados por meio do Grafana Loki
Traces integrados por meio do Grafana Tempo
Visualização das métricas coletadas com Prometheus integrado ao OpenTelemetry. Algumas métricas são expostas diretamente pelo Prometheus, enquanto outras são enriquecidas e encaminhadas pelo OpenTelemetry Collector.

Jaeger

Com o Jaeger, é possível observar traces, métricas e logs de forma minimalista e objetiva. Ele é extremamente útil para identificar gargalos e entender o fluxo de requisições dentro da aplicação, especialmente em ambientes com microsserviços. Além disso, o Jaeger permite importar traces em formato JSON, facilitando análises manuais ou integrações com outras ferramentas.

No detalhe 🔍

Conclusão

Neste artigo, criamos um exemplo prático de integração de aplicações em diferentes linguagens com o OpenTelemetry. Exploramos as principais configurações do repositório e vimos como os dados de observabilidade são exibidos no Jaeger e no Grafana.

Espero que essa introdução ao OpenTelemetry ajude a aumentar a visibilidade dos seus projetos e torne sua vida de desenvolvedor mais tranquila — e com menos emoções indesejadas.

E por hoje é isso!

Que Deus 🕊️ te abençoe, e não se esqueça: mantenha seu kernel 🧠 sempre atualizado!