# Auto-Instrumentação de uma aplicação Python usando Open Telemetry

## Introdução:

Vamos começar fazendo a instrumentação de uma aplicação em Python que salva os dados em um banco de dados Postgresql. O que esse tutorial vai abordar:

* Vou compartilhar a aplicação em Python utilizada
    
* Criação do Dockerfile
    
* Criação do Docker Compose
    
* Criação do Arquivo de configuração do Otlp Collector
    
* Além de explicar o que cada componente faz pretendo mostrar algumas outras formas de como fazer essa instrumentação.
    
* Uma série de videos que eu recomnedo são destes dois canais no youtube: [https://www.youtube.com/watch?v=NEYVJSp4rKo](https://www.youtube.com/watch?v=NEYVJSp4rKo) e [https://www.youtube.com/watch?v=9mifCIFhtIQ](https://www.youtube.com/watch?v=9mifCIFhtIQ)
    

## Entendendo o Opentelemetry Python **Auto-Instrumentation:**

É um projeto de código aberto que oferece uma abordagem unificada à observabilidade, oferecendo ferramentas e bibliotecas para coletar dados de telemetria, como rastros, métricas e logs. Um de seus principais recursos é a autoinstrumentação, que automatiza o processo de instrumentação de aplicativos para rastreamento distribuído sem a necessidade de alterações manuais no código.

Na documentação oficial do OTLP você consegue acessar outras linguagens que suportam este tipo de instrumentação: [https://opentelemetry.io/docs/zero-code/](https://opentelemetry.io/docs/zero-code/)

## Passo 1:

Nessa etapa, antes de transformar nossa aplicação em um container e salvar os dados em um banco de dados Postgresql, resolvi dar um passo atrás pra mostrar como podemos fazer a instrumentação e visualização dos dados usando apenas comandos manuais e vamos progredindo a partir disso.

Então nós temos uma aplicação super simples em Python usando Flask:

```python
import logging
from flask import Flask, request, jsonify
from datetime import datetime
import sys

app = Flask(__name__) 

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('app.log'),
        logging.StreamHandler(sys.stdout)
    ]
)

# Create logger
logger = logging.getLogger(__name__)

# Request logging middleware
@app.before_request
def log_request_info():
    logger.info(f"Request: {request.method} {request.url} from {request.remote_addr}")
    if request.method == 'POST':
        logger.info(f"Request data: {request.get_data()}")

@app.after_request
def log_response_info(response):
    logger.info(f"Response: {response.status_code} for {request.method} {request.path}")
    return response

# Error handler
@app.errorhandler(404)
def not_found(error):
    logger.warning(f"404 Error: {request.method} {request.url} - {error}")
    return jsonify({"error": "Endpoint not found"}), 404

@app.errorhandler(500)
def internal_error(error):
    logger.error(f"500 Error: {request.method} {request.url} - {error}")
    return jsonify({"error": "Internal server error"}), 500

# Define a simple route
@app.route('/')
def hello():
    logger.info("Hello endpoint accessed")
    return "Hello, World!"

# GET request - retrieve user info
@app.route('/user/<name>')
def get_user(name):
    logger.info(f"User endpoint accessed for: {name}")
    try:
        if len(name) < 2:
            raise ValueError("Name too short")
        return f"Hello, {name}! This is a GET request."
    except ValueError as e:
        logger.error(f"Validation error in get_user: {e}")
        return jsonify({"error": "Name must be at least 2 characters"}), 400

# POST request - create/submit data
@app.route('/submit', methods=['POST'])
def submit_data():
    logger.info("Submit endpoint accessed")
    try:
        data = request.get_json()
        if not data:
            logger.warning("No JSON data received in submit")
            return jsonify({"status": "error", "message": "No JSON data received"}), 400
        
        if 'message' not in data:
            logger.warning("Missing 'message' field in submit data")
            return jsonify({"status": "error", "message": "Missing 'message' field"}), 400
        
        logger.info(f"Successfully processed message: {data['message']}")
        return jsonify({
            "status": "success",
            "received_message": data['message'],
            "response": "Data received successfully!",
            "timestamp": datetime.now().isoformat()
        })
    except Exception as e:
        logger.error(f"Unexpected error in submit_data: {e}")
        return jsonify({"status": "error", "message": "Internal server error"}), 500

# Health check endpoint
@app.route('/health')
def health_check():
    logger.info("Health check accessed")
    return jsonify({
        "status": "healthy",
        "timestamp": datetime.now().isoformat(),
        "version": "1.0.0"
    })

# Run the application
if __name__ == '__main__':
    logger.info("Starting Flask application...")
    app.run(debug=True, host='0.0.0.0', port=5000)
```

A primeira coisa que devemos fazer é criar nosso ambiente virtual:

```python
python -m venv venv
source venv/bin/activate
```

Agora vamos usar uma ferramente que se chama **otel-tui** para instalar essa ferramenta é necessário que você tenha go instalado na sua vm.

O **otel-tui** é uma ferramenta que vai receber os seus dados instrumtnados da sua aplicação,ou seja, ele vai se comportar como otlp collector e backend ao mesmo tempo. Essa ferramenta é muito boa para casos de troubleshooting ou visualização incial dos dados, se eles estão se comportando como você gostaria, etc.

Se quiser saber mais sobre aqui está o link do repositório: [https://github.com/ymtdzzz/otel-tui](https://github.com/ymtdzzz/otel-tui)

Para instalar a ferramenta basta:

```go
go install github.com/ymtdzzz/otel-tui@latest
```

E para ver a ferramenta em execução faça:

```go
otel-tui
```

Vai mostrar algo assim, veja que se você tiver uma aplicação instrumentada, já é possivel ver os Traces, Metricas, Logs, Topologia, etc.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759148072278/24a651f5-9902-4f55-ae4a-4cba214fb0f5.png align="center")

Voltando ao que nos interessa, vamos definir as variáveis:  
OTEL\_SERVICE\_NAME: nome do serviço

OTEL\_EXPORTER\_OTLP\_ENDPOINT: pra onde seus dados devem ir, no nosso caso os dados vão ser enviados para o otel-tui.

OTEL\_TRACES\_EXPORTER, OTEL\_METRICS\_EXPORTER e OTEL\_LOGS\_EXPORTER: vamos definir como console e otlp, porque ativando o console conseguimos ver o que nossa aplicação instrumentada está fazendo, se realmente os dados estão sendo enviados para o destino esperado e se metricas, traces, logs estão sendo coletados.

```python
export OTEL_SERVICE_NAME='flask-python'
export OTEL_EXPORTER_OTLP_ENDPOINT='localhost:4317'
export OTEL_EXPORTER_OTLP_INSECURE='true'
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED='true'
export OTEL_TRACES_EXPORTER='console,otlp'
export OTEL_METRICS_EXPORTER='console,otlp'
export OTEL_LOGS_EXPORTER='console,otlp'
```

vamos instalar os binários necessários para nossa aplicação executar:

```python
pip install flask fastapi opentelemetry-distro opentelemetry-exporter-otlp
```

Configure o OTLP:

```python
opentelemetry-bootstrap -a install
```

Agora vamos executar nossa aplicação instrumentada, primeiro em um outro cmd deixe o otel-tui executando porque ele quem vai receber os dados:

```python
otel-tui
```

Execute a aplicação:

```python
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true opentelemetry-instrument --traces_exporter otlp,console --metrics_exporter otlp,console --logs_exporter otlp,console --service_name flask-python flask run -p 5000
```

Você vai ver algo como:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759149626274/743595aa-7734-4b12-b9d4-8efce8f4522a.png align="center")

Alguns testes que você pode fazer é:

```bash
curl http://localhost:5000/
curl http://localhost:5000/user/John
curl -X POST http://localhost:5000/submit \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello from curl!"}'
curl http://localhost:5000/health
curl -X POST http://localhost:5000/submit \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Complex request",
    "user": "admin",
    "priority": "high"
  }'
curl http://localhost:5000/09
curl http://localhost:5000/user/João
```

E no seu otel-tui você já vai começar a receber dados de traces e métricas:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759149750679/7de1cb9b-11f3-4dfd-b70e-975e2e890b06.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759149762826/b3104d28-fc97-4ef5-8266-111c9782d815.png align="center")

