Copy
import json
import os
import urllib3
from datetime import datetime
http = urllib3.PoolManager()
def get_alarm_color(state):
"""Return color based on alarm state"""
colors = {
'ALARM': '#FF0000', # Red
'OK': '#36A64F', # Green
'INSUFFICIENT_DATA': '#FFA500' # Orange
}
return colors.get(state, '#808080') # Gray for unknown
def get_alarm_emoji(state):
"""Return emoji based on alarm state"""
emojis = {
'ALARM': '🔴',
'OK': '✅',
'INSUFFICIENT_DATA': '⚠️'
}
return emojis.get(state, '❓')
def format_metric_name(metric_name):
"""Format metric names to be more readable"""
readable_names = {
'CPUUtilization': 'CPU Utilization',
'FreeableMemory': 'Available Memory',
'FreeStorageSpace': 'Free Storage Space',
'DatabaseConnections': 'Database Connections',
'TargetResponseTime': 'Target Response Time',
'HTTPCode_Target_4XX_Count': 'HTTP 4XX Errors',
'HTTPCode_Target_5XX_Count': 'HTTP 5XX Errors',
'RejectedConnectionCount': 'Rejected Connections',
'HealthyHostCount': 'Healthy Host Count'
}
return readable_names.get(metric_name, metric_name)
def parse_alarm_name(alarm_name):
"""Parse alarm name to extract resource type and name"""
# Format: CW-RDS-<db-name>-<metric> or CW-ALB-<alb-name>-<metric> or CW-TG-<tg-name>-<metric>
parts = alarm_name.split('-')
if len(parts) >= 3:
resource_type = parts[1] # RDS, ALB, or TG
return resource_type
return 'Unknown'
def lambda_handler(event, context):
"""Main Lambda handler function"""
# Get Slack webhook URL from environment variable
slack_webhook_url = os.environ.get('SLACK_WEBHOOK_URL')
if not slack_webhook_url:
print("ERROR: SLACK_WEBHOOK_URL environment variable not set")
return {
'statusCode': 500,
'body': json.dumps('SLACK_WEBHOOK_URL not configured')
}
# Parse SNS message
try:
sns_message = json.loads(event['Records'][0]['Sns']['Message'])
print(f"Received SNS message: {json.dumps(sns_message, indent=2)}")
except Exception as e:
print(f"ERROR parsing SNS message: {str(e)}")
return {
'statusCode': 400,
'body': json.dumps(f'Error parsing SNS message: {str(e)}')
}
# Extract alarm details
alarm_name = sns_message.get('AlarmName', 'Unknown Alarm')
new_state = sns_message.get('NewStateValue', 'UNKNOWN')
old_state = sns_message.get('OldStateValue', 'UNKNOWN')
reason = sns_message.get('NewStateReason', 'No reason provided')
region = sns_message.get('Region', 'ap-south-1')
timestamp = sns_message.get('StateChangeTime', datetime.utcnow().isoformat())
# Get trigger details
trigger = sns_message.get('Trigger', {})
metric_name = trigger.get('MetricName', 'Unknown')
namespace = trigger.get('Namespace', 'Unknown')
threshold = trigger.get('Threshold', 'N/A')
comparison_operator = trigger.get('ComparisonOperator', 'N/A')
# Parse dimensions to get resource name
dimensions = trigger.get('Dimensions', [])
resource_name = 'Unknown'
for dim in dimensions:
if dim.get('name') in ['DBInstanceIdentifier', 'LoadBalancer', 'TargetGroup']:
resource_name = dim.get('value', 'Unknown')
break
# Get resource type from alarm name
resource_type = parse_alarm_name(alarm_name)
# Format metric name
readable_metric = format_metric_name(metric_name)
# Get color and emoji
color = get_alarm_color(new_state)
emoji = get_alarm_emoji(new_state)
# Build resource icon
resource_icons = {
'RDS': '🗄️',
'ALB': '⚖️',
'TG': '🎯'
}
resource_icon = resource_icons.get(resource_type, '📊')
# Create Slack message
slack_message = {
"attachments": [
{
"color": color,
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"{emoji} CloudWatch Alarm: {new_state}",
"emoji": True
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": f"*{resource_icon} Resource Type:*\n{resource_type}"
},
{
"type": "mrkdwn",
"text": f"*📛 Alarm Name:*\n`{alarm_name}`"
},
{
"type": "mrkdwn",
"text": f"*📊 Metric:*\n{readable_metric}"
},
{
"type": "mrkdwn",
"text": f"*🎯 Resource:*\n`{resource_name}`"
},
{
"type": "mrkdwn",
"text": f"*State Transition:*\n`{old_state}` → `{new_state}`"
},
{
"type": "mrkdwn",
"text": f"*🚨 Threshold:*\n`{comparison_operator} {threshold}`"
},
{
"type": "mrkdwn",
"text": f"*🌍 Region:*\n{region}"
},
{
"type": "mrkdwn",
"text": f"*🕐 Time:*\n{timestamp}"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*📝 Reason:*\n```{reason}```"
}
},
{
"type": "divider"
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": f"🔗 <https://console.aws.amazon.com/cloudwatch/home?region={region}#alarmsV2:alarm/{alarm_name}|Click to View in CloudWatch Console>"
}
]
}
]
}
]
}
# Send to Slack
try:
encoded_data = json.dumps(slack_message).encode('utf-8')
response = http.request(
'POST',
slack_webhook_url,
body=encoded_data,
headers={'Content-Type': 'application/json'}
)
print(f"Slack response status: {response.status}")
print(f"Slack response data: {response.data.decode('utf-8')}")
if response.status == 200:
return {
'statusCode': 200,
'body': json.dumps('Successfully sent alarm to Slack')
}
else:
return {
'statusCode': response.status,
'body': json.dumps(f'Failed to send to Slack: {response.data.decode("utf-8")}')
}
except Exception as e:
print(f"ERROR sending to Slack: {str(e)}")
return {
'statusCode': 500,
'body': json.dumps(f'Error sending to Slack: {str(e)}')
}
