Estado: Aceptado
Fecha: 2025-11-14
Contexto: Arquitectura orientada a observabilidad
Los sistemas distribuidos modernos, especialmente aquellos basados en microservicios y arquitectura orientada a eventos, presentan desafíos únicos en cuanto a monitoreo, diagnóstico y resolución de problemas. La complejidad inherente de estos sistemas requiere que la observabilidad sea un principio fundamental desde el diseño, no una consideración secundaria.
Este framework debe ser observable por diseño, utilizando tecnologías modernas, gratuitas y open-source para garantizar que cada componente, funcionalidad y servicio pueda ser monitoreado, rastreado y analizado de manera efectiva.
Adoptamos una arquitectura orientada a observabilidad como principio rector fundamental del framework. La observabilidad no es opcional: es un requisito obligatorio para considerar cualquier funcionalidad como completada.
- Observabilidad Total: Todo componente debe ser observable
- Tres Pilares Obligatorios: Logs, Metrics, Traces en cada servicio
- Instrumentación desde el Diseño: No como agregado posterior
- Stack Open-Source: Solo tecnologías gratuitas y de código abierto
- Criterio de Finalización: La capacidad de observar es criterio de "Done"
- Propósito: Estándar unificado para instrumentación
- Componentes:
- OTel SDK para Python (backend)
- OTel SDK para JavaScript (frontend)
- OTel Collector para agregación y exportación
- Ventajas:
- Vendor-neutral
- Soporta traces, metrics, logs
- Amplia adopción en la industria
- Integración nativa con Prometheus, Jaeger, Grafana
- Propósito: Sistema de monitoreo y base de datos de métricas
- Métricas a Recolectar:
- Request rate, error rate, duration (RED method)
- Resource utilization (CPU, memory, disk)
- Custom business metrics
- Event processing metrics
- Características:
- Pull-based metric collection
- Powerful query language (PromQL)
- Alerting con Alertmanager
- Service discovery integrado
- Propósito: Visualización y dashboards
- Uso:
- Dashboards en tiempo real
- Alertas visuales
- Múltiples fuentes de datos (Prometheus, Loki, Jaeger)
- Templating para servicios similares
- Dashboards Estándar:
- Service Overview (RED metrics)
- Infrastructure Overview
- Event Processing Dashboard
- Business Metrics Dashboard
- Propósito: Distributed tracing
- Características:
- Rastreo de solicitudes end-to-end
- Visualización de dependencias entre servicios
- Análisis de latencia
- Root cause analysis
- Integración:
- Via OpenTelemetry Collector
- Context propagation automático
- Sampling configurable
- Propósito: Log aggregation (similar a Prometheus para logs)
- Características:
- Indexación eficiente por labels
- Query language similar a PromQL (LogQL)
- Integración nativa con Grafana
- Storage eficiente
- Propósito: Agent para enviar logs a Loki
- Características:
- Tail de archivos de log
- Parsing y labeling automático
- Integración con systemd, Docker, Kubernetes
# Estructura obligatoria de instrumentación
from opentelemetry import trace, metrics
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from prometheus_client import Counter, Histogram, Gauge
import structlog
# 1. Configuración de Traces (Jaeger via OTel)
tracer = trace.get_tracer(__name__)
# 2. Configuración de Metrics (Prometheus)
http_requests_total = Counter(
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status']
)
http_request_duration_seconds = Histogram(
'http_request_duration_seconds',
'HTTP request duration',
['method', 'endpoint']
)
# 3. Configuración de Logs (Loki via structlog)
logger = structlog.get_logger()@app.post("/api/orders")
@tracer.start_as_current_span("create_order")
async def create_order(order: OrderCreate):
span = trace.get_current_span()
span.set_attribute("order.id", order.id)
# Logging estructurado con contexto
logger.info("order_creation_started",
order_id=order.id,
user_id=order.user_id)
with http_request_duration_seconds.labels(
method='POST',
endpoint='/api/orders'
).time():
try:
result = await order_service.create(order)
http_requests_total.labels(
method='POST',
endpoint='/api/orders',
status='success'
).inc()
logger.info("order_creation_completed",
order_id=order.id,
duration_ms=span.elapsed_ms)
return result
except Exception as e:
http_requests_total.labels(
method='POST',
endpoint='/api/orders',
status='error'
).inc()
logger.error("order_creation_failed",
order_id=order.id,
error=str(e),
exc_info=True)
span.record_exception(e)
raise@event_handler("OrderCreatedEvent")
@tracer.start_as_current_span("handle_order_created")
async def handle_order_created(event: OrderCreatedEvent):
span = trace.get_current_span()
span.set_attribute("event.type", "OrderCreatedEvent")
span.set_attribute("event.id", event.id)
span.set_attribute("correlation_id", event.correlation_id)
logger.info("event_processing_started",
event_type="OrderCreatedEvent",
event_id=event.id,
correlation_id=event.correlation_id)
event_processing_duration.labels(
event_type='OrderCreatedEvent'
).observe(time.time())
try:
await process_order_created(event)
event_processing_total.labels(
event_type='OrderCreatedEvent',
status='success'
).inc()
logger.info("event_processing_completed",
event_type="OrderCreatedEvent",
event_id=event.id)
except Exception as e:
event_processing_total.labels(
event_type='OrderCreatedEvent',
status='error'
).inc()
logger.error("event_processing_failed",
event_type="OrderCreatedEvent",
event_id=event.id,
error=str(e),
exc_info=True)
raise// Instrumentación con OTel para navegador
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base';
// Custom metrics hook
function useMetrics() {
const recordPageView = (pageName: string) => {
// Send metric to backend or collection endpoint
fetch('/api/metrics', {
method: 'POST',
body: JSON.stringify({
metric: 'page_view',
page: pageName,
timestamp: Date.now()
})
});
};
return { recordPageView };
}FastAPI genera automáticamente documentación OpenAPI/Swagger:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI(
title="Spotify Integration API",
description="API para integración con Spotify Web API",
version="1.0.0",
docs_url="/docs", # Swagger UI
redoc_url="/redoc" # ReDoc UI
)
class OrderCreate(BaseModel):
"""Modelo para crear una orden"""
user_id: str = Field(..., description="ID del usuario")
track_uri: str = Field(..., description="URI del track de Spotify")
class Config:
json_schema_extra = {
"example": {
"user_id": "user123",
"track_uri": "spotify:track:abc123"
}
}
@app.post(
"/api/orders",
response_model=OrderResponse,
summary="Crear nueva orden",
description="Crea una orden de reproducción en Spotify",
tags=["Orders"]
)
async def create_order(order: OrderCreate):
"""
Crea una nueva orden de reproducción.
- **user_id**: Usuario que solicita la reproducción
- **track_uri**: URI del track de Spotify a reproducir
"""
return await order_service.create(order)Herramientas Adicionales:
- Reflect: Sistema de documentación backend para APIs
- Stoplight: Editor y documentación de OpenAPI
- Postman: Colecciones documentadas para testing
- Redocly: Documentación hermosa a partir de OpenAPI
Requisitos:
- ✅ Todos los endpoints deben tener docstrings
- ✅ Todos los modelos Pydantic deben tener descriptions
- ✅ Ejemplos de request/response en schemas
- ✅ Tags para agrupar endpoints relacionados
- ✅ Documentación accesible en
/docs(Swagger) y/redoc(ReDoc)
Para componentes React, usar Storybook:
// Component.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { PlaybackControls } from './PlaybackControls';
const meta: Meta<typeof PlaybackControls> = {
title: 'Features/Playback/PlaybackControls',
component: PlaybackControls,
parameters: {
docs: {
description: {
component: 'Controles de reproducción para DJ interface'
}
}
},
tags: ['autodocs']
};
export default meta;
type Story = StoryObj<typeof PlaybackControls>;
export const Playing: Story = {
args: {
isPlaying: true,
trackName: 'Test Track',
artistName: 'Test Artist'
}
};Requisitos:
- ✅ Todos los componentes públicos deben tener stories
- ✅ Props documentadas con JSDoc
- ✅ Variantes comunes como stories
- ✅ Storybook accesible en desarrollo
Formato Obligatorio:
{
"timestamp": "2025-11-14T22:05:36.209Z",
"level": "info",
"service": "spotify-integration-api",
"correlation_id": "abc-123-def",
"trace_id": "8e42f1a2b3c4d5e6",
"span_id": "f7g8h9i0j1k2",
"message": "user_authenticated",
"user_id": "user123",
"spotify_user_id": "spotify456",
"duration_ms": 142
}Campos Obligatorios:
timestamp: ISO 8601level: debug, info, warning, error, criticalservice: Nombre del serviciocorrelation_id: ID de correlación para rastreotrace_id: OpenTelemetry trace IDspan_id: OpenTelemetry span IDmessage: Mensaje descriptivo en snake_case
# Request Rate
http_requests_total
# Error Rate
http_requests_failed_total
# Duration
http_request_duration_seconds# Event rate
events_published_total{event_type="OrderCreatedEvent"}
events_consumed_total{event_type="OrderCreatedEvent"}
# Event processing duration
event_processing_duration_seconds{event_type="OrderCreatedEvent"}
# Event processing errors
event_processing_errors_total{event_type="OrderCreatedEvent"}# Ejemplos específicos del dominio
orders_created_total
payment_approved_total
spotify_tracks_played_total
dj_sessions_active- CPU utilization
- Memory utilization
- Disk I/O
- Network I/O
Contexto de Propagación:
- Cada request HTTP debe tener un trace ID
- Los eventos deben propagar el trace context
- Correlation ID en todos los logs
Spans Obligatorios:
- HTTP request/response
- Database queries
- External API calls
- Event publish/consume
- Business operations
- Request rate (requests/sec)
- Error rate (%)
- P50, P95, P99 latency
- Active connections
- Event processing rate
- CPU usage per service
- Memory usage per service
- Disk usage
- Network throughput
- Domain-specific metrics
- User activity metrics
- Business KPIs
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Service 1 │ │Service 2 │ │Service 3 │ │Service 4 │ │
│ │(OTel SDK)│ │(OTel SDK)│ │(OTel SDK)│ │(OTel SDK)│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ └─────────────┴─────────────┴─────────────┘ │
│ │ │
└──────────────────────────┼──────────────────────────────────┘
│
▼
┌────────────────────────┐
│ OpenTelemetry │
│ Collector │
│ (Aggregation Layer) │
└────────┬───────────────┘
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌─────────┐ ┌────────┐
│Promethe│ │ Jaeger │ │ Loki │
│ us │ │(Traces) │ │ (Logs) │
│(Metrics│ └─────────┘ └────────┘
│) │ │ │
└────┬───┘ │ │
│ │ │
└─────────────┴─────────────┘
│
▼
┌─────────────────┐
│ Grafana │
│ (Visualization)│
└─────────────────┘
Una funcionalidad o servicio solo se considera COMPLETO cuando:
-
Traces Implementadas:
- Instrumentación con OpenTelemetry SDK
- Trace propagation entre servicios
- Spans en operaciones críticas
- Context propagation en eventos
-
Metrics Implementadas:
- RED metrics (rate, errors, duration)
- Event processing metrics
- Business metrics relevantes
- Prometheus endpoint expuesto (
/metrics)
-
Logs Implementados:
- Logs estructurados con structlog (Python) o similar
- Correlation IDs en todos los logs
- Trace/Span IDs vinculados
- Log levels apropiados
-
Dashboards Creados:
- Dashboard de servicio en Grafana
- Alertas configuradas para errores críticos
- Visualizaciones de métricas clave
-
Documentación:
- Métricas documentadas
- Traces documentadas
- Runbooks para alertas
- Troubleshooting guide
- API Documentation: Endpoints documentados en sistema de documentación
- Backend: OpenAPI/Swagger automático (FastAPI) o herramientas como Reflect
- Frontend: Storybook para componentes React
-
Testing de Observabilidad:
- Tests que validan emisión de metrics
- Tests que validan creación de traces
- Tests que validan logs estructurados
- Visibilidad Total: Capacidad de observar el comportamiento de todo el sistema
- Debugging Eficiente: Reducción de tiempo en resolución de problemas
- Proactividad: Detección temprana de problemas antes de impactar usuarios
- Confiabilidad: Métricas objetivas de SLOs/SLAs
- Optimización: Identificación de bottlenecks y oportunidades de mejora
- Auditoría: Trazabilidad completa de operaciones
- Open-Source: Sin costos de licenciamiento, comunidad activa
- Overhead Inicial: Más código y configuración al inicio
- Curva de Aprendizaje: Equipo debe aprender las herramientas
- Infraestructura: Necesidad de desplegar stack de observabilidad
- Storage: Métricas, logs y traces consumen espacio
- Performance: Instrumentación tiene overhead (mitigable con sampling)
- Templates y generadores: Crear templates con instrumentación incluida
- Capacitación: Documentación y workshops sobre observabilidad
- Automatización: Scripts de despliegue del stack
- Sampling inteligente: Configurar sampling rates apropiados
- Retention policies: Políticas de retención de datos configurables
Mediremos el éxito de esta decisión con:
- Coverage: % de servicios con observabilidad completa
- MTTR: Tiempo medio de resolución de problemas (debe reducirse)
- MTTD: Tiempo medio de detección de problemas (debe reducirse)
- Adoption: % del equipo capacitado en herramientas
- Usage: % de incidents diagnosticados con herramientas de observabilidad
- OpenTelemetry Documentation
- Prometheus Best Practices
- Grafana Dashboards
- Jaeger Architecture
- The Three Pillars of Observability
- RED Method
- Observability Guide
- 2025-11-14: Creación inicial - Establecimiento de arquitectura observability-first