Notifications
With Go, Lambda and SNS
Traces and logs help us dig into requests, responses and errors, but they don’t alert us when an error occurs. For that, we need notifications.
Email and SMS notifications are easy with the Simple Notification Service (SNS).
Go Code
First we introduce a notification middleware – a function that takes and returns a function of the same definition. This one takes a handler function, calls it, sends an SNS notification if it returned an error then returns its return values.
type HandlerAPIGateway func(ctx context.Context, e events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error)
func NotifyAPIGateway(h HandlerAPIGateway) HandlerAPIGateway {
return func(ctx context.Context, e events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
r, err := h(ctx, e)
notify(ctx, err)
return r, err
}
}
func notify(ctx context.Context, err error) {
topic := os.Getenv("NOTIFICATION_TOPIC")
if err == nil || topic == "" {
return
}
subj := fmt.Sprintf("ERROR %s", os.Getenv("AWS_LAMBDA_FUNCTION_NAME"))
msg := fmt.Sprintf("%+v\n", err)
log.Printf("%s %s\n", subj, msg)
_, err = SNS().PublishWithContext(ctx, &sns.PublishInput{
Message: aws.String(msg),
Subject: aws.String(subj),
TopicArn: aws.String(topic),
})
}
From notify.go
Then we update our handler programs to use the middleware:
func main() {
lambda.Start(gofaas.NotifyAPIGateway(gofaas.Dashboard))
}
From [handlers/dashboard/main.go]
There are a couple Go-isms to note here.
The middleware technique means we don’t have to change our function code at all. Handlers can always return err
and leave it up to the middleware to publish to SNS.
Also notice the use of the errors
package and errors.WithStack()
here and throughout our functions. This gives us high quality back traces in our logs and notifications.
The notify
function would also be a good place to send errors directly to Rollbar, etc.
AWS Config
Next, we need to add a SNS resources to our config. We create the SNS topic and pass it to every function environment, and we conditionally create SNS subscriptions for an email or phone number parameter. We also give every function the SNSPublishMessagePolicy
so it can publish to the topic.
Conditions:
NotificationEmailSpecified: !Not [!Equals [!Ref NotificationEmail, ""]]
NotificationNumberSpecified: !Not [!Equals [!Ref NotificationNumber, ""]]
Globals:
Function:
Environment:
Variables:
NOTIFICATION_TOPIC: !Ref NotificationTopic
Parameters:
NotificationEmail:
Default: ""
Type: String
NotificationNumber:
Default: ""
Type: String
Resources:
DashboardFunction:
Properties:
Policies:
- SNSPublishMessagePolicy:
TopicName: !GetAtt NotificationTopic.TopicName
Type: AWS::Serverless::Function
NotificationTopic:
Properties:
DisplayName: Notification
Subscription:
- !If
- NotificationEmailSpecified
- Endpoint: !Ref NotificationEmail
Protocol: email
- !Ref AWS::NoValue
- !If
- NotificationNumberSpecified
- Endpoint: !Ref NotificationNumber
Protocol: sms
- !Ref AWS::NoValue
TopicName: Notification
Type: AWS::SNS::Topic
From template.yml
Summary
Sending notifications with Go and SNS is straightforward:
- Implement a notification middleware for our handlers
- Configure SNS with an email and/or SMS number
We no longer have to worry about:
- Email or SMS gateways
- 3rd party SaaS
AWS services make monitoring our application simple and cost effective.