C

CULTIVA IA — Cuaderno KQL

Consultas de referencia para Azure Log Analytics · Sentinel · ADX

Azure Log Analytics Microsoft Sentinel ADX Avanzado
14
Secciones KQL
20+
Queries listas
13
Errores mapeados
3
Tablas CULTIVA
📊
1. Diagnóstico diario — Uso por servicio
Tabla: CultivaUsageLogs · Filtros de tiempo seguros · Agregaciones pre-filtradas
Top servicios por tokens (últimas 24 h)
uso
// ✅ Siempre filtrar primero, luego summarize
CultivaUsageLogs
| where Timestamp > ago(24h)
| where ErrorCode == ""            // sólo peticiones OK
| summarize
    TotalTokens  = sum(TokensUsed),
    TotalReqs    = count(),
    P95LatencyMs = percentile(LatencyMs, 95)
    by ServiceName
| top 10 by TotalTokens desc
✓ Pre-filtrado por ago(24h) antes de summarize — seguro en tablas grandes.
Errores por cliente — campo dynamic
errores
// ⚠ ErrorCode es dynamic → cast antes de summarize by
CultivaUsageLogs
| where Timestamp > ago(7d)
| where isnotempty(tostring(ErrorCode))
| extend
    ErrorStr = tostring(ErrorCode),         // cast dinámico → string
    ClientStr = tostring(ClientId)
| summarize ErrorCount = count() by ClientStr, ErrorStr
| top 20 by ErrorCount desc
⚠ Sin el cast a tostring() obtendrías "is of a 'dynamic' type" en el by.
Latencia P50/P95/P99 por hora
SLA
CultivaUsageLogs
| where Timestamp between (datetime(2026-06-13) .. datetime(2026-06-14))
| extend Hour = bin(Timestamp, 1h)
| summarize
    P50 = percentile(LatencyMs, 50),
    P95 = percentile(LatencyMs, 95),
    P99 = percentile(LatencyMs, 99)
    by Hour, ServiceName
| order by Hour asc
ℹ Usa bin(Timestamp, 1h) para agrupar por hora — más explícito que startofhour().
Uso por tipo de acción — extraer campo de Metadata
dynamic
// Metadata es dynamic — extraer subcampo con indexación
CultivaUsageLogs
| where Timestamp > ago(3d)
| extend
    Model   = tostring(Metadata["model"]),
    Version = tostring(Metadata["api_version"])
| summarize
    Requests = count(),
    AvgTokens = avg(TokensUsed)
    by Model, Version, ActionType
| order by Requests desc
Metadata["model"] indexa el campo JSON dinámico; el tostring() evita errores en by.
💶
2. Facturación cruzada con uso
Join entre BillingEvents y CultivaUsageLogs · Verificación de cardinalidad obligatoria
Paso 1 — Comprobar cardinalidad antes del join
seguridad
// SIEMPRE verificar antes de unir tablas grandes
CultivaUsageLogs
| where Timestamp > ago(30d)
| summarize dcount(ClientId)   // → ¿cuántos clientes distintos?

BillingEvents
| where Timestamp > ago(30d)
| summarize dcount(ClientId)   // → comparar ambas cifras
⚠ Un join 25K×195 puede explotar en 4.8M filas y causar E_RUNAWAY_QUERY. Comproba primero.
Paso 2 — Join uso vs facturación con $left/$right explícito
join
// ✅ Especificar ambos lados del on: $left.X == $right.Y
let usage_30d =
    CultivaUsageLogs
    | where Timestamp > ago(30d)
    | summarize TotalTokens = sum(TokensUsed) by ClientId;
BillingEvents
| where Timestamp > ago(30d)
| summarize TotalEur = sum(AmountEur) by ClientId, PlanTier
| join kind=leftouter (usage_30d)
    on $left.ClientId == $right.ClientId
| extend EurPerToken = round(TotalEur / TotalTokens, 6)
| project ClientId, PlanTier, TotalEur, TotalTokens, EurPerToken
| order by TotalEur desc
let pre-agrega cada lado antes del join — reduce filas de miles a decenas.
📈
3. Series temporales y detección de anomalías
make-series · series_decompose_anomalies · row_cumsum con serialización obligatoria
Anomalías en peticiones diarias
anomalías
CultivaUsageLogs
| where Timestamp > ago(90d)
| make-series
    DailyReqs = count() default=0
    on Timestamp step 1d
