🆕 Video Course Out Now: Rapid Docker on AWS

End-user monitoring of your website with CloudWatch Synthetics

Michael Wittig – 04 Jun 2020

There are countless reasons why your website is not working as your users expect. From a technical point of view, you can monitor your load balancers, your web servers, and your database. But what if that external script that you embed is breaking your site? Expired TLS certificate? Something wrong with DNS? How can you test that your website works for real users?

End-user perspective

Do you prefer listening to a podcast episode over reading a blog post? Here you go!

Browsers can be used in an automated way controlled by a script. Wait for an element to become visible. Click on a link. Enter a form field. puppeteer allows you to remote control a headless Chrome browser in Node.js and is maintained by Google. We can monitor the user experience on our website if we can find a way to run a puppeteer script at regular intervals and record the results.

And that’s where Amazon CloudWatch Synthetics enters the stage. It allows you to create canaries to execute puppeteer scripts on a schedule. Each run creates detailed logs, screenshots, and a record of all network calls in HAR) format uploaded to S3. All you need to do is provide a script that performs the test, or use one of the blueprints. The following Node.js script opens https://marbot.io, waits for an <h1> HTML element, checks the title and status code, and performs a screenshot.

const synthetics = require('Synthetics'); // CloudWatch Synthetics lib
exports.handler = async () => {
const page = await synthetics.getPage();
const response = await page.goto('https://marbot.io', {
waitUntil: 'domcontentloaded',
timeout: 30000
});
try {
await page.waitFor('h1', {timeout: 15000}); // <h1> element expected
const title = await page.title();
if (!title.includes('marbot')) { // title must contain marbot
throw new Error('title not as expected');
}
if (response.status() !== 200) { // 200 status code expected
throw(new Error('Failed to load page!'));
}
} finally {
await synthetics.takeScreenshot('loaded', 'result'); // always create a screenshot
}
};

The rest is taken care of by CloudWatch Synthetics and is presented like this:

CloudWatch Synthetics canary run

Free Monitoring Checklist + Mind Map

Find the blind spots in your AWS monitoring!

Setting up monitoring on AWS is hard. AWS provides countless features and sources of events. Overlooking the important settings is easy. Our prioritized checklist includes all parts of a basic monitoring setup for AWS. Additionally, use our mind map to map your monitoring goals to AWS services. Download Free Monitoring Checklist + Mind Map!

Want to get notified about failed canary runs? Create a CloudWatch Alarm that watches the metrics of the canary to alert you if things go wrong. I created a CloudFormation template to help you with the setup (don’t forget to resolve the TODOs in the template!).