Agora vamos dar um próximo passo no nosso ambiente, ao invés de usarmos o **otel-tui** para receber os dados, vamos fazer uma configuração usando docker-compose, nós ainda vamos fazer a instrumentação do código na mão. Porém, o destino agora será um container que vai usar uma imagem **grafana/otel-lgtm:latest** essa imagem já tem o otlp collector embutido, além disso, a imagem possui alguns serviços web como o prometheus, grafana, tempo, etc, que nos possibilita visualizar os dados que estão chegando. Docker-compose:

```yaml
services:
    otel-lgtm:
    image: grafana/otel-lgtm
    container_name: otel-lgtm
    ports:
      - "4317:4317" # OTLP gRPC receiver
      # - "4318:4318" # OTLP HTTP receiver
      - "3000:3000" # Grafana UI
    # networks:
    #   - app-network
    restart: unless-stopped
```

Pode parar a execução do otel-tui e basta executar:

```yaml
docker compose up -d
```

Para executar a aplicação vamos fazer o mesmo comando:

```bash
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true opentelemetry-instrument --traces_exporter otlp,console --metrics_exporter otlp,console --logs_exporter otlp,console --service_name flask-python flask run -p 5000
```

Agora você pode ir Explorer no Grafana escolher o que você quer visualizar, usando Tempo, Prometheus ou até mesmo Loki:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759154143732/69791a43-c4a8-409d-ab68-6dc1c46d1775.png align="left")

