← Back to list

Enriching RUM with CloudFront Logs

We aim to improve in-house real user monitoring (RUM) by combining client-side telemetry with CloudFront logs. CloudFront provides…

Jens · 2025-12-18 18:43 · 1 claps · 3.5 min read
#aws-cloudfront #cloudfront-functions #analytics #real-user-monitoring #user-analytics
Open on Medium ↗
Wiki topics: GRW · Growth & Analytics ☁️ · DevOps & Cloud

Enriching RUM with CloudFront Logs

We aim to improve in-house real user monitoring (RUM) by combining client-side telemetry with CloudFront logs. CloudFront provides edge-level metadata — request IDs, POP information, cache status, and timing metrics — that can be used to enrich RUM events. Using these logs allows us to reduce client payloads, improve data quality, and gain insights into performance across regions.

Our objectives are:

  1. Accuracy: Distinguish between backend latency and network delay between the user and the CloudFront edge.
  2. Efficiency: Avoid sending data that is already captured by CloudFront, such as the full URL or user agent, reducing event size and storage costs.
  3. Traceability: Correlate browser events with edge and origin logs for comprehensive analysis.

CloudFront logs provide:

x-amz-cf-id / x-edge-request-id — unique edge request identifier x-amz-cf-pop — the POP that served the request time-taken and ttfb — edge timing metrics Cache result information (x-cache) Viewer geolocation information (country, POP)

This data enables filtering of events affected by network conditions, evaluating regional performance, and reducing redundant information in RUM events.

Full list of fields: Standard logging reference

Architecture

The pipeline is structured as follows:

Browser (RUM beacon)
  → CloudFront (serves pages, injects edge ID)
    → /rum endpoint (compact RUM events)
      → Kinesis/Firehose → S3

Both RUM events and CloudFront logs are stored in S3. We join them in the data lake using the CloudFront edge request ID, enabling analysis that combines browser metrics with edge and origin data.

Data Privacy concerns

As the cloudfront logs include the ip adress and might be used as additonal input for fingerprinting we introduced a process that scrubs this data always before storing it. This was already in place but it needs to be considered. It is probably also advisable to ensure that a later merging with raw logs is not possible. A resonable approach would be to replace the original id with something like a HMAC with a rotating key early in the processing besides other measures like deleting etc.

Capturing the CloudFront edge-request-id

To reliably correlate browser RUM with CloudFront access logs we need a key to join on. CloudFront emits an opaque per-request identifier — visible in access logs as x-edge-request-id and sent in responses as the x-amz-cf-id header — which is perfect for this job. However, the RUM script can’t directly read server response headers for the initial HTML document. We expose that per-request id to the page via a tiny CloudFront Function.

The function executes at the Viewer Response stage and sets a JS-readable session cookie pageCfId containing the CloudFront request id. Because CloudFront runs the viewer response function per-request — even for cached content — the cookie is accessible for our tracking solution.

Implementation

The implementation first exits early if the response is not HTML.

    const ctHeader = headers['content-type'];
    if (!ctHeader || !ctHeader[0].value.includes('text/html')) {
        return response;
    }

If it is an HTML response, the function reads the CloudFront request ID from the x-amz-cf-id header or exists if not set.

    const cfIdHeader = headers['x-amz-cf-id'];
    if (!cfIdHeader || !cfIdHeader[0].value) {
        return response;
    }
    const cfId = cfIdHeader[0].value;

As last we set the cookie header with the acquired id. The cookie is set with SameSite=Lax rather than Strict. While Strict provides stronger isolation, it would prevent the cookie from being sent on top-level navigations originating from external sites (e.g. search engines or links), breaking RUM–CloudFront correlation for first-visit traffic. Lax preserves correct behavior while still preventing third-party access. The ttl of the cookie is 0, the next request will get a new id.

  response.headers['set-cookie'] = [{
    key: 'Set-Cookie',
    value: [
      `pageCfId=${cfId}`,
      'Path=/',
      'HttpOnly=false', // JS-readable
      'Secure', // HTTPS only
      'SameSite=Lax', // Prevent CSRF from other sites
      'Max-Age=0' // Session-only, expires immediately
    ].join('; ')
  }];

The full implementation can be found here: inject-cf-id.js

Deploying the CloudFront Function

Setting up the CloudFront Function is straightforward:

  1. Open your CloudFront distribution in the AWS Console and navigate to Functions.
  2. Create a new function and paste in the function code.
  3. Publish the function so it’s available for use.
  4. Attach the function using the Viewer Response event.

Viewer Response functions execute after CloudFront generates the response, including cached content, and are the only type of function that can modify response headers, which is necessary to set a cookie.

Once attached, the function takes effect globally after the distribution finishes deploying. From that point on, every HTML page served by CloudFront will include the pageCfId cookie, ready for your RUM scripts to capture.

Frontend implementation

The frontend is now straight forward. The global var document.cookie contains all cookies accessible us. Just grab that and extract the id.

function getCfId() {
    const match = document.cookie
        .split('; ')
        .find(row => row.startsWith('pageCfId='));
    return match ? match.split('=')[1] : null;
}

console.log(getCfId()); // current request CF ID

Limitations

Using the cloudwatch logs is a cost effective solution if (near) real time data is not needed. This approach works also in realtime use cases when used with with real-time access logs which provides the logs as Amazon Kinesis Data Streams.

Thanks for your attention, you earned yourself a unicorn

Photo by James Lee on Unsplash

Photo by James Lee on Unsplash


메타데이터
post_id
bc01849e758f
slug
enriching-in-house-rum-with-cloudfront-logs-bc01849e758f
url
https://medium.com/@jens_93466/enriching-in-house-rum-with-cloudfront-logs-bc01849e758f
canonical_url
https://medium.com/@jens_93466/enriching-in-house-rum-with-cloudfront-logs-bc01849e758f
author_url
https://medium.com/@jens_93466
status
ok
fetched_at
2026-07-10 00:10:16