Skip to content

Commit

Permalink
chore(examples): Tracetest AWS Lambda Layers Example (#3877)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoscar authored May 29, 2024
1 parent da8fee4 commit b8d2667
Show file tree
Hide file tree
Showing 14 changed files with 9,475 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/quick-start-serverless-layers/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TRACETEST_API_TOKEN=
7 changes: 7 additions & 0 deletions examples/quick-start-serverless-layers/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# package directories
node_modules
jspm_packages

# Serverless directories
.serverless
.env
3 changes: 3 additions & 0 deletions examples/quick-start-serverless-layers/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
7 changes: 7 additions & 0 deletions examples/quick-start-serverless-layers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Trace-based Testing AWS Lambda with Tempo and OpenTelemetry Layers

This is a project bootstrapped with [Serverless Framework](https://www.serverless.com/).

## Learn more

Feel free to check out the [docs](https://docs.tracetest.io/), and join our [Slack Community](https://dub.sh/tracetest-community) for more info!
21 changes: 21 additions & 0 deletions examples/quick-start-serverless-layers/collector.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"

exporters:
logging:
verbosity: detailed
otlp:
endpoint: tempo-us-central1.grafana.net:443
headers:
authorization: Basic <base64 encoded username:password>

service:
pipelines:
traces:
receivers: [otlp]
exporters: [logging, otlp]
9,142 changes: 9,142 additions & 0 deletions examples/quick-start-serverless-layers/package-lock.json

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions examples/quick-start-serverless-layers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"dependencies": {
"@aws-sdk/client-dynamodb": "^3.509.0",
"@aws-sdk/lib-dynamodb": "^3.509.0",
"@opentelemetry/api": "^1.7.0",
"@opentelemetry/auto-instrumentations-node": "^0.41.1",
"@opentelemetry/exporter-trace-otlp-grpc": "^0.48.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.48.0",
"@opentelemetry/instrumentation": "^0.48.0",
"@opentelemetry/instrumentation-fetch": "^0.48.0",
"@opentelemetry/sdk-trace-base": "^1.21.0",
"@opentelemetry/sdk-trace-node": "^1.21.0",
"@tracetest/client": "^0.0.37",
"@types/aws-lambda": "^8.10.133",
"@types/node": "^20.11.17",
"dotenv": "^16.4.1",
"node-fetch": "^2.7.0"
},
"name": "tracetest-serverless",
"version": "1.0.0",
"scripts": {
"deploy": "serverless deploy",
"test": "ENDPOINT=\"$(sls info --verbose | grep HttpApiUrl | sed s/HttpApiUrl\\:\\ //g)\" ts-node tracetest.ts",
"start": "npm run deploy && npm run test"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"serverless-plugin-typescript": "^2.1.5",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}
12 changes: 12 additions & 0 deletions examples/quick-start-serverless-layers/resources/dynamodb.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Resources:
PokemonsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:custom.tableName}
AttributeDefinitions:
- AttributeName: id
AttributeType: N
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
49 changes: 49 additions & 0 deletions examples/quick-start-serverless-layers/serverless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
service: tracetest-layers
frameworkVersion: "3"

package:
include:
- collector.yaml

plugins:
- serverless-plugin-typescript

custom:
tableName: tracetest-pokemons

provider:
name: aws
runtime: nodejs18.x
environment:
TABLE_NAME: ${self:custom.tableName}
OPENTELEMETRY_COLLECTOR_CONFIG_FILE: /var/task/collector.yaml
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-handler

iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:DescribeTable
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- "Fn::GetAtt": [PokemonsTable, Arn]

functions:
api:
handler: src/handler.importPokemon
events:
- httpApi:
path: /import
method: post
layers:
- arn:aws:lambda:us-east-1:184161586896:layer:opentelemetry-nodejs-0_6_0:1
- arn:aws:lambda:us-east-1:184161586896:layer:opentelemetry-collector-amd64-0_6_0:1
resources:
# DynamoDB
- ${file(resources/dynamodb.yml)}
35 changes: 35 additions & 0 deletions examples/quick-start-serverless-layers/src/dynamodb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { PutCommand, DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

const { TABLE_NAME = '' } = process.env;

const DynamoDb = {
async put<T extends Record<string, any>>(item: T): Promise<T> {
const command = new PutCommand({
TableName: TABLE_NAME,
Item: item,
});

const result = await docClient.send(command);

return result.Attributes as T;
},

async get<T>(id: number): Promise<T | undefined> {
const command = new GetCommand({
TableName: TABLE_NAME,
Key: {
id,
},
});

const result = await docClient.send(command);

return result.Item as T | undefined;
},
};

export default DynamoDb;
49 changes: 49 additions & 0 deletions examples/quick-start-serverless-layers/src/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { APIGatewayEvent, Handler } from 'aws-lambda';
import fetch from 'node-fetch';
import { Pokemon, RawPokemon } from './types';
import DynamoDb from './dynamodb';

const Pokemon = (raw: RawPokemon): Pokemon => ({
id: raw.id,
name: raw.name,
types: raw.types.map((type) => type.type.name),
imageUrl: raw.sprites.front_default,
});

const getPokemon = async (id: string): Promise<Pokemon> => {
const url = `https://pokeapi.co/api/v2/pokemon/${id}`;
const res = await fetch(url);

const raw = await res.json();

return Pokemon(raw);
};

const insertPokemon = async (pokemon: Pokemon) => {
await DynamoDb.put(pokemon);

return DynamoDb.get<Pokemon>(pokemon.id);
};

type TBody = { id: string };

export const importPokemon: Handler<APIGatewayEvent> = async (event) => {
console.log(event);

const { id = '' } = JSON.parse(event.body || '') as TBody;

try {
const pokemon = await getPokemon(id);
const result = await insertPokemon(pokemon);

return {
statusCode: 200,
body: JSON.stringify(result),
};
} catch (error) {
return {
statusCode: 400,
body: error.message,
};
}
};
20 changes: 20 additions & 0 deletions examples/quick-start-serverless-layers/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export type RawPokemon = {
id: number;
name: string;
types: Array<{
type: {
name: string;
};
}>;
sprites: {
front_default: string;
};
};


export type Pokemon = {
id: number;
name: string;
types: string[];
imageUrl: string;
};
81 changes: 81 additions & 0 deletions examples/quick-start-serverless-layers/tracetest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Tracetest from '@tracetest/client';
import { TestResource } from '@tracetest/client/dist/modules/openapi-client';
import { config } from 'dotenv';

config();

const { TRACETEST_API_TOKEN = '' } = process.env;

const [raw = ''] = process.argv.slice(2);

let url = '';

try {
url = new URL(raw).origin;
} catch (error) {
console.error(
'The API Gateway URL is required as an argument. i.e: `npm test https://75yj353nn7.execute-api.us-east-1.amazonaws.com`'
);
process.exit(1);
}

const definition: TestResource = {
type: 'Test',
spec: {
id: 'ZV1G3v2IR',
name: 'Serverless: Import Pokemon',
trigger: {
type: 'http',
httpRequest: {
method: 'POST',
url: '${var:ENDPOINT}/import',
body: '{"id": "${var:POKEMON_ID}"}\n',
headers: [
{
key: 'Content-Type',
value: 'application/json',
},
],
},
},
specs: [
{
selector: 'span[tracetest.span.type="database"]',
name: 'All Database Spans: Processing time is less than 100ms',
assertions: ['attr:tracetest.span.duration < 100ms'],
},
{
selector: 'span[tracetest.span.type="http"]',
name: 'All HTTP Spans: Status code is 200',
assertions: ['attr:http.status_code = 200'],
},
{
selector:
'span[name="tracetest-layers-dev-api"] span[tracetest.span.type="http" name="GET" http.method="GET"]',
name: 'The request matches the pokemon Id',
assertions: ['attr:http.url = "https://pokeapi.co/api/v2/pokemon/${var:POKEMON_ID}"'],
},
],
},
};

const main = async () => {
const tracetest = await Tracetest(TRACETEST_API_TOKEN);

const test = await tracetest.newTest(definition);
await tracetest.runTest(test, {
variables: [
{
key: 'ENDPOINT',
value: url.trim(),
},
{
key: 'POKEMON_ID',
value: `${Math.floor(Math.random() * 100) + 1}`,
},
],
});
console.log(await tracetest.getSummary());
};

main();
14 changes: 14 additions & 0 deletions examples/quick-start-serverless-layers/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"preserveConstEnums": true,
"strictNullChecks": true,
"sourceMap": true,
"allowJs": true,
"target": "es5",
"outDir": ".build",
"moduleResolution": "node",
"lib": ["es2015"],
"rootDir": "./"
},
"exclude": ["tracetest.ts"]
}

0 comments on commit b8d2667

Please sign in to comment.