## Passo 2:

Já definimos nossa aplicação, fizemos a istrumentação e já estamos usando uma ferramenta que está recebendo nossos dados. Agora vamos dar mais uma incrementada na nossa stack, vamos melhorar nossa aplicação, agora vamos ajustar a aplicação para ela começar a salvar dados em um DB e depois veremos se a instrumentação está coletando este dado.

Você pode baixar a aplicação e os arquivos do projeto: [https://github.com/joaochiroli/otel-instrumentacao-python](https://github.com/joaochiroli/otel-instrumentacao-python). Criei uma nova branch pra essa etapa do projeto **application-with-db**.

OBS: quando estiver instrumentando sua aplicação não deixe o Debug enable, isso pode atrapalhar o otlp na hora de coletar os dados.

Vamos alterar e colocar as dependências no arquivo **requirements.txt** para:

```plaintext
flask 
fastapi 
opentelemetry-distro 
opentelemetry-exporter-otlp
Flask-SQLAlchemy
psycopg2-binary
python-dotenv
opentelemetry-instrumentation-flask
opentelemetry-instrumentation-sqlalchemy
opentelemetry-instrumentation-requests
opentelemetry-instrumentation-wsgi
flask_sqlalchemy
```

OBS: talvez eu tenha colocado dependencias demais, acredito que voces possam testar com menos.

Vamos fazer um ajuste no **Dockerfile**:

```yaml
FROM python:3.9-slim

# Defina o diretório de trabalho
WORKDIR /app

# Copie os arquivos necessários para o diretório de trabalho
COPY . /app

# Instale as dependências
RUN pip install -r requirements.txt

# Configura o OTLP
RUN opentelemetry-bootstrap -a install

# Exponha a porta que a aplicação vai rodar
EXPOSE 5000

# Comando para rodar a aplicação
CMD ["opentelemetry-instrument", "python", "app.py"]
```

Vamos fazer um update no nosso docker-compose, iremos colocar configurações do Postgresql e colocar as variáveis necessárias para nossa aplicação enviar os traces, métricas e logs pro no backend(grafana-lgtm):

```yaml
services:
  db:
    image: postgres:14.15
    container_name: postgres-db
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: simple_app
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck: 
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 10
    networks:
      - app-network

  otel-lgtm:
    image: grafana/otel-lgtm
    container_name: otel-lgtm
    ports:
      - "4317:4317" # OTLP gRPC receiver
      # - "4318:4318" # OTLP HTTP receiver
      - "3000:3000" # Grafana UI
    networks:
      - app-network
    restart: unless-stopped
  app:
    build: .
    container_name: flask-app
    volumes:
      - .:/app
    ports:
      - "5000:5000"
    environment:
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      POSTGRES_DB: simple_app
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      FLASK_ENV: development
      FLASK_DEBUG: "true"
      OTEL_SERVICE_NAME: flask-api
      OTEL_EXPORTER_OTLP_ENDPOINT: otel-lgtm:4317
      OTEL_EXPORTER_OTLP_PROTOCOL: grpc
      OTEL_EXPORTER_OTLP_INSECURE: "true"
      OTEL_TRACES_EXPORTER: console,otlp
      OTEL_METRICS_EXPORTER: console,otlp
      OTEL_LOGS_EXPORTER: console,otlp
      OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED: "true"
      # OTEL_LOG_LEVEL: DEBUG
      OTEL_PYTHON_LOG_CORRELATION: "true"
      OTEL_PYTHON_FLASK_ENABLED: "true"
      OTEL_PYTHON_SQLALCHEMY_ENABLED: "true"
      OTEL_PYTHON_REQUESTS_ENABLED: "true"
      OTEL_TRACES_SAMPLER: always_on
      OTEL_METRICS_SAMPLER: always_on
      # OTEL_TRACES_SAMPLER: "always_on"
      # OTEL_TRACES_SAMPLER_ARG: "1.0"
      # OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: ""
      # OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: ".*"
      # OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: ".*"
    networks:
      - app-network
    depends_on:
      db:
        condition: service_healthy
volumes:
  postgres_data:

networks:
  app-network:
    driver: bridge
```

Depois basta executar: **docker compose up -d —build**

Comandos que você pode usar pra testar a aplicação:

```yaml
curl http://localhost:5000/
curl http://localhost:5000/user/joao
curl -X POST http://localhost:5000/submit -H "Content-Type: application/json" -d '{"message":"test"}'
```

Depois disso, abrindo a página web do Grafana → Explore → Tempo → Query type: Search

E você vai ver os Traces e spans dos eventos que foram gerados através do curl, vai conseguir verificar os dados que foram coletados, o que foi feito, quanto tempo demorou pra cada evento acontecer, entre outras coisas.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759240395567/69472e46-ecde-4d98-836e-69a0da744086.png align="center")

