Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(cb2-11286): update aws sdk to v3 #75

Merged
merged 1 commit into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/
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
143 changes: 39 additions & 104 deletions src/functions/postLogs/framework/__tests__/createLogger.spec.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,28 @@
import * as awsSdkMock from "aws-sdk-mock";
import { CloudWatchLogs, CloudWatchLogsClient, CreateLogStreamCommand, CreateLogStreamRequest, PutLogEventsCommand, PutLogEventsRequest } 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", () => {
Expand Down Expand Up @@ -77,6 +56,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 +83,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 +95,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
Loading