1515logger = logging .getLogger ()
1616logger .setLevel (logging .INFO )
1717
18+
1819def lambda_handler (event , context ):
1920 """
2021 Main Lambda handler function
@@ -24,36 +25,38 @@ def lambda_handler(event, context):
2425 s3_client = boto3 .client ('s3' )
2526 ce_client = boto3 .client ('ce' )
2627 secrets_client = boto3 .client ('secretsmanager' )
27-
28+
2829 # Get environment variables
2930 bucket_name = os .environ ['BUCKET_NAME' ]
3031 cost_threshold = float (os .environ .get ('COST_THRESHOLD' , 50.0 ))
3132 slack_secret_name = os .environ ['SLACK_SECRET_NAME' ]
3233 environment = os .environ .get ('ENVIRONMENT' , 'dev' )
33-
34+
3435 logger .info (f"Starting cost collection for environment: { environment } " )
3536 logger .info (f"Cost threshold: ${ cost_threshold } " )
36-
37+
3738 # Get cost data from Cost Explorer
3839 cost_data = get_cost_data (ce_client )
39-
40+
4041 # Store cost data in S3
4142 s3_key = store_cost_data (s3_client , bucket_name , cost_data )
4243 logger .info (f"Cost data stored in S3: { s3_key } " )
43-
44+
4445 # Process cost data and check threshold
4546 cost_summary = process_cost_data (cost_data )
4647 logger .info (f"Total daily cost: ${ cost_summary ['total_cost' ]:.2f} " )
47-
48+
4849 # Send Slack alert if threshold exceeded
4950 if cost_summary ['total_cost' ] > cost_threshold :
5051 slack_webhook_url = get_slack_webhook (secrets_client , slack_secret_name )
5152 send_slack_alert (slack_webhook_url , cost_summary , cost_threshold , environment )
5253 alert_sent = True
5354 else :
5455 alert_sent = False
55- logger .info (f"Cost ${ cost_summary ['total_cost' ]:.2f} within threshold ${ cost_threshold :.2f} " )
56-
56+ logger .info (
57+ f"Cost ${ cost_summary ['total_cost' ]:.2f} within threshold ${ cost_threshold :.2f} "
58+ )
59+
5760 return {
5861 'statusCode' : 200 ,
5962 'body' : json .dumps ({
@@ -64,7 +67,7 @@ def lambda_handler(event, context):
6467 's3_key' : s3_key
6568 })
6669 }
67-
70+
6871 except Exception as e :
6972 logger .error (f"Error in cost collection: { str (e )} " )
7073 return {
@@ -74,6 +77,7 @@ def lambda_handler(event, context):
7477 })
7578 }
7679
80+
7781def get_cost_data (ce_client ) -> Dict :
7882 """
7983 Fetch cost data from AWS Cost Explorer
@@ -82,9 +86,9 @@ def get_cost_data(ce_client) -> Dict:
8286 today = datetime .date .today ()
8387 start_date = (today - datetime .timedelta (days = 1 )).strftime ('%Y-%m-%d' )
8488 end_date = today .strftime ('%Y-%m-%d' )
85-
89+
8690 logger .info (f"Fetching cost data for period: { start_date } to { end_date } " )
87-
91+
8892 try :
8993 response = ce_client .get_cost_and_usage (
9094 TimePeriod = {
@@ -100,69 +104,72 @@ def get_cost_data(ce_client) -> Dict:
100104 }
101105 ]
102106 )
103-
107+
104108 return response
105-
109+
106110 except Exception as e :
107111 logger .error (f"Error fetching cost data: { str (e )} " )
108112 raise
109113
114+
110115def store_cost_data (s3_client , bucket_name : str , cost_data : Dict ) -> str :
111116 """
112117 Store cost data in S3 bucket
113118 """
114119 # Generate S3 key with timestamp
115120 timestamp = datetime .datetime .now ().strftime ('%Y-%m-%d' )
116121 s3_key = f"cost_data/daily/{ timestamp } .json"
117-
122+
118123 try :
119124 s3_client .put_object (
120125 Bucket = bucket_name ,
121126 Key = s3_key ,
122127 Body = json .dumps (cost_data , indent = 2 , default = str ),
123128 ContentType = 'application/json'
124129 )
125-
130+
126131 return s3_key
127-
132+
128133 except Exception as e :
129134 logger .error (f"Error storing cost data in S3: { str (e )} " )
130135 raise
131136
137+
132138def process_cost_data (cost_data : Dict ) -> Dict :
133139 """
134140 Process cost data and extract key metrics
135141 """
136142 try :
137143 results = cost_data ['ResultsByTime' ][0 ]
138144 total_cost = float (results ['Total' ]['BlendedCost' ]['Amount' ])
139-
145+
140146 # Process service-level costs
141147 services = []
142148 for group in results .get ('Groups' , []):
143149 service_name = group ['Keys' ][0 ]
144150 service_cost = float (group ['Metrics' ]['BlendedCost' ]['Amount' ])
145-
151+
146152 if service_cost > 0 : # Only include services with actual costs
147153 services .append ({
148154 'name' : service_name ,
149155 'cost' : service_cost
150156 })
151-
157+
152158 # Sort services by cost (highest first)
153159 services .sort (key = lambda x : x ['cost' ], reverse = True )
154-
160+
155161 return {
156162 'total_cost' : total_cost ,
157163 'date' : results ['TimePeriod' ]['Start' ],
158164 'services' : services [:10 ], # Top 10 services
159165 'service_count' : len (services )
160166 }
161-
167+
162168 except Exception as e :
163169 logger .error (f"Error processing cost data: { str (e )} " )
164170 raise
165171
172+
166173def get_slack_webhook (secrets_client , secret_name : str ) -> str :
167174 """
168175 Retrieve Slack webhook URL from Secrets Manager
@@ -171,11 +178,12 @@ def get_slack_webhook(secrets_client, secret_name: str) -> str:
171178 response = secrets_client .get_secret_value (SecretId = secret_name )
172179 secret_data = json .loads (response ['SecretString' ])
173180 return secret_data ['SLACK_WEBHOOK_URL' ]
174-
181+
175182 except Exception as e :
176183 logger .error (f"Error retrieving Slack webhook: { str (e )} " )
177184 raise
178185
186+
179187def send_slack_alert (webhook_url : str , cost_summary : Dict , threshold : float , environment : str ):
180188 """
181189 Send cost alert to Slack
@@ -185,16 +193,16 @@ def send_slack_alert(webhook_url: str, cost_summary: Dict, threshold: float, env
185193 top_services = ""
186194 for i , service in enumerate (cost_summary ['services' ][:5 ], 1 ):
187195 top_services += f"{ i } . { service ['name' ]} : ${ service ['cost' ]:.2f} \n "
188-
189- # Create Slack message
196+
197+ # Create Slack message (removed emojis for professional appearance)
190198 message = {
191- "text" : f"🚨 * AWS Cost Alert - { environment .upper ()} * " ,
199+ "text" : f"AWS Cost Alert - { environment .upper ()} " ,
192200 "blocks" : [
193201 {
194202 "type" : "header" ,
195203 "text" : {
196204 "type" : "plain_text" ,
197- "text" : f"🚨 AWS Cost Alert - { environment .upper ()} "
205+ "text" : f"AWS Cost Alert - { environment .upper ()} "
198206 }
199207 },
200208 {
@@ -236,7 +244,7 @@ def send_slack_alert(webhook_url: str, cost_summary: Dict, threshold: float, env
236244 }
237245 ]
238246 }
239-
247+
240248 # Send to Slack
241249 http = urllib3 .PoolManager ()
242250 response = http .request (
@@ -245,12 +253,12 @@ def send_slack_alert(webhook_url: str, cost_summary: Dict, threshold: float, env
245253 body = json .dumps (message ),
246254 headers = {'Content-Type' : 'application/json' }
247255 )
248-
256+
249257 if response .status == 200 :
250258 logger .info ("Slack alert sent successfully" )
251259 else :
252260 logger .error (f"Failed to send Slack alert: { response .status } " )
253-
261+
254262 except Exception as e :
255263 logger .error (f"Error sending Slack alert: { str (e )} " )
256- raise
264+ raise
0 commit comments