Se você for até o Prometheus vai conseguer verificar os dados das requisições HTTP:

* GET
    
* POST
    
* PUT
    
* DELETE
    
* SELECT - do banco de dados
    

E você pode criar filtros de status code: 200, 404, 500, etc. Depende de quais status sua aplicação retorna.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759240911316/fa0f8c72-7cbc-4163-a4af-24949f0e3d43.png align="center")

## Passo 3:

Agora já temos um projeto com uma stack a mais que seria a parte de salvar os dados em um banco. Para incremetar um pouco mais vamos retirar o Grafana LGTM e iremos substituir por um OTLP Collector, além disso, vamos subir dois backends um Jaeger e um Prometheus que serão responsáveis por recebr estes dados.

Nesse caso vamos fazer alterações apenas no **docker-compose.** Você pode ter acesso ao código através do repositório do projeto [https://github.com/joaochiroli/otel-instrumentacao-python](https://github.com/joaochiroli/otel-instrumentacao-python) e foi criado a branch **otlp-collector** para essa parte do artigo.

Primeira coisa que devemos fazer é alterar a variável de ambiente da nossa aplicação instrumentada, antes os dados estavam indo para o Grafana LGTM e agora vamos enviar para o Otlp Collector:

```yaml
  app:
    build: .
    container_name: flask-app
    volumes:
      - .:/app
    ports:
      - "5000:5000"
    environment:
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      POSTGRES_DB: simple_app
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      FLASK_ENV: development
      FLASK_DEBUG: "true"
      OTEL_SERVICE_NAME: flask-api
      OTEL_EXPORTER_OTLP_ENDPOINT: otel-collector:4317 ### ALTERADO
      OTEL_EXPORTER_OTLP_PROTOCOL: grpc
      OTEL_EXPORTER_OTLP_INSECURE: "true"
      OTEL_TRACES_EXPORTER: console,otlp
      OTEL_METRICS_EXPORTER: console,otlp
      OTEL_LOGS_EXPORTER: console,otlp
      OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED: "true"
      OTEL_PYTHON_LOG_CORRELATION: "true"
      OTEL_PYTHON_FLASK_ENABLED: "true"
      OTEL_PYTHON_SQLALCHEMY_ENABLED: "true"
      OTEL_PYTHON_REQUESTS_ENABLED: "true"
      OTEL_TRACES_SAMPLER: always_on
      OTEL_METRICS_SAMPLER: always_on
      # OTEL_LOG_LEVEL: DEBUG
      # OTEL_TRACES_SAMPLER: "always_on"
      # OTEL_TRACES_SAMPLER_ARG: "1.0"
      # OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: ""
      # OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST: ".*"
      # OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE: ".*"
    networks:
      - app-network
    depends_on:
      db:
        condition: service_healthy
```

Agora vamos criar o nosso serviço do OTLP Collector:

```yaml
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    container_name: otel-collector
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317" # OTLP gRPC receiver
      - "4318:4318" # OTLP HTTP receiver
      - "8888:8888" # Prometheus metrics
      - "8889:8889" # Prometheus exporter metrics
    networks:
      - app-network
    restart: unless-stopped
```

Nós iremos usar a imagem **otel/opentelemetry-collector-contrib:latest**. Estamos usando o collector-contrib porque é uma imagem completa que já possui todas as dependências e extensões, como vamos usar diferentes destinos de backend essa imagem vai nos ajudar. Mas esta é uma imagem para ambientes de desenvolvimento, por conta do seu tamanho e quantidade muito grande de extensões que na maioria das vezes não serão usados. [https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension)

para ambientes de produção é recomendado que você faça a criação da sua própria imagem do collector usando uma imagem do collector oficial, sem dependências e bibliotecas. O otel-collector-contrib é baseado no otel-collector oficial.

Arquivo de configuração do otlp collector:

```yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 10s
    send_batch_size: 1024

exporters:
  debug:
    verbosity: detailed
  otlp:
    endpoint: jaeger:4317
    tls:
      insecure: true
  # prometheus:
  #   endpoint: 0.0.0.0:8889
  #   namespace: flask_app

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, otlp]
    # metrics:
    #   receivers: [otlp]
    #   processors: [batch]
    #   exporters: [debug, prometheus]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]
```

**Receivers** (Receptores) - Recebem dados de telemetria das aplicações. São o "ponto de entrada" do coletor.

* `otlp`: Protocolo padrão do OpenTelemetry
    
    * **gRPC** (porta 4317): Protocolo binário, mais performático
        
    * **HTTP** (porta 4318): Protocolo baseado em REST, mais compatível
        

---

**Processors** (Processadores) - Transformam, filtram ou enriquecem os dados antes de exportá-los.

* `batch`: Agrupa dados em lotes para melhorar performance
    
    * `timeout: 10s`: Envia a cada 10 segundos
        
    * `send_batch_size: 1024`: Envia quando acumular 1024 itens
        

---

**Exporters** (Exportadores) - Enviam os dados processados para destinos finais (backends).

* `debug`: Exibe logs detalhados no console (útil para troubleshooting)
    
* `otlp`: Envia para o Jaeger (sistema de tracing) via gRPC sem TLS
    
* `prometheus` (comentado): Exportaria métricas no formato Prometheus
    

---

**Service** (Serviço) - Define os **pipelines** - fluxo de dados do receiver → processor → exporter.

* `traces`: Pipeline para rastreamento distribuído (spans)
    
* `metrics` (comentado): Pipeline para métricas
    
* `logs`: Pipeline para logs estruturados
    

---

Configuração do Jaeger:

```yaml
jaeger:
    image: jaegertracing/all-in-one:latest
    container_name: jaeger
    environment:
      - COLLECTOR_OTLP_ENABLED=true
      - LOG_LEVEL=debug
    ports:
      - "16686:16686" # Jaeger UI
      # - "4317:4317" # OTLP gRPC receiver
      # - "4318:4318" # OTLP HTTP receiver
      - "14250:14250" # Jaeger gRPC
      - "14268:14268" # Jaeger HTTP
    networks:
      - app-network
```

Agora você pode visualizar os traces:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759260219308/42621eee-ca3b-44fe-abdd-423faa80f4bd.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759260253081/69e8ef15-9b88-4842-a014-02d32b607ed3.png align="center")

