@bitblit/ratchet-aws
Version:
Common tools for use with AWS browser and node
174 lines • 8 kB
JavaScript
import { CloudWatchLogsClient, DeleteLogGroupCommand, DeleteLogStreamCommand, DescribeLogGroupsCommand, DescribeLogStreamsCommand, GetQueryResultsCommand, OrderBy, StartQueryCommand, StopQueryCommand, } from '@aws-sdk/client-cloudwatch-logs';
import { Logger } from '@bitblit/ratchet-common/logger/logger';
import { PromiseRatchet } from '@bitblit/ratchet-common/lang/promise-ratchet';
import { RequireRatchet } from '@bitblit/ratchet-common/lang/require-ratchet';
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
export class CloudWatchLogsRatchet {
static MAX_DELETE_RETRIES = 5;
cwLogs;
constructor(cloudwatchLogs = null) {
this.cwLogs = cloudwatchLogs ? cloudwatchLogs : new CloudWatchLogsClient({ region: 'us-east-1' });
}
get cloudWatchLogsClient() {
return this.cwLogs;
}
async removeEmptyOrOldLogStreams(logGroupName, maxToRemove = 1000, oldestEventEpochMS = null) {
Logger.info('Removing empty streams from %s, oldest event epoch MS : %d', logGroupName, oldestEventEpochMS);
const streamSearchParams = {
logGroupName: logGroupName,
orderBy: OrderBy.LastEventTime,
};
const oldestEventTester = oldestEventEpochMS || 1;
let totalStreams = 0;
const removedStreams = [];
const failedRemovedStreams = [];
let waitPerDescribe = 10;
do {
Logger.debug('Executing search for streams');
try {
const streams = await this.cwLogs.send(new DescribeLogStreamsCommand(streamSearchParams));
totalStreams += streams.logStreams.length;
Logger.debug('Found %d streams (%d so far, %d to delete)', streams.logStreams.length, totalStreams, removedStreams.length);
for (let i = 0; i < streams.logStreams.length && removedStreams.length < maxToRemove; i++) {
const st = streams.logStreams[i];
if (!st.firstEventTimestamp) {
removedStreams.push(st);
}
else if (st.lastEventTimestamp < oldestEventTester) {
removedStreams.push(st);
}
}
streamSearchParams['nextToken'] = streams.nextToken;
}
catch (err) {
const oldWait = waitPerDescribe;
waitPerDescribe = Math.min(1000, waitPerDescribe * 1.5);
Logger.info('Caught while describing %s, increasing wait between deletes (was %d, now %d)', err, oldWait, waitPerDescribe);
}
} while (!!streamSearchParams['nextToken'] && removedStreams.length < maxToRemove);
Logger.info('Found %d streams to delete', removedStreams.length);
let waitPer = 10;
for (const rStream of removedStreams) {
const delParams = {
logGroupName: logGroupName,
logStreamName: rStream.logStreamName,
};
const type = rStream.storedBytes === 0 ? 'empty' : 'old';
Logger.info('Removing %s stream %s', type, rStream.logStreamName);
let removed = false;
let retry = 0;
while (!removed && retry < CloudWatchLogsRatchet.MAX_DELETE_RETRIES) {
try {
await this.cwLogs.send(new DeleteLogStreamCommand(delParams));
removed = true;
await PromiseRatchet.wait(waitPer);
}
catch (err) {
retry++;
const oldWait = waitPer;
waitPer = Math.min(1000, waitPer * 1.5);
Logger.info('Caught %s, increasing wait between deletes and retrying (wait was %d, now %d) (Retry %d of %d)', err, oldWait, waitPer, retry, CloudWatchLogsRatchet.MAX_DELETE_RETRIES);
}
}
if (!removed) {
failedRemovedStreams.push(rStream);
}
}
Logger.warn('Failed to remove streams : %j', failedRemovedStreams);
return removedStreams;
}
async findOldestEventTimestampInGroup(logGroupName) {
const stream = await this.findStreamWithOldestEventInGroup(logGroupName);
return stream ? stream.firstEventTimestamp : null;
}
async findStreamWithOldestEventInGroup(logGroupName) {
Logger.info('Finding oldest event in : %s', logGroupName);
let rval = null;
try {
const streamSearchParams = {
logGroupName: logGroupName,
orderBy: OrderBy.LastEventTime,
};
let totalStreams = 0;
do {
Logger.debug('Executing search for streams');
const streams = await this.cwLogs.send(new DescribeLogStreamsCommand(streamSearchParams));
totalStreams += streams.logStreams.length;
Logger.debug('Found %d streams (%d so far)', streams.logStreams.length, totalStreams);
streams.logStreams.forEach((s) => {
if (s.firstEventTimestamp && (rval === null || s.firstEventTimestamp < rval.firstEventTimestamp)) {
rval = s;
}
});
streamSearchParams['nextToken'] = streams.nextToken;
} while (streamSearchParams['nextToken']);
}
catch (err) {
Logger.error('Error attempting to find oldest event in group : %s : %s', logGroupName, err, err);
}
return rval;
}
async findLogGroups(prefix) {
RequireRatchet.notNullOrUndefined(prefix);
const params = {
logGroupNamePrefix: prefix,
};
let rval = [];
do {
Logger.info('%d found, pulling log groups : %j', rval.length, params);
const res = await this.cwLogs.send(new DescribeLogGroupsCommand(params));
rval = rval.concat(res.logGroups);
params.nextToken = res.nextToken;
} while (params.nextToken);
return rval;
}
async removeLogGroups(groups) {
RequireRatchet.notNullOrUndefined(groups);
const rval = [];
for (const dGroup of groups) {
try {
Logger.info('Deleting %j', dGroup);
const req = {
logGroupName: dGroup.logGroupName,
};
await this.cwLogs.send(new DeleteLogGroupCommand(req));
rval.push(true);
}
catch (err) {
Logger.error('Failure to delete %j : %s', dGroup, err);
rval.push(false);
}
}
return rval;
}
async removeLogGroupsWithPrefix(prefix) {
RequireRatchet.notNullOrUndefined(prefix);
RequireRatchet.true(StringRatchet.trimToEmpty(prefix).length > 0);
Logger.info('Removing log groups with prefix %s', prefix);
const groups = await this.findLogGroups(prefix);
return await this.removeLogGroups(groups);
}
async fullyExecuteInsightsQuery(sqr) {
RequireRatchet.notNullOrUndefined(sqr);
Logger.debug('Starting insights query : %j', sqr);
const resp = await this.cwLogs.send(new StartQueryCommand(sqr));
Logger.debug('Got query id %j', resp);
let rval = null;
let delayMS = 100;
while (!rval || ['Running', 'Scheduled'].includes(rval.status)) {
rval = await this.cwLogs.send(new GetQueryResultsCommand({ queryId: resp.queryId }));
await PromiseRatchet.wait(delayMS);
delayMS *= 2;
Logger.info('Got : %j', rval);
}
return rval;
}
async abortInsightsQuery(queryId) {
let rval = null;
if (queryId) {
rval = await this.cwLogs.send(new StopQueryCommand({ queryId: queryId }));
}
return rval;
}
}
//# sourceMappingURL=cloud-watch-logs-ratchet.js.map