Privacy-friendly site analytics with CloudFront logs and Athena
No tracking script, no cookie banner: query your CloudFront access logs with Athena to see what people read, for a few cents a month.
For a personal site, a JavaScript analytics tag is often overkill: it slows pages down, needs a consent banner in the EU, and gets blocked by a large share of visitors anyway. CloudFront already records every request. You just need a way to ask questions of those logs.
The setup
- Enable standard logging on the distribution, writing to a dedicated S3 bucket.
- Create an Athena table over that bucket.
- Query with SQL when you are curious.
There is nothing to run between queries, so the cost is S3 storage plus Athena’s per-query data scanned — negligible at personal-site traffic. Add an S3 lifecycle rule so old logs expire.
The table
CloudFront standard logs are gzipped, tab-separated files with two header lines. Columns must be declared in order; trailing ones you don’t need can be left off.
CREATE EXTERNAL TABLE IF NOT EXISTS cloudfront_logs (
`date` DATE,
`time` STRING,
location STRING,
bytes BIGINT,
request_ip STRING,
method STRING,
host STRING,
uri STRING,
status INT,
referrer STRING,
user_agent STRING,
query_string STRING,
cookie STRING,
result_type STRING,
request_id STRING,
host_header STRING,
request_protocol STRING,
request_bytes BIGINT,
time_taken FLOAT
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LOCATION 's3://my-cloudfront-logs/site/'
TBLPROPERTIES ('skip.header.line.count' = '2');
Questions worth asking
Most-read articles over the last 30 days, ignoring assets and bots:
SELECT uri, count(*) AS views
FROM cloudfront_logs
WHERE "date" >= current_date - INTERVAL '30' DAY
AND status = 200
AND uri LIKE '/blog/%'
AND uri NOT LIKE '%.%'
AND lower(user_agent) NOT LIKE '%bot%'
GROUP BY uri
ORDER BY views DESC
LIMIT 20;
Swap the GROUP BY for referrer to see where readers come from, or for result_type to check your cache hit ratio.
Trade-offs
- It counts requests, not people. That’s fine for “what do readers care about?”.
- Logs land with a delay, so this is not real-time.
- IP addresses are personal data under GDPR: keep retention short and restrict who can query the bucket.
For a blog, that’s a fair deal: no script, no cookies, full ownership of the data.