In Part 1 of this series we setup our development environment, created our twitter-bot
project, integrated it with the Twitter API so it could send out programmatic tweets, and performed a basic Atom
feed retrieval for some actual content to tweet. In Part 2 we integrated Airbrake's Node.js software to handle automatic error reporting, plus, we also started the AWS Lambda integration.
Today, we'll finish up this series and project by completing the integration into AWS Lambda by packaging our application code, uploading it to AWS Lambda, creating functions and handlers, testing, and eventually getting a fully automated, error-managed, serverless Twitter bot running, so let's get to it!
Packaging Our Application Code
AWS Lambda allows us to write code directly within the Lambda dashboard, but we must ensure that all dependencies that our Node.js application relies upon are included and available to AWS Lambda. Therefore, the solution is to package our entire project directory into a zip
file, which we can then upload directly to AWS Lambda. This will ensure that the node_modules
directory is included with our index.js
, along with all other application code.
Navigate to your project directory in the console and enter the following command:
$ zip -r twitter-bot.zip *
adding: airbrake-credentials.js (deflated 9%)
adding: index.js (deflated 64%)
adding: node_modules/ (stored 0%)
...
Create AWS Lambda Function
To create an AWS Lambda function, start by navigating to the AWS Lambda console and then click Create Function
.
Make sure Author from scratch
is selected, since we'll be using our own custom code and exported handler
. For this example I've named my function airbrake-article-twitter-bot
.
For the Runtime
dropdown you'll want to use the latest version of Node.js
, or the one closest to your own version. At the time of writing Node.js 6.10
is the latest supported version.
If this is your first Lambda function you'll likely need to Create a new role from template(s)
in the Role
dropdown. Then enter a descriptive Role name
, such as airbrakeArticleTwitterBotRole
. For this basic bot we don't need any advanced permissions, so leave Policy templates
blank and click Create function
.
Once created you'll be looking at the airbrake-article-twitter-bot
function dashboard, which includes the Designer
panel, where a graphical layout of your function is displayed. Below that is the Function code
screen, which includes a default handler
function. From there, most of the sections can be ignored for now.
To upload the zip
package we created simply click on the Function code > Code entry type
dropdown box and select Upload a .ZIP file
, then click Upload
and select the twitter-bot.zip
file.
Next, we need to tell AWS Lambda which particular handler
function it should invoke when executing this function, so we'll change Function code > Handler
to index.tweetHelloWorld
. The index
portion is the name of the app file (index.js
), and the second portion is the handler
function. Now click Save
at the top right and the zip
will be uploaded!
Testing a Lambda Function
Our code is uploaded and ready to go, but we need to actually tell AWS Lambda when and how to invoke our handler
function. In this case, it's easiest to start with a manual execution test.
Click the Test
button at the top right, select Create new test event
, choose Hello World
from the Event template
dropdown, then enter any Event name
you wish and click Create
. The actual parameters of the test aren't relevant to this particular example, but feel free to change them before creating your test.
Now your new test will be selected in the test dropdown, so just click the Test
button and this will invoke the AWS Lambda function, which will call the handler
function in your code (index.tweetHelloWorld
, in this case).
Once execution completes you'll see the result dialog at the top, which can be expanded for more detail. In this case, the function result shows:
"'Hello World' tweeted."
And the log output, which is automatically tracked by AWS Lambda and stored in Amazon CloudWatch Logs, shows:
START RequestId: b4ed494c-ef6e-11e7-a6f0-f73d0ba7827e Version: $LATEST
2018-01-02T03:40:43.288Z b4ed494c-ef6e-11e7-a6f0-f73d0ba7827e ---- TWEETED ----
2018-01-02T03:40:43.325Z b4ed494c-ef6e-11e7-a6f0-f73d0ba7827e { created_at: 'Tue Jan 02 03:40:43 +0000 2018',
id: 948036303739789300,
id_str: '948036303739789312',
text: 'Hello World',
...
END RequestId: b4ed494c-ef6e-11e7-a6f0-f73d0ba7827e
REPORT RequestId: b4ed494c-ef6e-11e7-a6f0-f73d0ba7827e Duration: 904.52 ms Billed Duration: 1000 ms Memory Size: 128 MB Max Memory Used: 41 MB
As expected, our tweet was successfully created by AWS Lambda and shows up on the AirbrakeArticles Twitter page!
AWS Lambda and Airbrake Error Handling
As you may recall from before, trying to send out a second, identical tweet results in a rejected request from the Twitter API, which indicates that the status is a duplicate. Therefore, this is a great opportunity to test our Airbrake-JS
integration with AWS Lambda. To do so just execute the same Test
once again, which will invoke the index.tweetHelloWorld
handler a second time and attempt to tweet "Hello World"
again.
As expected, the result of execution in AWS Lambda is:
{
"errorMessage": "Status is a duplicate."
}
The log output confirms the issue:
START RequestId: 5b12662e-ef6f-11e7-b472-21ffb7560a15 Version: $LATEST
2018-01-02T03:45:21.006Z 5b12662e-ef6f-11e7-b472-21ffb7560a15 [ { code: 187, message: 'Status is a duplicate.' } ]
2018-01-02T03:45:21.520Z 5b12662e-ef6f-11e7-b472-21ffb7560a15 {"errorMessage":"Status is a duplicate."}
END RequestId: 5b12662e-ef6f-11e7-b472-21ffb7560a15
Most importantly, looking over at the Airbrake project dashboard for our twitter-bot
Node project immediately displays a Status is a duplicate.
error, with all the same detailed contextual information that we saw previously from when this error occurred in our local development environment.
Repeating Schedule Lambda Functions
While this is cool so far that we're able to use AWS Lambda to execute our Node.js application code, it's not very useful unless execution can be triggered from another event. There are many possible ways to trigger Lambda functions, but we'll keep it simple by setting up a schedule. To add a scheduled trigger, open the Designer
panel in the airbrake-article-twitter-bot
dashboard and select CloudWatch Events
. This will add CloudWatch Events
to the trigger side of the visual display, while also opening the Configure triggers
panel where you'll actually specify how the trigger will behave.
Select Create a new rule
from the Rule
dropdown and enter a descriptive name. For testing purposes we'll be creating a trigger that occurs once a minute, so we'll name this rule every-minute
. We can now specify the schedule in the Schedule expression
box, using Cron
or rate
expressions. We don't need anything complex, so we'll just use rate(1 minute)
to trigger this function every 60 seconds. Finally, you may want to uncheck Enable trigger
for now, so the trigger won't be enabled until you do so yourself. Click Add
to create the trigger.
We also want to change the handler
function that is being executed to something that won't automatically produce an error. To modify our function code again just click on the visual airbrake-article-twitter-bot
box in the Designer
panel. Under Function code > Handler
enter index.tweetTime
, then click Save
. If you want to manually test the function again you can click Test
and ensure the result is successful:
"'3:56:56 AM' tweeted."
Our goal is to schedule the function to trigger on its own, so select CloudWatch Events
in the Designer
panel, Enable
the every-minute
trigger, then click Save
to confirm the changes. Now. we just sit back and wait a minute or two and we'll have an automatically scheduled trigger causing our AWS Lambda function to execute, which will generate a tweet of the current time every minute. We can confirm this either by checking for the produced tweets, or open the CloudWatch Logs
page on AWS Lambda:
2018-01-02T03:58:24.279Z 2dd869f5-ef71-11e7-bace-1515e5380db4 ---- TWEETED ----
2018-01-02T03:58:24.285Z 2dd869f5-ef71-11e7-bace-1515e5380db4 { created_at: 'Tue Jan 02 03:58:24 +0000 2018', id: 948040754030448600, id_str: '948040754030448646', text: '3:58:23 AM', truncated: false, entities: { hashtags: [], symbols: [], user_mentions: [], urls: [] }, source: '<a href="https://github.com/GabeStah/twitter-bot" rel="nofollow">AirbrakeArticles</a>', in_reply_to_status
END RequestId: 2dd869f5-ef71-11e7-bace-1515e5380db4
REPORT RequestId: 2dd869f5-ef71-11e7-bace-1515e5380db4 Duration: 453.04 ms Billed Duration: 500 ms Memory Size: 128 MB Max Memory Used: 42 MB
START RequestId: 51862ce1-ef71-11e7-b70b-05b293363b49 Version: $LATEST
2018-01-02T03:59:24.125Z 51862ce1-ef71-11e7-b70b-05b293363b49 ---- TWEETED ----
2018-01-02T03:59:24.125Z 51862ce1-ef71-11e7-b70b-05b293363b49 { created_at: 'Tue Jan 02 03:59:24 +0000 2018', id: 948041005034438700, id_str: '948041005034438661', text: '3:59:23 AM', truncated: false, entities: { hashtags: [], symbols: [], user_mentions: [], urls: [] }, source: '<a href="https://github.com/GabeStah/twitter-bot" rel="nofollow">AirbrakeArticles</a>', in_reply_to_status
END RequestId: 51862ce1-ef71-11e7-b70b-05b293363b49
REPORT RequestId: 51862ce1-ef71-11e7-b70b-05b293363b49 Duration: 433.53 ms Billed Duration: 500 ms Memory Size: 128 MB Max Memory Used: 43 MB
START RequestId: 7548af02-ef71-11e7-af26-3903d36c0918 Version: $LATEST
2018-01-02T04:00:24.150Z 7548af02-ef71-11e7-af26-3903d36c0918 ---- TWEETED ----
2018-01-02T04:00:24.150Z 7548af02-ef71-11e7-af26-3903d36c0918 { created_at: 'Tue Jan 02 04:00:24 +0000 2018', id: 948041256709496800, id_str: '948041256709496833', text: '4:00:23 AM', truncated: false, entities: { hashtags
Note: If you are experiencing timeout errors indicating that the AWS Lambda function concluded before execution completed, you may need to increase the timeout
period under the Basic settings
panel.
Finalizing Our Serverless Twitter Bot
Now that we know everything works as expected, the last step is to use the tweetRandomArticle
handler
for our AWS Lambda function, and to modify the schedule to something more appropriate. We'll begin by testing that the handler
works as expected and actually retrieves and posts a random article. After changing the handler
field to index.tweetRandomArticle
and clicking Save
we'll check the logs and our AirbrakeArticles Twitter account.
Sure enough, it works as expected and a random article was retrieved and tweeted automatically! Plus, when our bot inevitably chooses a random article that is a duplicate from a previous recent tweet, Airbrake catches and reports the error immediately.
The article retrieval process isn't very efficient, so we don't want to execute this all that often. Therefore, we'll change our schedule trigger to occur once every twelve hours, but otherwise we're all set. We now have a fully functional, serverless Twitter bot written with Node.js and automatically triggered and executed within AWS Lambda!