How Do I Create A CloudWatch Alarm For My AWS Amplify Function? - aws-amplify

Goal: To monitor the Lambda Throttles with CloudWatch Alarms. Lambdas are built in the CLI using amplify add function. The code below was implemented following the Amplify Documentation, running amplify add custom and then CDK.
Code:
/* AWS CDK code goes here - learn more: https://docs.aws.amazon.com/cdk/latest/guide/home.html */
import * as cdk from "#aws-cdk/core";
import * as AmplifyHelpers from "#aws-amplify/cli-extensibility-helper";
import { AmplifyDependentResourcesAttributes } from "../../types/amplify-dependent-resources-ref";
import { Alarm } from "#aws-cdk/aws-cloudwatch";
import { lambdaThrottles } from "./alarm-props";
import { ComparisonOperator, AlarmProps } from '#aws-cdk/aws-cloudwatch';
export class cdkStack extends cdk.Stack {
private readonly ApplicationToExpiredApplicationTableAlarm: Alarm;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) {
super(scope, id, props);
// 🔽 Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter
new cdk.CfnParameter(this, "env", { type: "String", description: "Current Amplify CLI env name" });
// 🔽 Obtaining the project information
const amplifyProjectInfo = AmplifyHelpers.getProjectInfo();
const environment = cdk.Fn.ref('env');
// 🔽 Obtaining the Lambda Functions from Amplify
const dependencies: AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this, amplifyResourceProps.category, amplifyResourceProps.resourceName, [
{ category: "function", resourceName: "ApplicationToExpiredApplicationTable" },
]);
// 🔽 Attempting to access the Lambda Function directly
const ApplicationToExpiredApplicationTable = dependencies.function["ApplicationToExpiredApplicationTable"];
// 🔽 Defining Alarm Props
const lambdaThrottles = (lambdaFunction: any): AlarmProps => {
return {
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
threshold: 1,
evaluationPeriods: 1,
metric: lambdaFunction.metricThrottles({ label : lambdaFunction.functionName}),
actionsEnabled: true,
alarmDescription: `This lambda has throttled ${lambdaFunction.functionName}`,
alarmName: `${lambdaFunction.functionMae}-Throttles`
}
};
// 🔽 Building the Alarms
this.ApplicationToExpiredApplicationTableAlarm = new Alarm(this, `${environment}-ApplicationToExpiredApplicationTableAlarm-${amplifyProjectInfo.projectName}`, lambdaThrottles(ApplicationToExpiredApplicationTable))
}
}
Current Error:
I cannot access the function like I would be able to with the CDK Function, and thus cannot write lambdaFunction.metricThrottles because it is not available.
I understand how to get the Arn or the Name following the Amplify Documentation, but this still does not allude to accessing the function itself, just properties of it.

Related

Create a custom resource with AWS Amplify and CDK