| extend
    anomalies  = series_decompose_anomalies(DailyReqs),
    forecast   = series_decompose_forecast(DailyReqs, 7)
| project Timestamp, DailyReqs, anomalies, forecast
make-series + default=0 rellena huecos — ideal para gráficas sin baches.
Tokens acumulados — row_cumsum con serialize
ventana
// ❌ SIN serialize → "row set must be serialized"
// ✅ | order by serializa implícitamente
CultivaUsageLogs
| where Timestamp > ago(30d)
| summarize
    DailyTokens = sum(TokensUsed)
    by bin(Timestamp, 1d)
| order by Timestamp asc       // serializa implícitamente
| extend
    CumulativeTokens = row_cumsum(DailyTokens),
    RowNum           = row_number()
row_cumsum(), row_number(), prev(), next() requieren serialización previa.
🛡
4. Alertas de Sentinel — Propiedades dinámicas y regex
SecurityAlerts · ExtendedProperties dynamic · extract_all con grupos de captura
Alertas críticas por cliente (últimas 48 h)
sentinel
SecurityAlerts
| where TimeGenerated > ago(48h)
| where Severity in ("High", "Critical")
| extend
    ClientStr  = tostring(ClientId),          // dynamic → string
    TacticStr  = tostring(ExtendedProperties["tactic"])
| summarize
    AlertCount = count(),
    Tactics    = make_set(TacticStr, 10)         // limitar make_set a 10
    by ClientStr, AlertName
| top 20 by AlertCount desc
make_set(..., 10) limita la cardinalidad — evita celdas enormes en la columna de arrays.
Extraer IPs con extract_all — grupos obligatorios
regex
// ❌ Sin () → "matching groups" error
// ✅ Con () capturando el grupo deseado
SecurityAlerts
| where TimeGenerated > ago(7d)
| extend
    AlertDesc = tostring(ExtendedProperties["description"])
| extend
    // ✅ paréntesis alrededor del patrón IPv4
    IPs = extract_all(@"(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)",
                    AlertDesc)
| where array_length(IPs) > 0
| mv-expand IP = IPs to typeof(string)
| summarize count() by IP
| top 15 by count_ desc
extract_all(@"\b\d+\.\d+\.\d+\.\d+\b", ...) falla — siempre envuelve en ().
🔧
5. Tabla de auto-corrección de errores — CULTIVA IA
Mapeo rápido: mensaje de error → causa → fix aplicado
Error KQL Causa probable Fix
is of a 'dynamic' type Columna dynamic en by / on / order by tostring() / tolong() / todouble()
Only equality is allowed Predicado de rango en condición de join Pre-bucket con bin() o S2 cells, luego join
extractall(): matching groups Regex sin grupo de captura () Añadir (): @"(\w+)" no @"\w+"
row set must be serialized row_cumsum / row_number sin orden previo | order by antes, o | serialize
Cannot compare values of types string and string Valor computado en comparación tostring() en ambos lados
for each left attribute, right attribute Cláusula on incompleta on $left.X == $right.Y explícito
E_LOW_MEMORY_CONDITION Scan sin filtro previo en tabla grande | where de rango temporal antes del summarize
E_RUNAWAY_QUERY Join/agregación → demasiadas filas de salida Comprobar dcount() en ambas tablas antes del join
Expected string literal in datetime() Año suelto: datetime(2026) datetime(2026-01-01) siempre con fecha completa
Unexpected token after by Expresión compleja en summarize by | extend primero, luego summarize by la columna
6. Checklist pre-ejecución
Pasar antes de lanzar cualquier query sobre tablas de producción CULTIVA
Tabla grande → filtro | where Timestamp > ago(Nd) antes de cualquier | summarize
Resultado exploratorio → termina con | take 20 o | top 20 by ...
Columna dynamic en by/on/order by → envuelta en tostring() / tolong()
Regex con extract_all → patrón tiene () obligatorios
Join entre tablas → dcount(ClientId) comprobado en ambas antes de ejecutar
Funciones de ventana (row_cumsum, prev, next) → precedidas de | order by o | serialize
Literal datetime → formato completo datetime(2026-06-14), nunca datetime(2026)
Columna de vectores/embeddings → | project para excluirla si no es necesaria (30 KB/fila)
Error en query → leer el mensaje, aplicar fix específico (Sección 5); NO cambiar de estrategia sin 2 intentos