UNPKG

@bitblit/ratchet-aws

Version:

Common tools for use with AWS browser and node

174 lines 8 kB
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