Skip to content

Commit 19639ae

Browse files
committed
Add Athena and CloudFront dashboards
1 parent 73fcf80 commit 19639ae

2 files changed

Lines changed: 214 additions & 0 deletions

File tree

infra/prod/base.cfn.yml

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,115 @@ Resources:
241241
field.delim: "\t"
242242
serialization.format: "\t"
243243

244+
# --- Athena Named Queries ---
245+
246+
PopularPagesQuery:
247+
Type: AWS::Athena::NamedQuery
248+
Properties:
249+
Name: !Sub "${AWS::StackName}-popular-pages"
250+
Description: Top 25 pages by request count
251+
WorkGroup: !Ref AthenaWorkGroup
252+
Database: !Ref AthenaDatabase
253+
QueryString: !Sub |
254+
SELECT uri_stem, COUNT(*) as requests
255+
FROM ${AthenaDatabaseName}.cloudfront_access_logs
256+
GROUP BY uri_stem
257+
ORDER BY requests DESC
258+
LIMIT 25
259+
260+
ErrorAnalysisQuery:
261+
Type: AWS::Athena::NamedQuery
262+
Properties:
263+
Name: !Sub "${AWS::StackName}-error-analysis"
264+
Description: Requests grouped by status code
265+
WorkGroup: !Ref AthenaWorkGroup
266+
Database: !Ref AthenaDatabase
267+
QueryString: !Sub |
268+
SELECT status_code, COUNT(*) as requests
269+
FROM ${AthenaDatabaseName}.cloudfront_access_logs
270+
GROUP BY status_code
271+
ORDER BY requests DESC
272+
273+
NotFoundQuery:
274+
Type: AWS::Athena::NamedQuery
275+
Properties:
276+
Name: !Sub "${AWS::StackName}-not-found"
277+
Description: Top 404 paths
278+
WorkGroup: !Ref AthenaWorkGroup
279+
Database: !Ref AthenaDatabase
280+
QueryString: !Sub |
281+
SELECT uri_stem, COUNT(*) as count
282+
FROM ${AthenaDatabaseName}.cloudfront_access_logs
283+
WHERE status_code = 404
284+
GROUP BY uri_stem
285+
ORDER BY count DESC
286+
LIMIT 25
287+
288+
AiCrawlerQuery:
289+
Type: AWS::Athena::NamedQuery
290+
Properties:
291+
Name: !Sub "${AWS::StackName}-ai-crawlers"
292+
Description: AI bot traffic (GPTBot, ClaudeBot, ByteSpider, etc.)
293+
WorkGroup: !Ref AthenaWorkGroup
294+
Database: !Ref AthenaDatabase
295+
QueryString: !Sub |
296+
SELECT url_decode(user_agent) as user_agent,
297+
COUNT(*) as requests, COUNT(DISTINCT client_ip) as unique_ips
298+
FROM ${AthenaDatabaseName}.cloudfront_access_logs
299+
WHERE LOWER(url_decode(user_agent)) LIKE '%gptbot%'
300+
OR LOWER(url_decode(user_agent)) LIKE '%claudebot%'
301+
OR LOWER(url_decode(user_agent)) LIKE '%bytespider%'
302+
OR LOWER(url_decode(user_agent)) LIKE '%ccbot%'
303+
OR LOWER(url_decode(user_agent)) LIKE '%perplexitybot%'
304+
OR LOWER(url_decode(user_agent)) LIKE '%amazonbot%'
305+
OR LOWER(url_decode(user_agent)) LIKE '%google-extended%'
306+
GROUP BY user_agent
307+
ORDER BY requests DESC
308+
309+
TrafficPerDayQuery:
310+
Type: AWS::Athena::NamedQuery
311+
Properties:
312+
Name: !Sub "${AWS::StackName}-traffic-per-day"
313+
Description: Daily request counts
314+
WorkGroup: !Ref AthenaWorkGroup
315+
Database: !Ref AthenaDatabase
316+
QueryString: !Sub |
317+
SELECT log_date, COUNT(*) as requests
318+
FROM ${AthenaDatabaseName}.cloudfront_access_logs
319+
GROUP BY log_date
320+
ORDER BY log_date DESC
321+
322+
CacheHitRateQuery:
323+
Type: AWS::Athena::NamedQuery
324+
Properties:
325+
Name: !Sub "${AWS::StackName}-cache-hit-rate"
326+
Description: Cache hit percentage by day
327+
WorkGroup: !Ref AthenaWorkGroup
328+
Database: !Ref AthenaDatabase
329+
QueryString: !Sub |
330+
SELECT log_date,
331+
COUNT(*) as total,
332+
SUM(CASE WHEN edge_result_type = 'Hit' THEN 1 ELSE 0 END) as hits,
333+
ROUND(100.0 * SUM(CASE WHEN edge_result_type = 'Hit' THEN 1 ELSE 0 END) / COUNT(*), 1) as hit_pct
334+
FROM ${AthenaDatabaseName}.cloudfront_access_logs
335+
GROUP BY log_date
336+
ORDER BY log_date DESC
337+
338+
TopReferrersQuery:
339+
Type: AWS::Athena::NamedQuery
340+
Properties:
341+
Name: !Sub "${AWS::StackName}-top-referrers"
342+
Description: Top referrers (where traffic comes from)
343+
WorkGroup: !Ref AthenaWorkGroup
344+
Database: !Ref AthenaDatabase
345+
QueryString: !Sub |
346+
SELECT url_decode(referer) as referer, COUNT(*) as requests
347+
FROM ${AthenaDatabaseName}.cloudfront_access_logs
348+
WHERE referer != '-'
349+
GROUP BY referer
350+
ORDER BY requests DESC
351+
LIMIT 25
352+
244353
Outputs:
245354
ContentBucketName:
246355
Value: !Ref ContentBucket
@@ -291,3 +400,24 @@ Outputs:
291400
AthenaDatabaseName:
292401
Description: Athena database name
293402
Value: !Ref AthenaDatabase
403+
404+
PopularPagesQueryId:
405+
Value: !Ref PopularPagesQuery
406+
407+
ErrorAnalysisQueryId:
408+
Value: !Ref ErrorAnalysisQuery
409+
410+
NotFoundQueryId:
411+
Value: !Ref NotFoundQuery
412+
413+
AiCrawlerQueryId:
414+
Value: !Ref AiCrawlerQuery
415+
416+
TrafficPerDayQueryId:
417+
Value: !Ref TrafficPerDayQuery
418+
419+
CacheHitRateQueryId:
420+
Value: !Ref CacheHitRateQuery
421+
422+
TopReferrersQueryId:
423+
Value: !Ref TopReferrersQuery

