Skip to content

Commit

Permalink
chore(cb2-11286): update aws sdk to v3 (#76)
Browse files Browse the repository at this point in the history
* chore(cb2-11286): update aws ssdk to v3

* chore(cb2-11286): update readme and fix lint issues

* chore(cb2-11286): remove extra code
  • Loading branch information
shivangidas authored Apr 18, 2024
1 parent 6a2dce5 commit 942b99c
Show file tree
Hide file tree
Showing 8 changed files with 1,264 additions and 743 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ artefacts

# Idea specific hidden files
.idea/**

reports/
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ npm test
```

Please refer to the the [docs](./docs/README.md) for the API specification and samples of postman requests.

If the tests return a credentials error, check `~/.aws/credentials` has dummy values set up
```
AWS_ACCESS_KEY_ID=some_value
AWS_SECRET_ACCESS_KEY=some_value
```
1,803 changes: 1,190 additions & 613 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"url": "git+https://github.com/dvsa/cvs-svc-app-logs.git"
},
"dependencies": {
"aws-sdk": "^2.1436.0",
"@aws-sdk/client-cloudwatch-logs": "3.540.0",
"moment": "^2.29.4"
},
"engines": {
Expand All @@ -28,14 +28,13 @@
"@commitlint/cli": "17.7.1",
"@commitlint/config-conventional": "17.7.0",
"@types/aws-lambda": "^8.10.119",
"@types/aws-sdk": "^2.7.0",
"@types/jasmine": "^4.3.5",
"@types/node": "^20.5.0",
"@types/sinon": "^10.0.16",
"@types/supertest": "^2.0.12",
"audit-filter": "^0.5.0",
"aws-lambda-test-utils": "^1.3.0",
"aws-sdk-mock": "^5.8.0",
"aws-sdk-client-mock": "4.0.0",
"husky": "8.0.3",
"jasmine": "^5.1.0",
"jasmine-spec-reporter": "^7.0.0",
Expand Down
1 change: 1 addition & 0 deletions serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ provider:
name: aws
runtime: nodejs18.x
lambdaHashingVersion: "20201221"
region: ${env:AWS_PROVIDER_REGION, 'local'}

package:
individually: true
Expand Down
146 changes: 38 additions & 108 deletions src/functions/postLogs/framework/__tests__/createLogger.spec.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,25 @@
import * as awsSdkMock from "aws-sdk-mock";
import { CloudWatchLogsClient, CreateLogStreamCommand, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs";
import { mockClient } from "aws-sdk-client-mock";
import { Mock, It, Times } from "typemoq";
import { CloudWatchLogs } from "aws-sdk";
import * as logger from "../createLogger";
import { LogDelegate } from "../../application/Logger";

describe("logging", () => {
const originalConsoleLog = console.log;
const moqConsoleLog = Mock.ofInstance(console.log);
const moqCreateLogStream = Mock.ofInstance(
new CloudWatchLogs().createLogStream
);
const moqPutLogEvents = Mock.ofInstance(new CloudWatchLogs().putLogEvents);

beforeEach(() => {
moqConsoleLog.reset();
moqCreateLogStream.reset();
moqPutLogEvents.reset();

moqCreateLogStream
.setup((x) => x(It.isAny(), It.isAny()))
.returns(() => <any>(<unknown>Promise.resolve(true)));

moqPutLogEvents
.setup((x) => x(It.isAny(), It.isAny()))
.returns(() => <any>(<unknown>Promise.resolve(true)));

moqConsoleLog
.setup((x) => x(It.isAny(), It.isAny()))
.callback((message?: any, ...optionalParams: any[]) =>
originalConsoleLog(message, ...optionalParams)
);

awsSdkMock.mock(
"CloudWatchLogs",
"createLogStream",
moqCreateLogStream.object
);
awsSdkMock.mock("CloudWatchLogs", "putLogEvents", moqPutLogEvents.object);

spyOn(console, "log").and.callFake(moqConsoleLog.object);
});

afterEach(() => {
awsSdkMock.restore("CloudWatchLogs", "createLogStream");
awsSdkMock.restore("CloudWatchLogs", "putLogEvents");
});

describe("createLogger", () => {
const sut = logger.createLogger;

Expand Down Expand Up @@ -77,6 +51,8 @@ describe("logging", () => {

it("creates a CloudWatch logger if CloudWatch LogGroupName is specified", async () => {
// ACT
const mockCloudWatchClient = mockClient(CloudWatchLogsClient);
mockCloudWatchClient.on(CreateLogStreamCommand).resolves({});
const result = await sut("testLogger", "cloudWatchLogGroupName");

// ASSERT
Expand All @@ -102,7 +78,10 @@ describe("logging", () => {
const sut = logger.createCloudWatchLogger;

it("creates a log delegate that logs to CloudWatch", async () => {
// ACT
const mockCloudWatchClient = mockClient(CloudWatchLogsClient);
mockCloudWatchClient.on(CreateLogStreamCommand).resolves({});
mockCloudWatchClient.on(PutLogEventsCommand).resolves({ nextSequenceToken: "123" });

const result: LogDelegate = await sut(
"testLoggerName",
"testLogGroupName"
Expand All @@ -111,126 +90,77 @@ describe("logging", () => {
{ timestamp: 265473, message: "test log message to cloudwatch" },
]);

// ASSERT
moqPutLogEvents.verify(
(x) =>
x(
It.is<CloudWatchLogs.Types.PutLogEventsRequest>(
(r) =>
r.logEvents[0].message === "test log message to cloudwatch" &&
r.logGroupName === "testLogGroupName" &&
r.logStreamName.indexOf("testLoggerName") > -1
),
It.isAny()
),
Times.once()
);
const putLogEventsStub = mockCloudWatchClient.commandCalls(PutLogEventsCommand);

expect(putLogEventsStub[0].args[0].input).toEqual(jasmine.objectContaining({
logGroupName: "testLogGroupName",
}));
expect(putLogEventsStub[0].args[0].input.logStreamName).toContain("testLoggerName");
expect(putLogEventsStub[0].args[0].input.logEvents).toEqual([{"timestamp":265473,"message":"test log message to cloudwatch"}]);
});

it("should call the `createLogStream` CloudWatchLogs method correctly", async () => {
// ACT
const mockCloudWatchClient = mockClient(CloudWatchLogsClient);
mockCloudWatchClient.on(CreateLogStreamCommand).resolves({});
mockCloudWatchClient.on(PutLogEventsCommand).resolves({ nextSequenceToken: "123" });

await sut("testLoggerName", "testLogGroupName");
const createLogEventsStub = mockCloudWatchClient.commandCalls(CreateLogStreamCommand);

// ASSERT
moqCreateLogStream.verify(
(x) =>
x(
It.is<CloudWatchLogs.Types.CreateLogStreamRequest>(
(r) =>
r.logGroupName === "testLogGroupName" &&
/^testLoggerName-\d\d\d\d-\d\d-\d\d-[0-9a-f]{32}$/.test(
r.logStreamName
)
),
It.isAny()
),
Times.once()
);
expect(createLogEventsStub[0].args[0].input).toEqual(jasmine.objectContaining({
logGroupName: "testLogGroupName",
}));
expect(createLogEventsStub[0].args[0].input.logStreamName).toContain("testLoggerName");
});

it("should use `sequenceToken` from previous `putLogEvents` result", async () => {
moqPutLogEvents.reset();
moqPutLogEvents
.setup((x) => x(It.isAny(), It.isAny()))
.returns(
() => <any>(<unknown>Promise.resolve({
nextSequenceToken: "example-sequenceToken-123",
}))
);
const mockCloudWatchClient = mockClient(CloudWatchLogsClient);
mockCloudWatchClient.on(CreateLogStreamCommand).resolves({});
mockCloudWatchClient.on(PutLogEventsCommand).resolves({ nextSequenceToken: "example-sequenceToken-123" });

// ACT
const result = await sut("testLoggerName", "testLogGroupName");
await result([
{ timestamp: 1, message: "test log message to cloudwatch 1" },
]);
await result([
{ timestamp: 2, message: "test log message to cloudwatch 2" },
]);
const putLogEventsStub = mockCloudWatchClient.commandCalls(PutLogEventsCommand);

// ASSERT
moqPutLogEvents.verify(
(x) =>
x(
It.is<CloudWatchLogs.Types.PutLogEventsRequest>(
(r) =>
r.logEvents[0].message === "test log message to cloudwatch 1" &&
r.sequenceToken === undefined
),
It.isAny()
),
Times.once()
);
expect(putLogEventsStub[0].args[0].input.logEvents).toEqual([{ timestamp: 1, message: "test log message to cloudwatch 1" }]);
expect(putLogEventsStub[0].args[0].input.sequenceToken).toBeUndefined();

moqPutLogEvents.verify(
(x) =>
x(
It.is<CloudWatchLogs.Types.PutLogEventsRequest>(
(r) =>
r.logEvents[0].message === "test log message to cloudwatch 2" &&
r.sequenceToken === "example-sequenceToken-123"
),
It.isAny()
),
Times.once()
);
expect(putLogEventsStub[1].args[0].input.logEvents).toEqual([{ timestamp: 2, message: "test log message to cloudwatch 2" }]);
expect(putLogEventsStub[1].args[0].input.sequenceToken).toEqual("example-sequenceToken-123");
});

it("should swallow a `ResourceAlreadyExistsException` error", async () => {
awsSdkMock.remock("CloudWatchLogs", "createLogStream", async () => {
throw {
errorType: "ResourceAlreadyExistsException",
};
});
const mockCloudWatchClient = mockClient(CloudWatchLogsClient);
mockCloudWatchClient.on(CreateLogStreamCommand).rejects({name: "ResourceAlreadyExistsException"});

// ACT
const result = await sut("testLoggerName", "testLogGroupName");

// ASSERT
expect(result).toBeDefined();
});

it("should throw on any other exceptions", async () => {
awsSdkMock.remock("CloudWatchLogs", "createLogStream", async () => {
throw {
errorType: "SomeOtherException",
};
});

const mockCloudWatchClient = mockClient(CloudWatchLogsClient);
mockCloudWatchClient.on(CreateLogStreamCommand).rejects({name: "SomeOtherException"});

let errorThrown: any | undefined = undefined;
let wasErrorThrown = false;

// ACT
try {
await sut("testLoggerName", "testLogGroupName");
} catch (e) {
errorThrown = e;
wasErrorThrown = true;
}

// ASSERT
expect(wasErrorThrown).toEqual(true);
expect(errorThrown).toBeDefined();
expect(errorThrown.errorType).toEqual("SomeOtherException");
expect(errorThrown.name).toEqual("SomeOtherException");
});
});

Expand Down
41 changes: 23 additions & 18 deletions src/functions/postLogs/framework/createLogger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CloudWatchLogs } from "aws-sdk";
import { CloudWatchLogsClient, CreateLogStreamCommand, PutLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs";
import { randomBytes } from "crypto";
import LogEvent from "../application/LogEvent";
import Logger, { LogDelegate } from "../application/Logger";
Expand All @@ -13,7 +13,7 @@ export function uniqueLogStreamName(loggerName: string): string {
}

function ignoreResourceAlreadyExistsException(err: any) {
if ((err.errorType || err.code) !== "ResourceAlreadyExistsException") {
if ((err.errorType || err.name) !== "ResourceAlreadyExistsException") {
throw err;
}
}
Expand All @@ -22,27 +22,32 @@ export async function createCloudWatchLogger(
loggerName: string,
logGroupName: string
): Promise<LogDelegate> {
const cloudWatchLogs = new CloudWatchLogs();
const client = new CloudWatchLogsClient();
const logStreamName = uniqueLogStreamName(loggerName);
const input = { // CreateLogStreamRequest
logGroupName: logGroupName,
logStreamName: logStreamName
};

await cloudWatchLogs
.createLogStream({ logGroupName, logStreamName })
.promise()
.catch(ignoreResourceAlreadyExistsException);
const command = new CreateLogStreamCommand(input);
try {
await client.send(command);
} catch(err) {
ignoreResourceAlreadyExistsException(err);
}

let sequenceToken: CloudWatchLogs.SequenceToken | undefined = undefined;
let sequenceToken: string | undefined = undefined;

const cloudWatchLogger = async (logEvents: LogEvent[]) => {
const logResult = await cloudWatchLogs
.putLogEvents({
logEvents,
logGroupName,
logStreamName,
sequenceToken,
})
.promise();

sequenceToken = logResult.nextSequenceToken;
const input = {
logGroupName: logGroupName,
logStreamName: logStreamName,
logEvents: logEvents,
sequenceToken: sequenceToken,
};
const command = new PutLogEventsCommand(input);
const logResult = await client.send(command);
sequenceToken = logResult.nextSequenceToken; //nextSequenceToken is deprecated
};

console.log(
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"lib": ["es2017", "dom"],
"target": "es5",
"types": ["node", "jasmine"],
"experimentalDecorators": true
"experimentalDecorators": true,
"skipLibCheck": true
},
"include": ["./src/**/*", "./spec/**/*"],
"exclude": ["node_modules"]
Expand Down

0 comments on commit 942b99c

Please sign in to comment.