Por desgracia, puede resultar caro externalizar esta responsabilidad; sin embargo, existen soluciones de código abierto, y una de las mejores es la plataforma Grafana. Grafana es la herramienta de visualización para una variedad de fuentes de datos, pero el equipo de Grafana también tiene sus propias fuentes de datos para registros (Loki), métricas (Mimir) y trazas (Tempo). En este artículo veremos cómo conectar una aplicación Spring Boot a este ecosistema.
Nuestro primer paso es configurar nuestro entorno de observabilidad. Utilizaremos docker compose para crear este entorno con este archivo docker-compose.yaml:
version: "3.9" networks: telemetry: volumes: influxdb-storage: grafana-storage: services: grafana: image: grafana/grafana:9.3.1 depends_on: - influxdb volumes: - ./docker/grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml - grafana-storage:/var/lib/grafana environment: - GF_SECURITY_ADMIN_USER=admin - GF_SECURITY_ADMIN_PASSWORD=admin1 - GF_SERVER_HTTP_PORT=3000 - INFLUXDB_HOST=influxdb - INFLUXDB_PORT=8086 - INFLUXDB_NAME=db0 - INFLUXDB_USER=influxuser - INFLUXDB_PASS=influxuser1 ports: - "3000:3000" networks: - telemetry influxdb: image: influxdb:latest ports: - '8086:8086' volumes: - influxdb-storage:/var/lib/influxdb environment: - INFLUXDB_URL=http://influxdb:8086 - INFLUXDB_ADMIN_USER=influxuser - INFLUXDB_ADMIN_PASSWORD=influxuser1 loki: image: grafana/loki:2.7.1 ports: - "3100:3100" command: -config.file=/etc/loki/loki.yaml volumes: - ./docker/loki.yaml:/etc/loki/loki.yaml networks: - telemetry tempo: image: grafana/tempo:1.5.0 command: [ "-config.file=/etc/tempo.yaml" ] volumes: - ./docker/tempo.yaml:/etc/tempo.yaml - ./data/tempo:/tmp/tempo ports: - "14268:14268" # jaeger ingest - "3200:3200" # tempo - "55680:55680" # otlp grpc - "55681:55681" # otlp http - "9411:9411" # zipkin - "4318:4318" # new http - "4317:4317" # new grpc networks: - telemetry mimir: image: grafana/mimir:2.5.0 command: "-config.file=/etc/mimir/mimir.yaml" ports: - "9009:9009" volumes: - "./docker/mimir.yaml:/etc/mimir/mimir.yaml" - "/tmp/mimir/rules:/tmp/mimir/rules" networks: - telemetry
Aquí puedes ver que estamos creando una instancia de Grafana respaldada por una Influxdb junto con instancias de Loki, Tempo y Mimir. Exponemos los puertos apropiados para configurar cada instancia con los archivos correspondientes de nuestra carpeta docker. Lo más interesante es el grafana-datasources.yaml que dice grafana configura para conectarse a Loki, Mimir, y Tempo.
apiVersion: 1 datasources: - name: Tempo type: tempo access: proxy orgId: 1 url: http://tempo:3200 basicAuth: false isDefault: true version: 1 editable: false apiVersion: 1 uid: tempo - name: Loki type: loki access: proxy orgId: 1 url: http://loki:3100 basicAuth: false isDefault: false version: 1 editable: false apiVersion: 1 jsonData: derivedFields: - datasourceUid: tempo matcherRegex: (?:traceID|trace_id)=(\w+) name: TraceID url: $${__value.raw} - name: Mimir type: prometheus access: proxy orgId: 1 url: http://mimir:9009/prometheus isDefault: false version: 1 editable: true
Una vez que ejecutamos "docker compose up" podemos acceder a localhost:9000 e iniciar sesión en Grafana con nuestras credenciales admin/admin1 definidas en el archivo docker compose. Ahora vamos a trabajar en conseguir algunos datos de nuestra aplicación Spring Boot. Empezaremos añadiendo logs a loki.
Registros
Por defecto, Spring Boot incluye slf4j por lo que no necesitamos añadir nada a nuestro build.gradle. Sin embargo, en este ejemplo, utilizaremos Grafana Agent para extraer nuestros logs y exportarlos a Loki, por lo que necesitamos actualizar nuestro archivo application.properties para escribir nuestros logs en un archivo y establecer un nivel de logging raíz como este:
logging.file.name=logs/app.log logging.level.root=INFO
A continuación añadimos el Grafana Agent a nuestro archivo docker-compose.yaml:
grafana-agent: image: grafana/agent:v0.22.0 volumes: - ./docker/grafana-agent.yaml:/etc/agent-config/grafana-agent.yaml - ./logs/:/var/log/ entrypoint: - /bin/agent - -config.file=/etc/agent-config/grafana-agent.yaml - -prometheus.wal-directory=/tmp/agent/wal ports: - "12345:12345" networks: - telemetry extra_hosts: - "host.docker.internal:host-gateway"
Y le decimos a Grafana Agent cómo raspar nuestros logs configurando su archivo de configuración (grafana-agent.yaml).
server: log_level: debug http_listen_port: 12345 logs: configs: - name: default positions: filename: /tmp/localhost-positions.yaml clients: - url: http://loki:3100/loki/api/v1/push scrape_configs: - job_name: system static_configs: - labels: job: localhostlogs __path__: /var/log/*log env: "local" app: "observability-example"
Métricas
Para enviar nuestras métricas necesitamos incluir algunas dependencias en nuestro gradle.build
dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' //enable /actuator/prometheus runtimeOnly 'io.micrometer:micrometer-registry-prometheus' //for timed aspect implementation 'org.springframework:spring-aspects' }
Ahora vamos a habilitar los endpoints de métricas en nuestra aplicación añadiendo algunas propiedades al application.properties:
management.endpoint.health.show-details=always management.endpoints.web.exposure.include=health,info,prometheus
Finalmente, le decimos a Grafana Agent de dónde leer nuestras métricas y dónde escribirlas actualizando el archivo de configuración grafana-agent.yaml:
management.endpoint.health.show-details=always management.endpoints.web.exposure.include=health,info,prometheus
Nuestro endpoint acutaor de la app Spring Boot contiene un montón de métricas geniales, si quieres ver ejemplos de algunas métricas personalizadas puedes mirar el FactorService en mi aplicación de ejemplo.
Trazas
Para añadir datos de trazas a nuestra aplicación necesitamos incluir algunas dependencias más en nuestro gradle.build
dependencies { implementation("io.micrometer:micrometer-tracing") implementation("io.micrometer:micrometer-tracing-bridge-otel") implementation("io.opentelemetry:opentelemetry-exporter-zipkin") }
Ahora vamos a habilitar la información de rastreo en nuestros logs y habilitar el rastreo y los endpoints en nuestra aplicación añadiendo algunas propiedades al application.properties:
logging.pattern.level="trace_id=%mdc{traceId} span_id=%mdc{spanId} trace_flags=%mdc{traceFlags} %p" management.tracing.enabled=true management.tracing.sampling.probability=1.0 management.zipkin.tracing.endpoint=http://localhost:9411
¡Ya está! Inicia la aplicación y relanza tu docker compose para que podamos recoger todos los cambios. Puedes encontrar todo el repositorio de la aplicación de ejemplo aquí https://github.com/scottbock/observability-example