I'm trying to create a custom resource in AWS Amplify, using AWS CDK. And, I'm trying to use an existing lambda function as a provider event handler.
When I do amplify push the resource creation fails with no useful information. What am I doing wrong here? How can I troubleshoot this?
import * as cdk from '#aws-cdk/core';
import * as AmplifyHelpers from '#aws-amplify/cli-extensibility-helper';
import * as cr from "#aws-cdk/custom-resources";
import * as logs from "#aws-cdk/aws-logs";
import * as lambda from '#aws-cdk/aws-lambda';
import { AmplifyDependentResourcesAttributes } from "../../types/amplify-dependent-resources-ref"
export class cdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) {
super(scope, id, props);
/* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
new cdk.CfnParameter(this, 'env', {
type: 'String',
description: 'Current Amplify CLI env name',
});
const dependencies: AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this,
amplifyResourceProps.category,
amplifyResourceProps.resourceName,
[{
category: "function",
resourceName: "myFunction"
}]
);
const myFunctionArn = cdk.Fn.ref(dependencies.function.myFunction.Arn);
const importedLambda = lambda.Function.fromFunctionArn(this, "importedLambda", myFunctionArn);
const provider = new cr.Provider(this, "MyCustomResourceProvider", {
onEventHandler: importedLambda,
logRetention: logs.RetentionDays.ONE_DAY,
})
new cdk.CustomResource(this, "MyCustomResource", {
serviceToken: provider.serviceToken
})
}
}
Here's the error I get:
CREATE_FAILED custommyCustomResourceXXXX AWS::CloudFormation::Stack Parameters: [AssetParametersXXXX, .....] must have values.
I got a response from AWS support team. It looks like the AssetParameters error is caused by the fact that Amplify CLI currently doesn't support a high level construct of Custom Resource Provider inside the custom resource category in Amplify CLI. The resource should be created this way:
import * as cdk from '#aws-cdk/core';
import * as AmplifyHelpers from '#aws-amplify/cli-extensibility-helper';
import * as lambda from '#aws-cdk/aws-lambda';
import { AmplifyDependentResourcesAttributes } from "../../types/amplify-dependent-resources-ref"
export class cdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) {
super(scope, id, props);
/* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
new cdk.CfnParameter(this, 'env', {
type: 'String',
description: 'Current Amplify CLI env name',
});
const dependencies: AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this,
amplifyResourceProps.category,
amplifyResourceProps.resourceName,
[{
category: "function",
resourceName: "myFunction"
}]
);
const myFunctionArn = cdk.Fn.ref(dependencies.function.myFunction.Arn);
const importedLambda = lambda.Function.fromFunctionArn(this, "importedLambda", myFunctionArn);
new cdk.CustomResource(this, "MyCustomResource", {
serviceToken: importedLambda.functionArn
})
}
}

How To Unit Test NextJS API Route That Uses Repository Pattern?

I am new prisma / nextjs user and I am trying to understand how to unit test an API route that uses prisma. I have read the unit testing guide.
I like the dependency injection approach and have started trying to implement it. However I am struggling with the following development issue. Can anybody help?
With the dependency injection approach the unit testing guide explains how to setup the mock context and use this in the data access layer. Does anyone have any examples of how and where the real context could be initialised and used with an API route that uses a repository pattern? Is it possible to expand the next.js api handler with middleware to include the context to facilitate testing?
import type { NextApiRequest, NextApiResponse } from 'next'
import { PublishRepository } from '../../../repository'
// PUT /api/publish/:id
export default async function handle(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method == 'PUT') {
const postId = req.query.id;
let repo = new PublishRepository( // where does the live context come from and how is it initialised??? )
const post = repo.set_published(postId)
res.json(post);
}
}
Repository - Initialised using Context instance - How is this initialise for development and how is it mocked?
import { Post, PrismaClient } from "#prisma/client"
import { Context } from "../context"
import prisma from "lib/prisma"
export class PublishRepository {
private prisma: PrismaClient
constructor(context: Context) {
this.prisma = context.prisma
}
async set_published(post_id: string | string[]): Promise<Post> {
return await prisma.post.update({
where: { id: Number(post_id) },
data: { published: true },
});
}
}

How to configure apollo server with meteor with the meteor/apollo package?

I'm trying to build an app with meteor, apollo/graphql for the first time and the tutorial I'm watching might have outdated versions of apollo. I set up my server as:
import { createApolloServer } from "meteor/apollo";
import { makeExecutableSchema } from "graphql-tools";
const typeDefs = `
type Query {
hi: String
}
`;
const resolvers = {
Query: {
hi() {
return "Hello world";
}
}
};
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
createApolloServer({ schema });
However I usually get this as an error:
TypeError: graphqlExpress is not a function
I know it has to do with the apollo package, but I don't know how to get the migration changes made for apollo 2.0.0 into the meteor/apollo file from the migration doc on apollo's site. Any help is appreciated!

Log 'jsonPayload' in Firebase Cloud Functions

TL;DR;
Does anyone know if it's possible to use console.log in a Firebase/Google Cloud Function to log entries to Stack Driver using the jsonPayload property so my logs are searchable (currently anything I pass to console.log gets stringified into textPayload).
I have a multi-module project with some code running on Firebase Cloud Functions, and some running in other environments like Google Compute Engine. Simplifying things a little, I essentially have a 'core' module, and then I deploy the 'cloud-functions' module to Cloud Functions, 'backend-service' to GCE, which all depend on 'core' etc.
I'm using bunyan for logging throughout my 'core' module, and when deployed to GCE the logger is configured using '#google-cloud/logging-bunyan' so my logs go to Stack Driver.
Aside: Using this configuration in Google Cloud Functions is causing issues with Error: Endpoint read failed which I think is due to functions not going cold and trying to reuse dead connections, but I'm not 100% sure what the real cause is.
So now I'm trying to log using console.log(arg) where arg is an object, not a string. I want this object to appear in Stack Driver under the jsonPayload but it's being stringified and put into the textPayload field.
It took me awhile, but I finally came across this example in firebase functions samples repository. In the end I settled on something a bit like this:
const Logging = require('#google-cloud/logging');
const logging = new Logging();
const log = logging.log('my-func-logger');
const logMetadata = {
resource: {
type: 'cloud_function',
labels: {
function_name: process.env.FUNCTION_NAME ,
project: process.env.GCLOUD_PROJECT,
region: process.env.FUNCTION_REGION
},
},
};
const logData = { id: 1, score: 100 };
const entry = log.entry(logMetaData, logData);
log.write(entry)
You can add a string severity property value to logMetaData (e.g. "INFO" or "ERROR"). Here is the list of possible values.
Update for available node 10 env vars. These seem to do the trick:
labels: {
function_name: process.env.FUNCTION_TARGET,
project: process.env.GCP_PROJECT,
region: JSON.parse(process.env.FIREBASE_CONFIG).locationId
}
UPDATE: Looks like for Node 10 runtimes they want you to set env values explicitly during deploy. I guess there has been a grace period in place because my deployed functions are still working.
I ran into the same problem, and as stated by comments on #wtk's answer, I would like to add replicating all of the default cloud function logging behavior I could find in the snippet below, including execution_id.
At least for using Cloud Functions with the HTTP Trigger option the following produced correct logs for me. I have not tested for Firebase Cloud Functions
// global
const { Logging } = require("#google-cloud/logging");
const logging = new Logging();
const Log = logging.log("cloudfunctions.googleapis.com%2Fcloud-functions");
const LogMetadata = {
severity: "INFO",
type: "cloud_function",
labels: {
function_name: process.env.FUNCTION_NAME,
project: process.env.GCLOUD_PROJECT,
region: process.env.FUNCTION_REGION
}
};
// per request
const data = { foo: "bar" };
const traceId = req.get("x-cloud-trace-context").split("/")[0];
const metadata = {
...LogMetadata,
severity: 'INFO',
trace: `projects/${process.env.GCLOUD_PROJECT}/traces/${traceId}`,
labels: {
execution_id: req.get("function-execution-id")
}
};
Log.write(Log.entry(metadata, data));
The github link in #wtk's answer should be updated to:
https://github.com/firebase/functions-samples/blob/2f678fb933e416fed9be93e290ae79f5ea463a2b/stripe/functions/index.js#L103
As it refers to the repository as of when the question was answered, and has the following function in it:
// To keep on top of errors, we should raise a verbose error report with Stackdriver rather
// than simply relying on console.error. This will calculate users affected + send you email
// alerts, if you've opted into receiving them.
// [START reporterror]
function reportError(err, context = {}) {
// This is the name of the StackDriver log stream that will receive the log
// entry. This name can be any valid log stream name, but must contain "err"
// in order for the error to be picked up by StackDriver Error Reporting.
const logName = 'errors';
const log = logging.log(logName);
// https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource
const metadata = {
resource: {
type: 'cloud_function',
labels: {function_name: process.env.FUNCTION_NAME},
},
};
// https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent
const errorEvent = {
message: err.stack,
serviceContext: {
service: process.env.FUNCTION_NAME,
resourceType: 'cloud_function',
},
context: context,
};
// Write the error log entry
return new Promise((resolve, reject) => {
log.write(log.entry(metadata, errorEvent), (error) => {
if (error) {
return reject(error);
}
resolve();
});
});
}
// [END reporterror]

How to call GCP Datastore from GCP Cloud Function?

Here's the starter code provided with a new GCP Cloud Function:
/**
* Responds to any HTTP request that can provide a "message" field in the body.
*
* #param {!Object} req Cloud Function request context.
* #param {!Object} res Cloud Function response context.
*/
exports.helloWorld = function helloWorld(req, res) {
// Example input: {"message": "Hello!"}
if (req.body.message === undefined) {
// This is an error case, as "message" is required.
res.status(400).send('No message defined!');
} else {
// Everything is okay.
console.log(req.body.message);
res.status(200).send('Success: ' + req.body.message);
}
};
... and the package.json:
{
"name": "sample-http",
"version": "0.0.1"
}
Looking for a basic example of calling DataStore from here.
I'm not a Node.js user, but based on the documentation I think one convenient way would be to use the Node.js Cloud Datastore Client Library. The example from that page:
// Imports the Google Cloud client library
const Datastore = require('#google-cloud/datastore');
// Your Google Cloud Platform project ID
const projectId = 'YOUR_PROJECT_ID';
// Instantiates a client
const datastore = Datastore({
projectId: projectId
});
// The kind for the new entity
const kind = 'Task';
// The name/ID for the new entity
const name = 'sampletask1';
// The Cloud Datastore key for the new entity
const taskKey = datastore.key([kind, name]);
// Prepares the new entity
const task = {
key: taskKey,
data: {
description: 'Buy milk'
}
};
// Saves the entity
datastore.save(task)
.then(() => {
console.log(`Saved ${task.key.name}: ${task.data.description}`);
})
.catch((err) => {
console.error('ERROR:', err);
});
But you may want to take a look at Client Libraries Explained as well, as it describes or points to detailed pages about other options as well, some of which one might find preferable.
you need to include the DataStore dependency in the package.json
{
"name": "sample-http",
"dependencies": {
"#google-cloud/datastore": "1.3.4"
},
"version": "0.0.1"
}

Resources