Project2018Internal

Console Analytics

A lightweight frontend telemetry tracker built for the AWS Management Console.

The story

When I joined the AWS Console team, we had a visibility problem. Millions of developers used the console daily, but we were flying blind — no reliable data on which features resonated, where users got stuck, or how changes affected real workflows.

The existing options didn't fit. Third-party trackers raised security concerns for enterprise customers. Server-side logging missed the nuance of frontend interactions. And anything we shipped had to respect strict performance budgets — the console couldn't slow down for analytics.

Before writing anything, I read the source of Snowplow's JavaScript tracker and studied Piwik's (now Matomo) codebase. Both were serious pieces of software, and both carried features we didn't need and overhead we couldn't afford in the console.

So I built a small TypeScript tracker of our own. It collected data passively through the MutationObserver and PerformanceObserver browser APIs, and it throttled outgoing events with a token-bucket utility — keyed per event, so no single event type could overwhelm the system. That throttling piece, aws-event-bucket-throttle, was open-sourced.

The tracker itself stayed internal to AWS, so I can't link the code. What I can show is the part of the design I still like most: event paths a human can read without a lookup table.

How it works

The piece that made the data readable was xpath-util, a small utility that generated selectors for tracked elements. It supported standard XPath generation, but its better trick was a custom path format driven by two data attributes, data-analytics anddata-analytics-type. Tagging an element looked like this:

example.html
1<div
2 data-analytics="ec2"
3 data-analytics-type="service"
4>

When an event fired, the tracker walked up the DOM from the target and assembled a path from every tagged ancestor. Consider a nested structure like this:

complex-example.html
1<div id="main-content">
2 <div class="service-region" data-analytics="us-west-2" data-analytics-type="region">
3 <div class="service-group" data-analytics="compute" data-analytics-type="category">
4 <div class="service-container">
5 <div
6 class="service-card"
7 data-analytics="ec2"
8 data-analytics-type="service"
9 >
10 <div class="action-container">
11 <button
12 class="launch-instance"
13 data-analytics="launch"
14 data-analytics-type="action"
15 >
16 Launch Instance
17 </button>
18 </div>
19 </div>
20 </div>
21 </div>
22 </div>
23</div>

A traditional XPath to that button is brittle and hard to read:

traditional-xpath.txt
1//*[@id="main-content"]/div[@class="service-region"]/div[@class="service-group"]/div[@class="service-container"]/div[@class="service-card"]/div[@class="action-container"]/button[@class="launch-instance"]

xpath-util produced this instead — two ways to represent the same interaction:

xpath-util-paths.txt
1# Full path showing hierarchy
2/region(us-west-2)/category(compute)/service(ec2)/action(launch)
3
4# Direct path to action with context
5/action(launch)

Clicking the "Launch Instance" button enriched the event with every analytics attribute found on the way up the tree:

event-context.json
1{
2 "event": "click",
3 "target": {
4 "action": "launch",
5 "service": "ec2",
6 "category": "compute",
7 "region": "us-west-2"
8 },
9 "path": "/region(us-west-2)/category(compute)/service(ec2)/action(launch)",
10 "timestamp": "2018-06-14T10:30:45.123Z",
11 "elementType": "button",
12 "metadata": {
13 "text": "Launch Instance",
14 "visible": true,
15 "position": {
16 "x": 150,
17 "y": 200
18 }
19 }
20}

That enrichment bought us a few things. Every event carried its full hierarchical context, so analysts could aggregate at any level — region, category, service, or action. And because the paths came from data attributes rather than DOM structure, refactoring the markup didn't break the analytics as long as the attributes survived.

Demo

Ambient illustration — sample dashboard, not real Console data
Analytics Overview
Page Views
1234
Active Users
156
Bounce Rate %
42
Avg. Session (s)
321
The numbers are generated in the browser on a timer; nothing here comes from the actual tracker.

Artifacts & further reading

  1. the throttling utility, open-sourced