---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudWatch Synthetics website monitoring'
Resources:
Topic:
Type: 'AWS::SNS::Topic'
Properties:
Subscription:
- Endpoint: 'mail@site.com'
Protocol: email
TopicPolicy:
Type: 'AWS::SNS::TopicPolicy'
Properties:
PolicyDocument:
Id: Id1
Version: '2012-10-17'
Statement:
- Sid: Sid1
Effect: Allow
Principal:
AWS: '*' # Allow CloudWatch Alarms
Action: 'sns:Publish'
Resource: !Ref Topic
Condition:
StringEquals:
'AWS:SourceOwner': !Ref 'AWS::AccountId'
Topics:
- !Ref Topic
CanaryBucket:
Type: 'AWS::S3::Bucket'
Properties: {}
CanaryRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: 'lambda.amazonaws.com'
Action: 'sts:AssumeRole'
Policies:
- PolicyName: execution
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: 's3:ListAllMyBuckets'
Resource: '*'
- Effect: Allow
Action: 's3:PutObject'
Resource: !Sub '${CanaryBucket.Arn}/*'
- Effect: Allow
Action: 's3:GetBucketLocation'
Resource: !GetAtt 'CanaryBucket.Arn'
- Effect: Allow
Action: 'cloudwatch:PutMetricData'
Resource: '*'
Condition:
StringEquals:
'cloudwatch:namespace': CloudWatchSynthetics
CanaryLogGroup:
Type: 'AWS::Logs::LogGroup'
Properties:
LogGroupName: !Sub '/aws/lambda/cwsyn-${Canary}-${Canary.Id}'
RetentionInDays: 14
CanaryPolicy:
Type: 'AWS::IAM::Policy'
Properties:
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: !GetAtt 'CanaryLogGroup.Arn'
PolicyName: logs
Roles:
- !Ref CanaryRole
Canary:
Type: 'AWS::Synthetics::Canary'
Properties:
ArtifactS3Location: !Sub 's3://${CanaryBucket}'
Code:
Handler: 'index.handler'
Script: |
const synthetics = require('Synthetics');
const log = require('SyntheticsLogger');
exports.handler = async () => {
const page = await synthetics.getPage();
const response = await page.goto('https://site.com', {waitUntil: 'domcontentloaded', timeout: 30000}); // TODO replace with your URL
try {
await page.waitFor('h1', {timeout: 15000}); // TODO replace with an HTML element to look for
const title = await page.title();
if (!title.includes('REPLACE')) { // TODO replace with your important word in the title
throw new Error('title not as expected!');
}
if (response.status() !== 200) {
throw(new Error('Failed to load page!'));
}
} finally {
await synthetics.takeScreenshot('loaded', 'result');
}
};
ExecutionRoleArn: !GetAtt 'CanaryRole.Arn'
FailureRetentionPeriod: 30
Name: 'site-monitoring' # TODO replace with better name
RunConfig:
TimeoutInSeconds: 60
RuntimeVersion: 'syn-1.0'
Schedule:
DurationInSeconds: '0' # run forever
Expression: 'rate(15 minutes)'
StartCanaryAfterCreation: true
SuccessRetentionPeriod: 30
SuccessPercentAlarm:
DependsOn: TopicPolicy
Type: 'AWS::CloudWatch::Alarm'
Properties:
AlarmActions:
- !Ref Topic
AlarmDescription: 'Canary is failing.'
ComparisonOperator: LessThanThreshold
Dimensions:
- Name: CanaryName
Value: !Ref Canary
EvaluationPeriods: 1
MetricName: SuccessPercent
Namespace: CloudWatchSynthetics
OKActions:
- !Ref Topic
Period: 300
Statistic: Minimum
Threshold: 90 # TODO replace or confirm threshold
TreatMissingData: notBreaching

Don’t forget to check out the CloudWatch pricing details.

I also integrated CloudWatch Synthetics into marbot. You can set up your external website from Slack and receive alerts in Slack like this:

  1. Send a message to marbot on a channel and ask him to monitor your website.
  2. Select your preferred way of interacting with AWS (Management Console, CLI).
  3. Set the monitoring goal to Synthetics website.
  4. Select your AWS region.
  5. Follow marbot to deploy a CloudFormation stack to set up CloudWatch Synthetics, CloudWatch Alarms, and much more.

CloudWatch Synthetics setup

That’s it. Your website is now monitored from an end-user perspective. If things go wrong, you will receive a message in Slack.

CloudWatch Synthetics Alarm in Slack

Are you interested in marbot? Configure AWS monitoring, receive alerts, solve incidents from Slack.

Michael Wittig

Michael Wittig

I’m an independent consultant, technical writer, and programming founder. All these activities have to do with AWS. I’m writing this blog and all other projects together with my brother Andreas.

In 2009, we joined the same company as software developers. Three years later, we were looking for a way to deploy our software—an online banking platform—in an agile way. We got excited about the possibilities in the cloud and the DevOps movement. It’s no wonder we ended up migrating the whole infrastructure of Tullius Walden Bank to AWS. This was a first in the finance industry, at least in Germany! Since 2015, we have accelerated the cloud journeys of startups, mid-sized companies, and enterprises. We have penned books like Amazon Web Services in Action and Rapid Docker on AWS, we regularly update our blog, and we are contributing to the Open Source community. Besides running a 2-headed consultancy, we are entrepreneurs building Software-as-a-Service products.

We are available for projects.

You can contact me via Email, Twitter, and LinkedIn.

Briefcase icon
Hire me