En los sistemas de software modernos, el monitoring (como se discute en Risk-Based Testing: Prioritizing Test Efforts for Maximum Impact) y la observabilidad han evolucionado desde preocupaciones operacionales a prácticas críticas de aseguramiento de calidad. Los profesionales QA deben entender no solo si las características funcionan correctamente durante las pruebas, sino también cómo se comportan los sistemas en producción, identificar cuellos de botella de rendimiento y detectar problemas antes de que los usuarios (como se discute en Bug Anatomy: From Discovery to Resolution) los experimenten.
Esta guía comprensiva explora cómo los equipos QA pueden aprovechar herramientas de monitoring y observabilidad—incluyendo ELK Stack para logs, Prometheus y Grafana para métricas, distributed tracing y synthetic monitoring—para mejorar estrategias de testing, mejorar la confiabilidad del sistema y habilitar aseguramiento de calidad proactivo.
Entendiendo Monitoring vs. Observabilidad
Aunque a menudo se usan indistintamente, monitoring y observabilidad sirven propósitos diferentes:
Monitoring
Definición: Recopilar, agregar y analizar métricas predefinidas para detectar problemas conocidos.
Características:
- Responde preguntas conocidas: “¿El sistema está activo?” “¿El uso de CPU está por encima del 80%?”
- Enfoque reactivo: Las alertas se disparan cuando se exceden umbrales
- Se enfoca en salud y disponibilidad del sistema
- Funciona con dashboards y alertas predefinidos
Ejemplo: Alertar cuando el tiempo de respuesta de API excede 500ms durante 5 minutos consecutivos.
Observabilidad
Definición: Entender el estado interno del sistema basándose en salidas externas (logs, métricas, traces) para responder preguntas arbitrarias.
Características:
- Responde preguntas desconocidas: “¿Por qué falla el checkout para usuarios iOS en Europa?”
- Enfoque proactivo: Habilita exploración y depuración
- Se enfoca en entender el comportamiento del sistema
- Funciona con consultas flexibles y correlación
Ejemplo: Investigar por qué falló la transacción de un usuario específico correlacionando logs, métricas y traces a través de múltiples servicios.
Los Tres Pilares de la Observabilidad
- Logs: Eventos discretos con timestamps describiendo qué sucedió
- Métricas: Mediciones numéricas a lo largo del tiempo mostrando rendimiento del sistema
- Traces: Viaje end-to-end de requests a través de sistemas distribuidos
ELK Stack para Gestión de Logs
El ELK Stack (Elasticsearch, Logstash, Kibana) proporciona potentes capacidades de agregación, búsqueda y visualización de logs.
Arquitectura ELK Stack
Elasticsearch: Motor de búsqueda y análisis distribuido para almacenar y consultar logs Logstash: Pipeline de procesamiento de datos del lado del servidor para ingerir, transformar y enviar logs Kibana: Herramienta de visualización y exploración para datos de Elasticsearch Beats (a menudo añadido): Recolectores de datos ligeros para reenviar logs desde aplicaciones
Configurando ELK Stack
Setup Docker Compose (docker-compose.yml):
version: '3.8'
services:
elasticsearch:
image: docker (como se discute en [Service Mesh Testing: Istio and Linkerd Testing Guide](/blog/service-mesh-testing)).elastic.co/elasticsearch/elasticsearch:8.10.0
container_name: elasticsearch
environment:
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- xpack.security.enabled=false
ports:
- "9200:9200"
- "9300:9300"
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
networks:
- elk
logstash:
image: docker.elastic.co/logstash/logstash:8.10.0
container_name: logstash
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline
ports:
- "5044:5044"
networks:
- elk
depends_on:
- elasticsearch
kibana:
image: docker.elastic.co/kibana/kibana:8.10.0
container_name: kibana
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
networks:
- elk
depends_on:
- elasticsearch
volumes:
elasticsearch-data:
networks:
elk:
driver: bridge
Configuración pipeline de Logstash (logstash/pipeline/logstash.conf):
input {
beats {
port => 5044
}
tcp {
port => 5000
codec => json
}
}
filter {
# Parsear logs JSON
if [message] =~ /^\{.*\}$/ {
json {
source => "message"
}
}
# Extraer nivel de log
grok {
match => {
"message" => "%{LOGLEVEL:log_level}"
}
}
# Parsear timestamp
date {
match => [ "timestamp", "ISO8601", "yyyy-MM-dd HH:mm:ss,SSS" ]
target => "@timestamp"
}
# Filtrar logs de healthcheck
if [path] == "/health" or [url] == "/healthz" {
drop { }
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "logs-%{[service][name]}-%{+YYYY.MM.dd}"
}
stdout {
codec => rubydebug
}
}
Usando Kibana para QA
Creando Index Patterns:
- Navegar a Management → Stack Management → Index Patterns
- Crear patrón:
logs-*
- Seleccionar campo timestamp:
@timestamp
Construyendo Dashboards Enfocados en QA:
Dashboard de Monitoring de Ejecución de Pruebas:
{
"title": "Test Execution Monitoring",
"panels": [
{
"title": "Test Pass Rate",
"type": "metric",
"query": "service.name:test-runner AND test.status:*"
},
{
"title": "Failed Tests Over Time",
"type": "line",
"query": "test.status:failed"
},
{
"title": "Test Duration Distribution",
"type": "histogram",
"field": "test.duration"
}
]
}
Ejemplos útiles de Kibana Query Language (KQL):
# Encontrar todos los errores en servicio de checkout
service.name:"checkout" AND log_level:ERROR
# Encontrar respuestas API lentas (>1 segundo)
http.response.time_ms > 1000
# Encontrar intentos de autenticación fallidos
event.action:"login" AND event.outcome:"failure"
# Encontrar errores afectando usuario específico
user.id:"12345" AND log_level:ERROR
# Encontrar errores de conexión a base de datos
message:"connection refused" OR message:"timeout"
# Encontrar errores en los últimos 15 minutos
log_level:ERROR AND @timestamp >= now-15m
# Excluir logs de healthcheck y monitoring
NOT (url:"/health" OR url:"/metrics")
Correlación de Logs para QA
Correlacionar logs a través de servicios usando trace IDs:
Logging de aplicación con contexto de trace (ejemplo Node.js):
const winston = require('winston');
const { v4: uuidv4 } = require('uuid');
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' })
]
});
// Middleware para añadir trace ID
function traceMiddleware(req, res, next) {
req.traceId = req.headers['x-trace-id'] || uuidv4();
res.setHeader('X-Trace-ID', req.traceId);
req.logger = logger.child({
traceId: req.traceId,
service: 'api-gateway',
environment: process.env.NODE_ENV
});
next();
}
// Usar en requests
app.use(traceMiddleware);
app.post('/checkout', async (req, res) => {
req.logger.info('Checkout initiated', {
userId: req.user.id,
cartItems: req.body.items.length
});
try {
const result = await processCheckout(req.body, req.traceId);
req.logger.info('Checkout completed', { orderId: result.orderId });
res.json(result);
} catch (error) {
req.logger.error('Checkout failed', {
error: error.message,
userId: req.user.id
});
res.status(500).json({ error: 'Checkout failed' });
}
});
Prometheus y Grafana para Métricas
Prometheus recopila y almacena métricas como datos de series temporales, mientras Grafana proporciona visualización y alertas.
Arquitectura Prometheus
Componentes:
- Prometheus Server: Recopila y almacena métricas
- Exporters: Exponen métricas de aplicaciones e infraestructura
- Pushgateway: Permite a trabajos de corta duración enviar métricas
- Alertmanager: Maneja alertas y notificaciones
Configurando Prometheus
Adición Docker Compose:
prometheus:
image: prom/prometheus:v2.47.0
container_name: prometheus
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
ports:
- "9090:9090"
networks:
- monitoring
grafana:
image: grafana/grafana:10.1.0
container_name: grafana
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
ports:
- "3000:3000"
networks:
- monitoring
depends_on:
- prometheus
Configuración Prometheus (prometheus/prometheus.yml):
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'api-gateway'
static_configs:
- targets: ['api-gateway:8080']
metrics_path: '/metrics'
- job_name: 'checkout-service'
static_configs:
- targets: ['checkout:8081']
Instrumentando Aplicaciones para Prometheus
Métricas de aplicación Node.js (Express + prom-client):
const express = require('express');
const promClient = require('prom-client');
const app = express();
const register = new promClient.Registry();
promClient.collectDefaultMetrics({ register });
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 0.5, 1, 2, 5]
});
const checkoutTotal = new promClient.Counter({
name: 'checkout_total',
help: 'Total number of checkout attempts',
labelNames: ['status', 'payment_method']
});
register.registerMetric(httpRequestDuration);
register.registerMetric(checkoutTotal);
// Middleware para rastrear requests
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const route = req.route ? req.route.path : req.path;
httpRequestDuration
.labels(req.method, route, res.statusCode)
.observe(duration);
});
next();
});
// Endpoint de métricas
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
app.listen(8080);
Dashboards Grafana para QA
Consultas PromQL útiles para QA:
# Tasa de requests por segundo
rate(http_requests_total[5m])
# Porcentaje de tasa de error
(rate(http_requests_total{status_code=~"5.."}[5m]) / rate(http_requests_total[5m])) * 100
# Tiempo de respuesta percentil 95
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
# Checkouts fallidos en última hora
increase(checkout_total{status="failure"}[1h])
# Duración promedio de checkout
rate(checkout_duration_seconds_sum[5m]) / rate(checkout_duration_seconds_count[5m])
# Porcentaje de uso de memoria
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100
# Uso de CPU
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
Distributed Tracing
El distributed tracing rastrea requests a medida que fluyen a través de microservicios, proporcionando visibilidad end-to-end.
Setup de Jaeger
Adición Docker Compose:
jaeger:
image: jaegertracing/all-in-one:1.50
container_name: jaeger
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- "16686:16686" # UI
- "14250:14250"
- "14268:14268"
networks:
- tracing
Instrumentando Node.js con OpenTelemetry:
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
const provider = new NodeTracerProvider();
const exporter = new JaegerExporter({
endpoint: 'http://jaeger:14268/api/traces',
});
provider.addSpanProcessor(new opentelemetry.tracing.SimpleSpanProcessor(exporter));
provider.register();
const tracer = provider.getTracer('checkout-service');
async function processCheckout(order) {
const span = tracer.startSpan('process_checkout');
span.setAttributes({
'order.id': order.id,
'user.id': order.userId,
});
try {
const result = await createOrder(order);
span.setStatus({ code: opentelemetry.SpanStatusCode.OK });
return result;
} catch (error) {
span.recordException(error);
throw error;
} finally {
span.end();
}
}
Synthetic Monitoring
El synthetic monitoring prueba proactivamente la disponibilidad y rendimiento del sistema desde la perspectiva del usuario.
Usando Prometheus Blackbox Exporter
Configuración (blackbox.yml):
modules:
http_2xx:
prober: http
timeout: 5s
http:
method: GET
valid_status_codes: [200]
http_post_checkout:
prober: http
timeout: 10s
http:
method: POST
headers:
Content-Type: application/json
body: '{"userId": "test", "items": [{"id": "123"}]}'
valid_status_codes: [200, 201]
Alerting para QA
Reglas de alerta de Prometheus (alerts/qa-alerts.yml):
groups:
- name: qa_alerts
interval: 30s
rules:
- alert: HighErrorRate
expr: |
(rate(http_requests_total{status_code=~"5.."}[5m]) / rate(http_requests_total[5m])) * 100 > 5
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value }}%"
- alert: SlowAPIResponse
expr: |
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 2
for: 10m
labels:
severity: warning
annotations:
summary: "API response time degraded"
- alert: ServiceDown
expr: up{job="api-gateway"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Service {{ $labels.job }} is down"
Conclusión
El monitoring y la observabilidad son componentes esenciales de las prácticas QA modernas. Aprovechando herramientas como ELK Stack para logs, Prometheus y Grafana para métricas, distributed tracing con Jaeger y synthetic monitoring, los equipos QA pueden cambiar del descubrimiento reactivo de bugs al aseguramiento proactivo de calidad.
Estas herramientas permiten a los profesionales QA entender el comportamiento del sistema en producción, identificar cuellos de botella de rendimiento, correlacionar problemas a través de servicios y detectar problemas antes de que impacten a los usuarios.
Puntos Clave:
- La observabilidad extiende QA más allá del testing tradicional
- Logs, métricas y traces proporcionan visibilidad comprensiva del sistema
- ELK Stack habilita búsqueda y análisis potente de logs
- Prometheus y Grafana rastrean métricas de rendimiento a lo largo del tiempo
- Distributed tracing revela interacciones de servicios y cuellos de botella
- Synthetic monitoring valida proactivamente disponibilidad del sistema
- Alerting habilita respuesta rápida a problemas de calidad
- Integración con CI/CD proporciona insights continuos de calidad