Skip to content

Amazon CloudWatch ​

Amazon CloudWatch es el sistema nervioso operativo de AWS: Logs para ver qué pasó, Metrics para medirlo y Alarms para que te avise antes que el cliente. En serverless (Lambda, Step Functions, SQS, API Gateway) casi toda la observabilidad sale de aquí, y el 90 % del día a día cabe en una docena de comandos CLI.

Comandos verificados contra AWS CLI v2. Tiempos de filter-log-events/start-query van en milisegundos epoch; en macOS: $(($(date -v-1H +%s)*1000)) (última hora).

Conceptos clave ​

  • Log group / Log stream: el grupo es el contenedor (/aws/lambda/MiFuncion); el stream, cada instancia de ejecución que escribe
  • Retention: días que se guarda cada grupo (--retention-in-days); sin política, guarda para siempre (y factura)
  • Filter pattern: sintaxis para buscar (ERROR, "PaymentError", { $.level = "error" })
  • CloudWatch Logs Insights: SQL-like sobre logs (fields | filter | stats)
  • Namespace / Dimensions / Period / Statistic: coordenadas de una métrica (AWS/Lambda + FunctionName=X + 60 s + Sum)
  • Alarm: umbral sobre una métrica con estados OK / ALARM / INSUFFICIENT_DATA y acciones (SNS, auto scaling, etc.)

Dónde loguea cada servicio serverless ​

ServicioLog group¿Automático?
Lambda/aws/lambda/<FunctionName>Sí, desde la primera invocación
Step Functions/aws/states/<StateMachine>No, hay que activar logging-configuration
API Gateway REST (v1)Execution logs por stage + access logsNo, se activa por stage
API Gateway HTTP (v2)Solo access logs a un grupo elegidoNo, se configura en el stage
SQS— (no hay logs de mensajes)Métricas + CloudTrail para la API
bash
# Ver qué grupos existen (filtra por prefijo del servicio)
aws logs describe-log-groups --log-group-name-prefix /aws/lambda
aws logs describe-log-groups --log-group-name-prefix /aws/states

Logs en vivo y búsqueda (lo de todos los días) ​

bash
# Seguir el log en vivo como un tail -f (lo más usado en incidentes)
aws logs tail /aws/lambda/ProcessPaymentFunction --follow --since 1h

# Solo errores de la última hora
aws logs tail /aws/lambda/ProcessPaymentFunction --since 1h \
  --filter-pattern ERROR

# Búsqueda acotada por tiempo (milisegundos epoch) con patrón JSON
aws logs filter-log-events \
  --log-group-name /aws/lambda/ProcessPaymentFunction \
  --start-time $(($(date -v-1H +%s)*1000)) \
  --filter-pattern '{ $.level = "error" }' \
  --query 'events[*].[timestamp,message]' --output text

# Leer un stream concreto (útil cuando un @logStream concentra el fallo)
aws logs describe-log-streams \
  --log-group-name /aws/lambda/ProcessPaymentFunction \
  --order-by LastEventTime --descending --max-items 5
aws logs get-log-events \
  --log-group-name /aws/lambda/ProcessPaymentFunction \
  --log-stream-name '<logStreamName>' \
  --start-from-head --limit 50

CloudWatch Logs Insights (preguntas agregadas) ​

bash
# Top 10 mensajes de error de la última hora en una Lambda
aws logs start-query \
  --log-group-name /aws/lambda/ProcessPaymentFunction \
  --start-time $(($(date -v-1H +%s)*1000)) \
  --end-time $(($(date +%s)*1000)) \
  --query-string 'fields @timestamp, @message
    | filter @message like /ERROR/
    | stats count() as errores by bin(5m)
    | sort errores desc'

# Recoger el resultado (repite hasta status Complete)
aws logs get-query-results --query-id $QUERY_ID

Anatomía del lenguaje (CWLI, el de pipes) ​

Toda query es un pipeline comando | comando. Con estos cinco verbos resuelves casi todo:

VerboPara quéEjemplo
fieldsElegir columnas (@timestamp, @message y @type siempre existen)fields @timestamp, @message
filterQuedarte con eventos (like para texto, =/>/< para campos)filter @message like /ERROR/
statsAgregar (count(), avg(), max(), pct(campo, 99), count_distinct())stats count() by bin(5m)
sort / limitOrdenar y recortarsort @timestamp desc | limit 20
parseExtraer campos de texto no estructuradoparse @message "paymentId=*" as paymentId

