Skip to content

Commit 2a28c2e

Browse files
committed
added unit tests for retry and max retry mechanism
1 parent 6d98cda commit 2a28c2e

File tree

2 files changed

+138
-22
lines changed

2 files changed

+138
-22
lines changed

packages/core/__tests__/Providers/AWSCloudWatchProvider-test.ts

Lines changed: 119 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
DescribeLogStreamsCommand,
1515
PutLogEventsCommand,
1616
} from '@aws-sdk/client-cloudwatch-logs';
17+
import { loggers } from 'winston';
1718

1819
const credentials = {
1920
accessKeyId: 'accessKeyId',
@@ -178,33 +179,52 @@ describe('AWSCloudWatchProvider', () => {
178179
});
179180

180181
describe('pushLogs test', () => {
182+
let provider;
183+
let mockInitiateLogPushInterval;
184+
beforeEach(() => {
185+
provider = new AWSCloudWatchProvider(testConfig);
186+
mockInitiateLogPushInterval = jest
187+
.spyOn(provider as any, '_initiateLogPushInterval')
188+
.mockImplementation();
189+
});
190+
afterEach(() => {
191+
jest.clearAllMocks();
192+
});
181193
it('should add the provided logs to the log queue', () => {
182-
const provider = new AWSCloudWatchProvider(testConfig);
183194
provider.pushLogs([{ message: 'hello', timestamp: 1111 }]);
184195

185196
let logQueue = provider.getLogQueue();
186-
187197
expect(logQueue).toHaveLength(1);
188198

189199
provider.pushLogs([
190-
{
191-
message: 'goodbye',
192-
timestamp: 1112,
193-
},
194-
{
195-
message: 'ohayou',
196-
timestamp: 1113,
197-
},
198-
{
199-
message: 'konbanwa',
200-
timestamp: 1114,
201-
},
200+
{ message: 'goodbye', timestamp: 1112 },
201+
{ message: 'ohayou', timestamp: 1113 },
202+
{ message: 'konbanwa', timestamp: 1114 },
202203
]);
203204

204205
logQueue = provider.getLogQueue();
205-
206206
expect(logQueue).toHaveLength(4);
207207
});
208+
it('should reset retry mechanism when _maxRetriesReached is true', () => {
209+
provider['_maxRetriesReached'] = true;
210+
provider['_retryCount'] = 6;
211+
212+
provider.pushLogs([{ message: 'test', timestamp: Date.now() }]);
213+
214+
expect(provider['_retryCount']).toBe(0);
215+
expect(provider['_maxRetriesReached']).toBe(false);
216+
expect(mockInitiateLogPushInterval).toHaveBeenCalledTimes(2);
217+
});
218+
it('should not reset retry mechanism when _maxRetriesReached is false', () => {
219+
provider['_maxRetriesReached'] = false;
220+
provider['_retryCount'] = 3;
221+
222+
provider.pushLogs([{ message: 'test', timestamp: Date.now() }]);
223+
224+
expect(provider['_retryCount']).toBe(3);
225+
expect(provider['_maxRetriesReached']).toBe(false);
226+
expect(mockInitiateLogPushInterval).toHaveBeenCalledTimes(1);
227+
});
208228
});
209229

210230
describe('_safeUploadLogEvents test', () => {
@@ -397,4 +417,88 @@ describe('AWSCloudWatchProvider', () => {
397417
});
398418
});
399419
});
420+
describe('_initiateLogPushInterval', () => {
421+
let provider: AWSCloudWatchProvider;
422+
let safeUploadLogEventsSpy: jest.SpyInstance;
423+
let getDocUploadPermissibilitySpy: jest.SpyInstance;
424+
let setIntervalSpy: jest.SpyInstance;
425+
426+
beforeEach(() => {
427+
jest.useFakeTimers();
428+
provider = new AWSCloudWatchProvider(testConfig);
429+
safeUploadLogEventsSpy = jest.spyOn(
430+
provider as any,
431+
'_safeUploadLogEvents'
432+
);
433+
getDocUploadPermissibilitySpy = jest.spyOn(
434+
provider as any,
435+
'_getDocUploadPermissibility'
436+
);
437+
setIntervalSpy = jest.spyOn(global, 'setInterval');
438+
});
439+
440+
afterEach(() => {
441+
jest.useRealTimers();
442+
jest.restoreAllMocks();
443+
});
444+
445+
it('should clear existing timer and set a new one', () => {
446+
(provider as any)._timer = setInterval(() => {}, 1000);
447+
(provider as any)._initiateLogPushInterval();
448+
449+
expect(setIntervalSpy).toHaveBeenCalledTimes(1);
450+
});
451+
452+
it('should not upload logs if max retries are reached', async () => {
453+
(provider as any)._maxRetriesReached = true;
454+
(provider as any)._initiateLogPushInterval();
455+
456+
jest.advanceTimersByTime(2000);
457+
await Promise.resolve();
458+
459+
expect(safeUploadLogEventsSpy).not.toHaveBeenCalled();
460+
});
461+
462+
it('should upload logs if conditions are met', async () => {
463+
getDocUploadPermissibilitySpy.mockReturnValue(true);
464+
safeUploadLogEventsSpy.mockResolvedValue(undefined);
465+
466+
(provider as any)._initiateLogPushInterval();
467+
468+
jest.advanceTimersByTime(2000);
469+
await Promise.resolve();
470+
471+
expect(safeUploadLogEventsSpy).toHaveBeenCalledTimes(1);
472+
expect((provider as any)._retryCount).toBe(0);
473+
});
474+
475+
it('should increment retry count on error', async () => {
476+
getDocUploadPermissibilitySpy.mockReturnValue(true);
477+
safeUploadLogEventsSpy.mockRejectedValue(new Error('Test error'));
478+
479+
(provider as any)._initiateLogPushInterval();
480+
481+
jest.advanceTimersByTime(2000);
482+
await Promise.resolve();
483+
484+
expect((provider as any)._retryCount).toBe(0);
485+
});
486+
487+
it('should stop retrying after max retries', async () => {
488+
getDocUploadPermissibilitySpy.mockReturnValue(true);
489+
safeUploadLogEventsSpy.mockRejectedValue(new Error('Test error'));
490+
(provider as any)._maxRetries = 3;
491+
492+
(provider as any)._initiateLogPushInterval();
493+
494+
for (let i = 0; i < 4; i++) {
495+
jest.advanceTimersByTime(2000);
496+
await Promise.resolve(); // Allow any pending promise to resolve
497+
}
498+
499+
expect((provider as any)._retryCount).toBe(2);
500+
501+
expect(safeUploadLogEventsSpy).toHaveBeenCalledTimes(4);
502+
});
503+
});
400504
});