infra/prod/distribution.cfn.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,86 @@ Resources:
113113

114114
PriceClass: PriceClass_100
115115

116+
CloudFrontDashboard:
117+
Type: AWS::CloudWatch::Dashboard
118+
Properties:
119+
DashboardName: !Sub "${AWS::StackName}-cloudfront"
120+
DashboardBody: !Sub |
121+
{
122+
"widgets": [
123+
{
124+
"type": "metric",
125+
"x": 0,
126+
"y": 0,
127+
"width": 12,
128+
"height": 6,
129+
"properties": {
130+
"metrics": [
131+
["AWS/CloudFront", "Requests", "DistributionId", "${Distribution}", "Region", "Global"]
132+
],
133+
"period": 300,
134+
"stat": "Sum",
135+
"region": "us-east-1",
136+
"title": "Requests",
137+
"view": "timeSeries"
138+
}
139+
},
140+
{
141+
"type": "metric",
142+
"x": 12,
143+
"y": 0,
144+
"width": 12,
145+
"height": 6,
146+
"properties": {
147+
"metrics": [
148+
["AWS/CloudFront", "4xxErrorRate", "DistributionId", "${Distribution}", "Region", "Global"],
149+
["AWS/CloudFront", "5xxErrorRate", "DistributionId", "${Distribution}", "Region", "Global"]
150+
],
151+
"period": 300,
152+
"stat": "Average",
153+
"region": "us-east-1",
154+
"title": "Error Rates",
155+
"view": "timeSeries",
156+
"yAxis": { "left": { "min": 0 } }
157+
}
158+
},
159+
{
160+
"type": "metric",
161+
"x": 0,
162+
"y": 6,
163+
"width": 12,
164+
"height": 6,
165+
"properties": {
166+
"metrics": [
167+
["AWS/CloudFront", "BytesDownloaded", "DistributionId", "${Distribution}", "Region", "Global"]
168+
],
169+
"period": 300,
170+
"stat": "Sum",
171+
"region": "us-east-1",
172+
"title": "Bytes Downloaded",
173+
"view": "timeSeries"
174+
}
175+
},
176+
{
177+
"type": "metric",
178+
"x": 12,
179+
"y": 6,
180+
"width": 12,
181+
"height": 6,
182+
"properties": {
183+
"metrics": [
184+
["AWS/CloudFront", "TotalErrorRate", "DistributionId", "${Distribution}", "Region", "Global"]
185+
],
186+
"period": 300,
187+
"stat": "Average",
188+
"region": "us-east-1",
189+
"title": "Total Error Rate",
190+
"view": "singleValue"
191+
}
192+
}
193+
]
194+
}
195+
116196
Outputs:
117197
DistributionId:
118198
Description: Use in hugo.toml as deployment.targets.cloudFrontDistributionID
@@ -124,3 +204,7 @@ Outputs:
124204

125205
WebsiteURL:
126206
Value: !Sub "https://${Domain}"
207+
208+
DashboardName:
209+
Description: CloudWatch dashboard for CloudFront metrics
210+
Value: !Sub "${AWS::StackName}-cloudfront"

0 commit comments

Comments
 (0)