Writing a Node.js canary script using the Puppeteer runtime
Topics
Creating a CloudWatch Synthetics canary from scratch
Here is an example minimal Synthetics Canary script. This script passes as a
successful run, and returns a string. To see what a failing canary looks like, change let
fail = false; to let fail = true;.
You must define an entry point function for the canary script. To see how files are
uploaded to the Amazon S3 location specified as the canary's ArtifactS3Location,
create these files in the /tmp folder. All canary artifacts should
be stored in /tmp, because it's the only writable directory. Be
sure that the screenshot path is set to /tmp for any screenshots or
other files created by the script. Synthetics automatically uploads files in
/tmp to an S3 bucket.
/tmp/<name>
After the script runs, the pass/fail status and the duration metrics are published
to CloudWatch and the files under/tmp are uploaded to an S3 bucket.
const basicCustomEntryPoint = async function () { // Insert your code here // Perform multi-step pass/fail check // Log decisions made and results to /tmp // Be sure to wait for all your code paths to complete // before returning control back to Synthetics. // In that way, your canary will not finish and report success // before your code has finished executing // Throw to fail, return to succeed let fail = false; if (fail) { throw "Failed basicCanary check."; } return "Successfully completed basicCanary checks."; }; exports.handler = async () => { return await basicCustomEntryPoint(); };
Next, we'll expand the script to use Synthetics logging and make a call using the AWS SDK. For demonstration purposes, this script will create an Amazon DynamoDB client and make a call to the DynamoDB listTables API. It logs the response to the request and logs either pass or fail depending on whether the request was successful.
const log = require('@aws/synthetics-logger'); const AWS = require('aws-sdk'); // Require any dependencies that your script needs // Bundle additional files and dependencies into a .zip file with folder structure // nodejs/node_modules/additional files and foldersconst basicCustomEntryPoint = async function () { log.info("Starting DynamoDB:listTables canary."); let dynamodb = new AWS.DynamoDB(); var params = {}; let request = await dynamodb.listTables(params); try { let response = await request.promise(); log.info("listTables response: " + JSON.stringify(response)); } catch (err) { log.error("listTables error: " + JSON.stringify(err), err.stack); throw err; } return "Successfully completed DynamoDB:listTables canary."; }; exports.handler = async () => { return await basicCustomEntryPoint(); };
Packaging your Node.js canary files
For syn-nodejs-puppeteer-11.0 and above
The older packaging structure (for syn-nodejs-puppeteer-10.0 and below) is still supported in newer versions.
Create a script using one of the following options:
-
.js file (CommonJS syntax)
-
.mjs file (ES modules syntax)
For ES modules, use one of the following options:
-
.js file (CommonJS syntax)
-
.mjs file (ES modules syntax)
The package structure is defined below:
-
Root-level handler file (index.js/index.mjs)
-
Optional configuration file (synthetics.json)
-
Additional dependencies in node_modules (if needed)
Packaging structure example:
my_function/ ├── index.mjs ├── synthetics.json ├── helper-utils.mjs └── node_modules/ └── dependencies
To package, follow the steps below:
-
Install dependencies (if any).
npm install -
Create a .zip package.
zip -r my_deployment_package.zip
For syn-nodejs-puppeteer-11.0 and below
The following structure is required when using Amazon S3:
nodejs/ └── node_modules/ └── myCanaryFilename.js
To add an optional sub-folder support in syn-nodejs-puppeteer-3.4+:
nodejs/ └── node_modules/ └── myFolder/ └── myCanaryFilename.js
Note
Handler path in configuration must match your file location.
Handler name
Be sure to set your canary's script entry point (handler) as
myCanaryFilename.functionName to match the file name of your script's entry
point. If you are using a runtime earlier than syn-nodejs-puppeteer-3.4,
then functionName must be handler. If you are using
syn-nodejs-puppeteer-3.4 or later, you can choose any function name as the
handler. If you are using syn-nodejs-puppeteer-3.4 or later, you can also
optionally store the canary in a separate folder such as
nodejs/node_modules/myFolder/my_canary_filename. If you store it in a separate
folder, specify that path in your script entry point, such as
myFolder/my_canary_filename.functionName.
Changing an existing Puppeteer script to use as a Synthetics canary
This section explains how to take Puppeteer scripts and modify them to run as
Synthetics canary scripts. For more information about Puppeteer, see Puppeteer API
v1.14.0
We'll start with this example Puppeteer script:
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com'); await page.screenshot({path: 'example.png'}); await browser.close(); })();
The conversion steps are as follows:
-
Create and export a
handlerfunction. The handler is the entry point function for the script. If you are using a runtime earlier thansyn-nodejs-puppeteer-3.4, the handler function must be namedhandler. If you are usingsyn-nodejs-puppeteer-3.4or later, the function can have any name, but it must be the same name that is used in the script. Also, if you are usingsyn-nodejs-puppeteer-3.4or later, you can store your scripts under any folder and specify that folder as part of the handler name.const basicPuppeteerExample = async function () {}; exports.handler = async () => { return await basicPuppeteerExample(); }; -
Use the
Syntheticsdependency.var synthetics = require('@aws/synthetics-puppeteer'); -
Use the
Synthetics.getPagefunction to get a PuppeteerPageobject.const page = await synthetics.getPage();The page object returned by the Synthetics.getPage function has the page.on
request,responseandrequestfailedevents instrumented for logging. Synthetics also sets up HAR file generation for requests and responses on the page, and adds the canary ARN to the user-agent headers of outgoing requests on the page.
The script is now ready to be run as a Synthetics canary. Here is the updated script:
var synthetics = require('@aws/synthetics-puppeteer'); // Synthetics dependency const basicPuppeteerExample = async function () { const page = await synthetics.getPage(); // Get instrumented page from Synthetics await page.goto('https://example.com'); await page.screenshot({path: '/tmp/example.png'}); // Write screenshot to /tmp folder }; exports.handler = async () => { // Exported handler function return await basicPuppeteerExample(); };