Appearance
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-queryvan 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_DATAy acciones (SNS, auto scaling, etc.)
Dónde loguea cada servicio serverless
| Servicio | Log 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 logs | No, se activa por stage |
| API Gateway HTTP (v2) | Solo access logs a un grupo elegido | No, 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/statesLogs 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 50CloudWatch 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_IDAnatomía del lenguaje (CWLI, el de pipes)
Toda query es un pipeline comando | comando. Con estos cinco verbos resuelves casi todo:
| Verbo | Para qué | Ejemplo |
|---|---|---|
fields | Elegir columnas (@timestamp, @message y @type siempre existen) | fields @timestamp, @message |
filter | Quedarte con eventos (like para texto, =/>/< para campos) | filter @message like /ERROR/ |
stats | Agregar (count(), avg(), max(), pct(campo, 99), count_distinct()) | stats count() by bin(5m) |
sort / limit | Ordenar y recortar | sort @timestamp desc | limit 20 |
parse | Extraer campos de texto no estructurado | parse @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
REPORTde Lambda traen@type = "REPORT"con@duration,@billedDuration,@memorySize,@maxMemoryUsed: filtra por@typepara no mezclarlas con tus logs. - Con
--log-group-names(plural) consultas varios grupos a la vez, p. ej. ambas Lambdas del flujo. --limitdel 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 textMé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 SumMé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 tableActivar 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=0Con 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-policyen cada grupo que crees; audita losnullcada mes - JSON en Lambda:
LogFormat=JSONconvierte cadaconsole.logen campos filtrables - Step Functions sin logging es ciega: actívalo con
includeExecutionDataen 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-dataconsciente: en bajo tráfico,notBreachingevita falsos OK; en crítico,breachingdespierta - 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/WARNreales)
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.