E pra finalizar vamos adicionar a configuração do Prometheus

Vamos começar descomentando as linhas do arquivo otel-collector-config.yaml:

```yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 10s
    send_batch_size: 1024

exporters:
  debug:
    verbosity: detailed
  otlp:
    endpoint: jaeger:4317
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889
    namespace: flask_app

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, otlp]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, prometheus]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug]
```

Vamos ajustar o arquivo de configuração do docker-compose:

```yaml
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
      - "--web.console.libraries=/usr/share/prometheus/console_libraries"
      - "--web.console.templates=/usr/share/prometheus/consoles"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - app-network
    depends_on:
      - otel-collector
    restart: unless-stopped

volumes:
  postgres_data:
  prometheus-data:
```

Precisamos criar um arquivo prometheus.yml, ele quem vai até o collector na porta 8889 para coletar as métricas:

```yaml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  # Scrape metrics do OpenTelemetry Collector
  - job_name: 'otel-collector'
    static_configs:
      - targets: ['otel-collector:8889']
        labels:
          service: 'flask-api'

  # Scrape metrics do próprio Prometheus
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
```

Para testar se essa parte de métricas está funcionando existem algumas maneiras, uma delas é indo até a página web do Prometheus → Target health e verificando se o Prometheus está acessando o Collector na porta correta

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759261392926/1333eed7-5d65-4a26-9d66-799150deee9c.png align="center")

