Logs and CloudWatch for Lambda
Introduction
Logs are essential for understanding the behavior of your Lambda functions and diagnosing issues. Amazon CloudWatch Logs is the native service for collecting, storing, and analyzing these logs.
Lambda Logs Architecture
┌─────────────────────────────────────────────────────────────────┐
│ LAMBDA LOGS FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Lambda │────►│ CloudWatch │────►│ Analysis │ │
│ │ Function │ │ Logs │ │ / Alerts │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ Each invocation generates: │
│ • START RequestId │
│ • Your logs (print, console.log) │
│ • END RequestId │
│ • REPORT (duration, memory, billing) │
│ │
└─────────────────────────────────────────────────────────────────┘
Log Groups Structure
CloudWatch Logs
│
├── /aws/lambda/my-function-1
│ ├── Log Stream 2024/01/15/[$LATEST]abc123
│ ├── Log Stream 2024/01/15/[$LATEST]def456
│ └── Log Stream 2024/01/16/[$LATEST]ghi789
│
├── /aws/lambda/my-function-2
│ └── ...
│
└── /aws/lambda/my-function-3
└── ...
Key Concepts:
- Log Group: One group per Lambda function (
/aws/lambda/function-name) - Log Stream: One stream per Lambda container instance
- Log Event: Each individual log line
Lambda Logs Format
START Message
START RequestId: 12345-abcde-67890 Version: $LATEST
Custom Messages (your code)
# Python
print("Processing user:", user_id)
logger.info("Connecting to database")
logger.error("Error: file not found")
// Node.js
console.log("Processing user:", userId);
console.info("Connecting to database");
console.error("Error: file not found");
END Message
END RequestId: 12345-abcde-67890
REPORT Message (very important!)
REPORT RequestId: 12345-abcde-67890
Duration: 234.56 ms
Billed Duration: 235 ms
Memory Size: 128 MB
Max Memory Used: 67 MB
Init Duration: 145.23 ms
REPORT Analysis
┌─────────────────────────────────────────────────────────────────┐
│ REPORT ANALYSIS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Duration: 234.56 ms │
│ └── Real execution time of your code │
│ │
│ Billed Duration: 235 ms │
│ └── Billed time (rounded up to the nearest ms) │
│ │
│ Memory Size: 128 MB │
│ └── ALLOCATED memory (configured) │
│ │
│ Max Memory Used: 67 MB │
│ └── ACTUALLY used memory │
│ If close to Memory Size = risk of OOM! │
│ │
│ Init Duration: 145.23 ms (Cold Start only) │
│ └── Container initialization time │
│ │
└─────────────────────────────────────────────────────────────────┘
Logging Best Practices
1. Logging Structure (JSON)
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# Log structure to facilitate analysis
log_entry = {
"level": "INFO",
"request_id": context.aws_request_id,
"action": "process_order",
"order_id": event.get("order_id"),
"user_id": event.get("user_id")
}
logger.info(json.dumps(log_entry))
2. Log Levels
┌─────────────────────────────────────────────────────────────────┐
│ LOG LEVELS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ DEBUG → Technical details (dev only) │
│ INFO → Normal operations │
│ WARNING → Abnormal but handled situations │
│ ERROR → Errors requiring attention │
│ CRITICAL → Severe errors, function compromised │
│ │
│ Production: INFO and above │
│ Development: DEBUG and above │
│ │
└─────────────────────────────────────────────────────────────────┘
3. Essential Information to Log
def lambda_handler(event, context):
# ALWAYS log at the start
logger.info(f"START - RequestId: {context.aws_request_id}")
logger.info(f"Event: {json.dumps(event)}")
try:
# Your logic
result = process_data(event)
# Log success
logger.info(f"SUCCESS - Result: {result}")
return result
except Exception as e:
# Log error with details
logger.error(f"ERROR - Type: {type(e).__name__}")
logger.error(f"ERROR - Message: {str(e)}")
logger.error(f"ERROR - Event: {json.dumps(event)}")
raise
Searching Logs
CloudWatch Logs Insights
-- Find all errors
fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 100
-- Analyze execution durations
fields @timestamp, @duration
| filter @type = "REPORT"
| stats avg(@duration), max(@duration), min(@duration)
-- Find Cold Starts
fields @timestamp, @message
| filter @message like /Init Duration/
| sort @timestamp desc
-- Memory close to limit
fields @timestamp, @maxMemoryUsed, @memorySize
| filter @type = "REPORT"
| filter @maxMemoryUsed / @memorySize > 0.8
Pattern Filters
# Find errors
?ERROR ?Error ?error
# Find a specific RequestId
"12345-abcde-67890"
# Find timeouts
"Task timed out"
# Find OOM (Out of Memory)
"Runtime exited" "memory"
Logs Retention
┌─────────────────────────────────────────────────────────────────┐
│ LOGS RETENTION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ By default: Logs are retained INDEFINITELY │
│ └── Beware of storage costs! │
│ │
│ Retention options: │
│ • 1 day, 3 days, 5 days, 1 week │
│ • 2 weeks, 1 month, 2 months, 3 months │
│ • 6 months, 1 year, 13 months, 18 months │
│ • 2 years, 3 years, 5 years, 10 years │
│ • Never expire │
│ │
│ Recommendation: │
│ • Dev/Test: 1-7 days │
│ • Production: 30-90 days │
│ • Compliance: according to regulation │
│ │
└─────────────────────────────────────────────────────────────────┘
Important Metrics
| Metric | Description | Typical Alert Threshold |
|---|---|---|
| Invocations | Number of calls | According to baseline |
| Duration | Execution time | > 80% of timeout |
| Errors | Number of errors | > 1% of invocations |
| Throttles | Limited invocations | > 0 |
| ConcurrentExecutions | Parallel executions | > 80% of limit |
Practical Exercise
Scenario: Analyze logs of a problematic function
Observed logs:
────────────────────────────────────────────────────────
START RequestId: abc-123
Connecting to database...
REPORT Duration: 29543.21 ms Billed: 29544 ms
Memory: 128 MB Max Used: 125 MB
START RequestId: def-456
Connecting to database...
Task timed out after 30.00 seconds
START RequestId: ghi-789
Runtime exited with error: signal: killed
────────────────────────────────────────────────────────
Questions:
1. What is the main problem?
2. Which metrics are abnormal?
3. What solutions do you propose?
See analysis
Identified Problems:
- Timeout: The function takes almost 30 seconds (limit)
- Memory: 125/128 MB used (97% - critical!)
- OOM: "Runtime exited... signal: killed" = Out of Memory
Solutions:
- Increase memory (256 MB or more)
- Optimize DB connection code (connection pooling)
- Increase timeout if necessary
- Add logs to identify bottleneck
Summary
- CloudWatch Logs automatically captures all Lambda logs
- The REPORT message contains performance metrics
- Use structured logging (JSON) to facilitate analysis
- Configure retention to control costs
- CloudWatch Logs Insights allows SQL queries on logs
Next section: Validation Quiz