Notas que ahorran disgustos:

  • En logs JSON (Lambda con LogFormat=JSON) cada clave es un campo filtrable: filter level = "error", filter status >= 500.
  • Las líneas REPORT de Lambda traen @type = "REPORT" con @duration, @billedDuration, @memorySize, @maxMemoryUsed: filtra por @type para no mezclarlas con tus logs.
  • Con --log-group-names (plural) consultas varios grupos a la vez, p. ej. ambas Lambdas del flujo.
  • --limit del CLI recorta filas devueltas, no eventos escaneados: el costo lo manda el rango de tiempo y los grupos.
bash
# Lentas y glotonas: top 20 invocaciones REPORT por duración
aws logs start-query \
  --log-group-name /aws/lambda/ProcessPaymentFunction \
  --start-time $(($(date -v-1H +%s)*1000)) \
  --end-time $(($(date +%s)*1000)) \
  --query-string 'filter @type = "REPORT"
    | fields @requestId, @duration, @billedDuration, @maxMemoryUsed
    | sort @duration desc
    | limit 20'

# API Gateway: 5xx y p99 de latencia por ruta (access logs en JSON)
aws logs start-query \
  --log-group-name /aws/apigateway/pedidos \
  --start-time $(($(date -v-1H +%s)*1000)) \
  --end-time $(($(date +%s)*1000)) \
  --query-string 'filter status >= 500
    | stats count() as errores, pct(latency, 99) as p99 by path
    | sort errores desc'

# Step Functions: fallos de ejecución con su ARN (ambas Lambdas a la vez)
aws logs start-query \
  --log-group-names /aws/lambda/ValidateOrderFunction /aws/lambda/ProcessPaymentFunction \
  --start-time $(($(date -v-1H +%s)*1000)) \
  --end-time $(($(date +%s)*1000)) \
  --query-string 'filter @message like /ValidationError|PaymentError/
    | stats count() as fallos by @logStream
    | sort fallos desc'

Retención y ahorro ​

bash
# 30 días para app, 7 para alto volumen; revisa lo heredado sin política
aws logs put-retention-policy \
  --log-group-name /aws/lambda/ProcessPaymentFunction \
  --retention-in-days 30

aws logs describe-log-groups \
  --query 'logGroups[?retentionInDays==`null`].[logGroupName]' --output text

Métricas: lo justo para operar serverless ​

bash
# Lambda: errores e invocaciones de los últimos 30 min (puntos de 5 min)
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Errors \
  --dimensions Name=FunctionName,Value=ProcessPaymentFunction \
  --start-time $(date -u -v-30M +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 300 --statistics Sum

# Step Functions: ejecuciones fallidas por state machine
aws cloudwatch get-metric-statistics \
  --namespace AWS/States \
  --metric-name ExecutionsFailed \
  --dimensions Name=StateMachineArn,Value=$SM_ARN \
  --start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 300 --statistics Sum

# SQS: mensajes visibles (backlog) y edad del más viejo
aws cloudwatch get-metric-statistics \
  --namespace AWS/SQS \
  --metric-name ApproximateNumberOfMessagesVisible \
  --dimensions Name=QueueName,Value=pedidos \
  --start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 300 --statistics Average Maximum

# API Gateway: conteo y 5xx por API y stage
aws cloudwatch get-metric-statistics \
  --namespace AWS/ApiGateway \
  --metric-name 5XXError \
  --dimensions Name=ApiName,Value=pedidos-api Name=Stage,Value=prod \
  --start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 300 --statistics Sum

Métricas que importan por servicio: Lambda (Errors, Throttles, Duration, ConcurrentExecutions), Step Functions (ExecutionsFailed, ExecutionsTimedOut, ExecutionTime), SQS (ApproximateNumberOfMessagesVisible, ApproximateAgeOfOldestMessage, NumberOfMessagesDeadLettered vía redrive), API Gateway (Count, 4XXError, 5XXError, Latency, IntegrationLatency).

Alarmas mínimas por servicio ​

bash
export SNS_ARN=arn:aws:sns:$AWS_REGION:123456789012:ops-alerts

# Lambda con errores
aws cloudwatch put-metric-alarm \
  --alarm-name lambda-ProcessPayment-errors \
  --metric-name Errors --namespace AWS/Lambda --statistic Sum \
  --dimensions Name=FunctionName,Value=ProcessPaymentFunction \
  --period 60 --evaluation-periods 1 \
  --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions $SNS_ARN

# State machine con fallos
aws cloudwatch put-metric-alarm \
  --alarm-name sfn-OrderProcessing-failed \
  --metric-name ExecutionsFailed --namespace AWS/States --statistic Sum \
  --dimensions Name=StateMachineArn,Value=$SM_ARN \
  --period 300 --evaluation-periods 1 \
  --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions $SNS_ARN

# Cola con backlog creciendo (3 periodos de 5 min por encima de 100)
aws cloudwatch put-metric-alarm \
  --alarm-name sqs-pedidos-backlog \
  --metric-name ApproximateNumberOfMessagesVisible --namespace AWS/SQS --statistic Average \
  --dimensions Name=QueueName,Value=pedidos \
  --period 300 --evaluation-periods 3 \
  --threshold 100 --comparison-operator GreaterThanThreshold \
  --alarm-actions $SNS_ARN

# Ver estado de todas las alarmas
aws cloudwatch describe-alarms \
  --query 'MetricAlarms[*].[AlarmName,StateValue]' --output table

Activar el logging donde no es automático ​

bash
# Lambda: logs en JSON (queryables en Insights) en vez de texto plano
aws lambda update-function-configuration \
  --function-name ProcessPaymentFunction \
  --logging-config '{"LogFormat":"JSON","ApplicationLogLevel":"INFO","SystemLogLevel":"WARN"}'

# Step Functions: loguear todo con datos de ejecución a un grupo propio
aws logs create-log-group --log-group-name /aws/states/OrderProcessingStateMachine
aws stepfunctions update-state-machine \
  --state-machine-arn $SM_ARN \
  --logging-configuration '{"level":"ALL","includeExecutionData":true,
    "destinations":[{"cloudWatchLogsLogGroup":{"logGroupArn":"arn:aws:logs:'$AWS_REGION':123456789012:log-group:/aws/states/OrderProcessingStateMachine:*"}}]}'