packages/core/src/Providers/AWSCloudWatchProvider.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ class AWSCloudWatchProvider implements LoggingProvider {
5656
private _timer;
5757
private _nextSequenceToken: string | undefined;
5858
private _maxRetries = 5;
59-
private _retryCount;
59+
private _retryCount = 0;
60+
private _maxRetriesReached: boolean = false;
6061

6162
constructor(config?: AWSCloudWatchProviderOptions) {
6263
this.configure(config);
@@ -211,6 +212,12 @@ class AWSCloudWatchProvider implements LoggingProvider {
211212
public pushLogs(logs: InputLogEvent[]): void {
212213
logger.debug('pushing log events to Cloudwatch...');
213214
this._dataTracker.logEvents = [...this._dataTracker.logEvents, ...logs];
215+
216+
if (this._maxRetriesReached) {
217+
this._retryCount = 0;
218+
this._maxRetriesReached = false;
219+
this._initiateLogPushInterval();
220+
}
214221
}
215222

216223
private async _validateLogGroupExistsAndCreate(
@@ -493,22 +500,27 @@ class AWSCloudWatchProvider implements LoggingProvider {
493500
}
494501

495502
this._timer = setInterval(async () => {
503+
if (this._maxRetriesReached) {
504+
return;
505+
}
506+
496507
try {
497508
if (this._getDocUploadPermissibility()) {
498509
await this._safeUploadLogEvents();
499510
this._retryCount = 0;
500511
}
501512
} catch (err) {
502513
this._retryCount++;
503-
if (this._retryCount < this._maxRetries) {
514+
if (this._retryCount > this._maxRetries) {
504515
logger.error(
505-
`error when calling _safeUploadLogEvents in the timer interval - ${err}`
506-
);
507-
} else if (this._retryCount === this._maxRetries) {
508-
logger.error(
509-
`CloudWatch log upload failed after ${this._maxRetries} attempts. Suppressing further error logs. Upload attempts will continue in the background.`
516+
`Max retries (${this._maxRetries}) reached. Stopping log uploads.`
510517
);
518+
this._maxRetriesReached = true;
519+
return;
511520
}
521+
logger.error(
522+
`error when calling _safeUploadLogEvents in the timer interval - ${err}`
523+
);
512524
}
513525
}, 2000);
514526
}

0 commit comments

Comments
 (0)