Para ver as métricas você pode acessar:

[http://localhost:8889/metrics](http://localhost:8889/metrics)

E também é possivel fazer uma busca no Prometheus pelo nome do nosso serviço, assim vai me trazer todas as métricas deste serviço, usei o comando a seguir: {\_\_name\_\_=~"flask\_app.\*"}. Você pode usar diferentes filtros, o que vai depender é quais métricas você está querendo acessar e quais são os componentes.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759261498044/b9162ab1-e44b-4079-b04e-cfb1153bb59c.png align="center")

O OTLP é tão completo que ele já me traz informações a respeito do meu outro componente o DB:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1759261610330/ff603645-5233-4580-b244-3132a7f16c8a.png align="center")

## Conclusão:

Neste artigo foi feito:

* A criação de uma aplicação Flask e sua auto-instrumentação inicial, integrada com o otel-tui e depois com o Grafana LGTM.
    
* Depois progredimos e melhoramos a aplicação, criamos um DB para salvar os dados e fizemos a auto-instrumentação de todos os componentes que foram integrados ao Grafana LGTM.
    
* Por último e não menos importante fizemos a criação da stack completa com a aplicação e seus componentes auto instrumentados, enviando dados para o OTLP Collector e os dados foram enviados para o Jaeger e para o Prometheus.