# API Gateway REST: execution logs del stage (ERROR + info) + access logs
aws apigateway update-stage \
  --rest-api-id $API_ID --stage-name prod \
  --patch-operations op=replace,path='/*/*/logging/loglevel',value=INFO \
                      op=replace,path='/*/*/logging/dataTrace',value=true

# API Gateway HTTP (v2): access logs con variables $context a un grupo
aws apigatewayv2 update-stage \
  --api-id $HTTP_API_ID --stage-name prod --auto-deploy \
  --access-log-settings '{"DestinationArn":"arn:aws:logs:'$AWS_REGION':123456789012:log-group:/aws/apigateway/pedidos:*",
    "Format":"{\"requestId\":\"$context.requestId\",\"status\":$context.status,\"latency\":$context.responseLatency}"}'

De log a métrica (metric filters) ​

bash
# Contar "PaymentError" en la Lambda como métrica propia OrderApp/PaymentErrorCount
aws logs put-metric-filter \
  --log-group-name /aws/lambda/ProcessPaymentFunction \
  --filter-name PaymentErrors \
  --filter-pattern '"PaymentError"' \
  --metric-transformations metricName=PaymentErrorCount,metricNamespace=OrderApp,metricValue=1,defaultValue=0

Con la métrica creada, alármala como cualquier otra. Y si el log debe salir de CloudWatch (Elasticsearch, S3, otra Lambda), el camino es put-subscription-filter.

Buenas prácticas para continuar con CloudWatch ​

  • Retención desde el día uno: put-retention-policy en cada grupo que crees; audita los null cada mes
  • JSON en Lambda: LogFormat=JSON convierte cada console.log en campos filtrables
  • Step Functions sin logging es ciega: actívalo con includeExecutionData en no-prod al menos
  • SQS no tiene logs: vigílalo con backlog + edad del mensaje + alarmas, y traza la API con CloudTrail
  • Alarmas con treat-missing-data consciente: en bajo tráfico, notBreaching evita falsos OK; en crítico, breaching despierta
  • Insights antes que tail: ante un pico, agrega (stats by bin()) antes de leer líneas sueltas
  • Costos: ingestion + retención + Insights escaneado; filtra ruido en origen (niveles INFO/WARN reales)

CloudWatch es fundamental para operar Serverless: los logs cuentan qué pasó en cada Lambda, ejecución y petición; las métricas lo convierten en números accionables por servicio; y cuatro alarmas bien puestas (errores, fallos, backlog, 5xx) cubren el 90 % de los incidentes antes de que escalen.