Application Insights almacena logs, métricas y traces distribuidos. El dato está ahí. El problema es que la vista predeterminada del portal de Azure (Live Metrics, el overview básico) no muestra lo que necesitas para operación diaria: latencia por endpoint, error rate por servicio, traces de requests que fallaron, alertas cuando algo cruza un umbral.
Azure Workbooks convierte queries KQL en visualizaciones interactivas. Este post es el conjunto de queries y el diseño de dashboard que uso para monitorear servicios en producción.
Fundamentos de KQL que importan
KQL (Kusto Query Language) es el lenguaje de consulta de Azure Monitor y Application Insights. La sintaxis es distinta de SQL pero el modelo es similar: tablas, filtros, aggregations.
// Estructura básica
tableName
| where condition
| summarize aggregation by grouping
| order by campo desc
| take 100
Las tablas principales de Application Insights:
requests— HTTP requests recibidosdependencies— llamadas a servicios externos (DB, HTTP, Service Bus)traces— logs estructurados (lo que logueas conlogger.info())exceptions— excepciones no manejadascustomEvents— eventos custom que puedes emitir desde el SDK
Error rate por endpoint
requests
| where timestamp > ago(24h)
| summarize
total = count(),
errors = countif(success == false),
error_rate = round(100.0 * countif(success == false) / count(), 2)
by name
| where total > 10 // Solo endpoints con tráfico significativo
| order by error_rate desc
| project name, total, errors, error_rate
Este query retorna la tasa de error por endpoint en las últimas 24 horas. Útil como primera vista del estado del sistema.
Para ver la evolución temporal del error rate:
requests
| where timestamp > ago(7d)
| summarize
total = count(),
error_rate = round(100.0 * countif(success == false) / count(), 2)
by bin(timestamp, 1h)
| render timechart
bin(timestamp, 1h) agrupa por intervalos de 1 hora. render timechart renderiza como gráfico de línea en el portal y en Workbooks.
Latencia por percentil (P50, P95, P99)
El promedio de latencia miente. Un endpoint con P50=50ms y P99=10,000ms tiene “promedio” de ~150ms pero el 1% de los usuarios espera 10 segundos.
requests
| where timestamp > ago(24h)
| where name !startswith "GET /health" // Excluir health checks
| summarize
p50 = percentile(duration, 50),
p95 = percentile(duration, 95),
p99 = percentile(duration, 99),
avg_ms = round(avg(duration), 0),
count = count()
by name
| order by p99 desc
Para ver evolución de P95 en el tiempo:
requests
| where timestamp > ago(7d)
| summarize p95 = percentile(duration, 95)
by bin(timestamp, 1h), name
| where name == "POST /api/pedidos" // Filtrar por endpoint específico
| render timechart
Traces correlacionados por operation_Id
Cuando un request HTTP llama a una base de datos y a un servicio externo, todos esos eventos tienen el mismo operation_Id. Buscar todos los eventos de un request fallido:
let failed_operation = "a1b2c3d4e5f6"; // operation_Id del request fallido
union requests, dependencies, traces, exceptions
| where operation_Id == failed_operation
| order by timestamp asc
| project timestamp, itemType, name, duration, success, message, severityLevel
union combina múltiples tablas en un solo resultado. itemType indica de qué tabla viene cada fila.
Para encontrar los operation_Ids de requests que fallaron recientemente:
requests
| where timestamp > ago(1h)
| where success == false
| project timestamp, name, duration, operation_Id, resultCode
| order by timestamp desc
| take 20
Dependencias lentas (DB queries, HTTP externos)
Las dependencias son las llamadas a servicios externos. Encontrar las más lentas:
dependencies
| where timestamp > ago(24h)
| where success == true // Solo las que completaron (no las que fallaron por timeout)
| summarize
p95 = percentile(duration, 95),
count = count()
by name, type
| where count > 50 // Solo dependencias con volumen significativo
| order by p95 desc
| take 20
type puede ser “SQL”, “HTTP”, “Azure Service Bus”, etc. Filtrar por tipo para ver solo queries SQL lentos:
dependencies
| where timestamp > ago(24h)
| where type == "SQL"
| summarize
p95 = percentile(duration, 95),
calls = count()
by name
| order by p95 desc
Logs con atributos canónicos (customDimensions)
Los logs estructurados con atributos canónicos son queryables por campo:
traces
| where timestamp > ago(1h)
| where customDimensions["correlation.id"] == "req-xyz-123"
| project timestamp, message, severityLevel, customDimensions
| order by timestamp asc
Para logs con un campo específico:
traces
| where timestamp > ago(24h)
| where customDimensions contains "kpi_calculator.not_configured"
| summarize count() by bin(timestamp, 1h)
| render timechart
Esto muestra cuántas veces por hora el SDK de KPI está en modo no-op — una alerta visual de problemas de configuración.
Excepciones más frecuentes
exceptions
| where timestamp > ago(24h)
| summarize
count = count(),
sample_message = any(innermostMessage)
by type, outerMessage
| order by count desc
| take 20
any(innermostMessage) retorna un ejemplo del mensaje de la excepción — útil para ver qué está fallando sin listar todas las ocurrencias.
Dashboard en Azure Workbooks
Azure Workbooks permite combinar múltiples queries KQL en un dashboard interactivo con parámetros de tiempo y filtros.
Estructura del Workbook
Workbook: "Dashboard Operacional"
├── Parameter: timeRange (último 1h / 6h / 24h / 7d)
├── Parameter: service (selector de servicios)
│
├── Sección: Overview
│ ├── Stat tile: Total requests (últimas 24h)
│ ├── Stat tile: Error rate actual
│ └── Stat tile: P99 latencia
│
├── Sección: Latencia
│ ├── Chart: P95 latencia por endpoint (timechart)
│ └── Grid: P50/P95/P99 por endpoint (tabla)
│
├── Sección: Errores
│ ├── Chart: Error rate por hora (timechart)
│ └── Grid: Top 10 errores con ejemplo de mensaje
│
└── Sección: Dependencias
└── Grid: Dependencias más lentas (P95)
Para hacer el Workbook dinámico con el parámetro timeRange:
// En el query, usar {timeRange} como parámetro
requests
| where timestamp > ago({timeRange})
| summarize count() by bin(timestamp, 1h)
| render timechart
{timeRange} se reemplaza por el valor del parámetro seleccionado en la UI del Workbook.
Alertas desde KQL
Una vez que tienes el query correcto, convertirlo en alerta es un paso más:
// Query para alerta: error rate > 5% en los últimos 5 minutos
requests
| where timestamp > ago(5m)
| summarize error_rate = 100.0 * countif(success == false) / count()
| where error_rate > 5
En Azure Monitor → Alerts → Create alert rule → Log search, pegar el query y configurar la condición y el action group (email, Teams, webhook).
Lo que aprendí
Los percentiles son obligatorios para SLOs. “Latencia promedio bajo” puede esconder un P99 inaceptable. Los SLOs deben definirse en términos de P95 o P99, no de promedio.
Los atributos canónicos valen el refactor. Cuando el campo se llama igual en todos los servicios (correlation.id, operation.attempt), un query KQL funciona para todos los servicios. Si cada servicio usa un nombre diferente para el mismo concepto, necesitas queries diferentes para cada uno.
Azure Workbooks con parámetros de tiempo es el dashboard mínimo viable. Un Workbook con los 4-5 queries de este post y un parámetro de rango de tiempo cubre el 80% de las necesidades de monitoreo operacional sin herramientas adicionales.
Guardar queries útiles en un repositorio. Los queries KQL son código. Los útiles deben versionarse igual que el código de la aplicación — con comentarios de qué muestran y cuándo usarlos.