Introduction
We have multiple CloudFront distributions deployed in our AWS account, and we want to monitor the requests and
responses. Most of the distributions are running in the free tier except the critical production ones. CloudFront
distributions using a flat-rate free plan offer no request logs to understand security and delivery traffic patterns.
This brings us to our solution: using native CloudFront Functions to log the requests and responses to CloudWatch Logs.
CloudFront Settings
In order to make sure that the requests contain the correct headers that can be logged, we need to set Origin request
policy to AllViewerExceptHostHeader. This policy forwards all viewer request headers, cookies, and query
strings to your origin server, except for the Host header.
The AllViewerExceptHostHeader policy is intended for use with Amazon API Gateway and AWS Lambda function URLs. It
usually breaks Application Load Balancers (ALBs) and is unnecessary for Amazon S3. Since we are using S3 as our origin,
we can safely set this policy to capture all viewer request headers.
CloudFront Function for Request and Response Logging
We will now create the request logging function. We are using CloudFront Functions JavaScript 2.0 runtime (
cloudfront-js-2.0):
1
2
3
4
5
6
7
8
9
| function handler(event) {
var request = event.request;
// Log the entire request object as a JSON string to CloudWatch
console.log(JSON.stringify(request));
// Always return the request object
return request;
}
|
The above function will log the entire request object to CloudWatch Logs and has to be attached to the viewer request
event type. Here is the code for the viewer response event:
1
2
3
4
5
6
7
8
9
| function handler(event) {
var response = event.response;
// Log the entire response object (Status code, headers, etc.) as a JSON string to CloudWatch
console.log(JSON.stringify(response));
// Always return the response object to pass it back to the client browser
return response;
}
|
By attaching these functions to the viewer request and viewer response events, we can log the entire request and
response objects to CloudWatch Logs. Here is a sample request logged object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
| {
"request": {
"method": "GET",
"uri": "/tags/container/index.html",
"querystring": {},
"headers": {
"user-agent": {
"value": "Mozilla/5.0 (Linux; Android 7.0;) AppleWebKit/537.36 (HTML, like Gecko) Mobile Safari/537.36"
},
"cloudfront-viewer-http-version": {
"value": "1.1"
},
"cloudfront-is-tablet-viewer": {
"value": "false"
},
"cloudfront-is-smarttv-viewer": {
"value": "false"
},
"cloudfront-viewer-asn": {
"value": "136907"
},
"cloudfront-is-android-viewer": {
"value": "true"
},
"referer": {
"value": "https://habibiops.com/p/terraform-container-image-for-cicd-pipelines/"
},
"cloudfront-viewer-tls": {
"value": "TLSv1.3:TLS_AES_128_GCM_SHA256:fullHandshake"
},
"accept": {
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
},
"host": {
"value": "habibiops.com"
},
"cloudfront-viewer-country": {
"value": "SG"
},
"cloudfront-viewer-city": {
"value": "Singapore"
},
"cloudfront-is-mobile-viewer": {
"value": "true"
},
"cloudfront-viewer-time-zone": {
"value": "Asia/Singapore"
},
"cloudfront-forwarded-proto": {
"value": "https"
},
"cloudfront-viewer-address": {
"value": "114.119.<redacted>.<redacted>:29579"
},
"accept-language": {
"value": "en"
},
"cloudfront-is-desktop-viewer": {
"value": "false"
},
"cloudfront-viewer-postal-code": {
"value": "17"
},
"accept-encoding": {
"value": "gzip, deflate, br, zstd"
},
"cloudfront-viewer-longitude": {
"value": "103.85070"
},
"cloudfront-is-ios-viewer": {
"value": "false"
},
"cloudfront-viewer-country-name": {
"value": "Singapore"
},
"cloudfront-viewer-latitude": {
"value": "1.28720"
}
},
"cookies": {}
}
}
|
Here is a sample response logged object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| {
"response": {
"statusDescription": "OK",
"statusCode": 200,
"headers": {
"server": {
"value": "AmazonS3"
},
"transfer-encoding": {
"value": "chunked"
},
"x-frame-options": {
"value": "SAMEORIGIN"
},
"x-content-type-options": {
"value": "nosniff"
},
"date": {
"value": "Sun, 11 Oct 2026 14:30:56 GMT"
},
"content-encoding": {
"value": "br"
},
"etag": {
"value": "W/\"<redacted>\""
},
"last-modified": {
"value": "Wed, 29 Jul 2026 12:22:56 GMT"
},
"x-xss-protection": {
"value": "1; mode=block"
},
"vary": {
"value": "Accept-Encoding"
},
"content-type": {
"value": "text/html"
},
"x-amz-server-side-encryption": {
"value": "AES256"
},
"via": {
"value": "1.1 <redacted>.cloudfront.net (CloudFront)"
},
"referrer-policy": {
"value": "strict-origin-when-cross-origin"
},
"strict-transport-security": {
"value": "max-age=31536000"
}
},
"cookies": {}
}
}
|
To correlate the requests and responses to each other, we can also log the request ID in both functions:
event.context.requestId.
The CloudFront Function CloudWatch log group will be created automatically inside the N. Virginia (us-east-1) region.
They will be named as follows: /aws/cloudfront/function/<FUNCTION_NAME>. The log group will have a “never expire”
retention policy. Be sure to either export the logs somewhere else or delete them after processing.
Request Headers and Query Parameters
The CloudFront event structure contains a lot of information about the request, including the request headers,
query parameters, and cookies. We can get some insights from the following fields:
uri: determine the URI.user-agent inside the headers object: determine the type of device and browser or if it is a bot.cloudfront-is-tablet-viewer, cloudfront-is-android-viewer, cloudfront-is-mobile-viewer, or cloudfront-is-desktop-viewer: determine the type of device.referer: determine the referrer of the request.cloudfront-viewer-country: determine the country of the viewer.cloudfront-viewer-city: determine the city of the viewer.cloudfront-viewer-address: determine the ip address of the viewer.cloudfront-viewer-country-name: determine the country name of the viewer.
Conclusion
We saw how to use CloudFront Functions to log the requests and responses to CloudWatch Logs. This can be useful for
monitoring and debugging when using a free tier CloudFront distribution that does not support enabling access logs. We
will see in the coming posts how to use Grafana to visualize these logs with a nice dashboard.
References: