From 4a7a75f92d2041f3949421de4353411d249b8881 Mon Sep 17 00:00:00 2001 From: Keegan Irby Date: Thu, 7 Nov 2024 18:14:29 -0800 Subject: [PATCH 1/5] Add clear screen and stop session code lens --- .../awsService/cloudWatchLogs/activation.ts | 21 +++++++- .../cloudWatchLogs/commands/tailLogGroup.ts | 46 ++++++++++------- .../document/liveTailCodeLensProvider.ts | 51 +++++++++++++++++++ .../registry/liveTailSession.ts | 14 ++++- .../registry/liveTailSessionRegistry.ts | 2 +- .../commands/tailLogGroup.test.ts | 7 ++- 6 files changed, 115 insertions(+), 26 deletions(-) create mode 100644 packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts diff --git a/packages/core/src/awsService/cloudWatchLogs/activation.ts b/packages/core/src/awsService/cloudWatchLogs/activation.ts index 75951604bea..de75d9e72a4 100644 --- a/packages/core/src/awsService/cloudWatchLogs/activation.ts +++ b/packages/core/src/awsService/cloudWatchLogs/activation.ts @@ -19,13 +19,14 @@ import { searchLogGroup } from './commands/searchLogGroup' import { changeLogSearchParams } from './changeLogSearch' import { CloudWatchLogsNode } from './explorer/cloudWatchLogsNode' import { loadAndOpenInitialLogStreamFile, LogStreamCodeLensProvider } from './document/logStreamsCodeLensProvider' -import { tailLogGroup } from './commands/tailLogGroup' +import { clearDocument, closeSession, tailLogGroup } from './commands/tailLogGroup' import { LiveTailDocumentProvider } from './document/liveTailDocumentProvider' import { LiveTailSessionRegistry } from './registry/liveTailSessionRegistry' import { DeployedResourceNode } from '../appBuilder/explorer/nodes/deployedNode' import { isTreeNode } from '../../shared/treeview/resourceTreeDataProvider' import { getLogger } from '../../shared/logger/logger' import { ToolkitError } from '../../shared' +import { LiveTailCodeLensProvider } from './document/liveTailCodeLensProvider' export async function activate(context: vscode.ExtensionContext, configuration: Settings): Promise { const registry = LogDataRegistry.instance @@ -48,6 +49,16 @@ export async function activate(context: vscode.ExtensionContext, configuration: vscode.workspace.registerTextDocumentContentProvider(CLOUDWATCH_LOGS_SCHEME, documentProvider) ) + context.subscriptions.push( + vscode.languages.registerCodeLensProvider( + { + language: 'log', + scheme: cloudwatchLogsLiveTailScheme, + }, + new LiveTailCodeLensProvider() + ) + ) + context.subscriptions.push( vscode.workspace.registerTextDocumentContentProvider(cloudwatchLogsLiveTailScheme, liveTailDocumentProvider) ) @@ -112,6 +123,14 @@ export async function activate(context: vscode.ExtensionContext, configuration: await tailLogGroup(liveTailRegistry, logGroupInfo) }), + Commands.register('aws.cwl.stopTailingLogGroup', async (document: vscode.TextDocument) => { + closeSession(document.uri, liveTailRegistry) + }), + + Commands.register('aws.cwl.clearDocument', async (document: vscode.TextDocument) => { + await clearDocument(document) + }), + Commands.register('aws.appBuilder.searchLogs', async (node: DeployedResourceNode) => { try { const logGroupInfo = isTreeNode(node) diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts index 69ab4569186..e2d6cd7b972 100644 --- a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts +++ b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts @@ -13,7 +13,7 @@ import { LiveTailSessionUpdate, StartLiveTailResponseStream, } from '@aws-sdk/client-cloudwatch-logs' -import { globals, ToolkitError } from '../../../shared' +import { getLogger, ToolkitError } from '../../../shared' export async function tailLogGroup( registry: LiveTailSessionRegistry, @@ -32,32 +32,29 @@ export async function tailLogGroup( region: wizardResponse.regionLogGroupSubmenuResponse.region, } const session = new LiveTailSession(liveTailSessionConfig) - if (registry.has(session.uri)) { + if (registry.has(session.uri.toString())) { await prepareDocument(session) return } - registry.set(session.uri, session) + registry.set(session.uri.toString(), session) const document = await prepareDocument(session) - const timer = globals.clock.setInterval(() => { - session.updateStatusBarItemText() - }, 500) + hideShowStatusBarItemsOnActiveEditor(session, document) - registerTabChangeCallback(session, registry, document, timer) + registerTabChangeCallback(session, registry, document) const stream = await session.startLiveTailSession() - await handleSessionStream(stream, document, session, timer) + await handleSessionStream(stream, document, session) } -export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry, timer: NodeJS.Timer) { - globals.clock.clearInterval(timer) - const session = registry.get(sessionUri) +export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry) { + const session = registry.get(sessionUri.toString()) if (session === undefined) { throw new ToolkitError(`No LiveTail session found for URI: ${sessionUri.toString()}`) } session.stopLiveTailSession() - registry.delete(sessionUri) + registry.delete(sessionUri.toString()) } export async function clearDocument(textDocument: vscode.TextDocument) { @@ -80,8 +77,7 @@ async function prepareDocument(session: LiveTailSession): Promise, document: vscode.TextDocument, - session: LiveTailSession, - timer: NodeJS.Timer + session: LiveTailSession ) { try { for await (const event of stream) { @@ -100,8 +96,21 @@ async function handleSessionStream( session.isSampled = isSampled(event.sessionUpdate) } } - } finally { - globals.clock.clearInterval(timer) + } catch (e) { + if (session.isAborted) { + //Expected case. User action cancelled stream (CodeLens, Close Editor, etc.). + //AbortSignal interrupts the LiveTail stream, causing error to be thrown here. + //Can assume that stopLiveTailSession() has already been called - AbortSignal is only + //exposed through that method. + getLogger().info(`Session ${session.uri.toString()} stopped.`) + } else { + //Unexpected exception. + session.stopLiveTailSession() + throw ToolkitError.chain( + e, + `Unexpected on-stream execption while tailing session: ${session.uri.toString()}` + ) + } } } @@ -196,13 +205,12 @@ function hideShowStatusBarItemsOnActiveEditor(session: LiveTailSession, document function registerTabChangeCallback( session: LiveTailSession, registry: LiveTailSessionRegistry, - document: vscode.TextDocument, - timer: NodeJS.Timer + document: vscode.TextDocument ) { vscode.window.tabGroups.onDidChangeTabs((tabEvent) => { const isOpen = isLiveTailSessionOpenInAnyTab(session) if (!isOpen) { - closeSession(session.uri, registry, timer) + closeSession(session.uri, registry) void clearDocument(document) } }) diff --git a/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts new file mode 100644 index 00000000000..428a472b593 --- /dev/null +++ b/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts @@ -0,0 +1,51 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants' + +export class LiveTailCodeLensProvider implements vscode.CodeLensProvider { + onDidChangeCodeLenses?: vscode.Event | undefined + + provideCodeLenses( + document: vscode.TextDocument, + token: vscode.CancellationToken + ): vscode.ProviderResult { + const uri = document.uri + if (uri.scheme !== cloudwatchLogsLiveTailScheme) { + return [] + } + const codeLenses: vscode.CodeLens[] = [] + codeLenses.push(this.buildClearDocumentCodeLens(document)) + codeLenses.push(this.buildStopTailingCodeLens(document)) + return codeLenses + } + + private buildClearDocumentCodeLens(document: vscode.TextDocument): vscode.CodeLens { + const range = new vscode.Range( + new vscode.Position(document.lineCount - 1, 0), + new vscode.Position(document.lineCount - 1, 0) + ) + const command: vscode.Command = { + title: 'Clear document', + command: 'aws.cwl.clearDocument', + arguments: [document], + } + return new vscode.CodeLens(range, command) + } + + private buildStopTailingCodeLens(document: vscode.TextDocument): vscode.CodeLens { + const range = new vscode.Range( + new vscode.Position(document.lineCount - 1, 0), + new vscode.Position(document.lineCount - 1, 0) + ) + const command: vscode.Command = { + title: 'Stop tailing', + command: 'aws.cwl.stopTailingLogGroup', + arguments: [document], + } + return new vscode.CodeLens(range, command) + } +} diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts index 7e4b64ccc57..f01e389bf3e 100644 --- a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts +++ b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts @@ -10,7 +10,7 @@ import { } from '@aws-sdk/client-cloudwatch-logs' import { LogStreamFilterResponse } from '../wizard/liveTailLogStreamSubmenu' import { CloudWatchLogsSettings } from '../cloudWatchLogsUtils' -import { convertToTimeString, Settings, ToolkitError } from '../../../shared' +import { convertToTimeString, globals, Settings, ToolkitError } from '../../../shared' import { createLiveTailURIFromArgs } from './liveTailSessionRegistry' import { getUserAgent } from '../../../shared/telemetry/util' @@ -39,6 +39,9 @@ export class LiveTailSession { private _eventRate: number private _isSampled: boolean + //While session is running, used to update the StatusBar each half second. + private statusBarUpdateTimer: NodeJS.Timer | undefined + static settings = new CloudWatchLogsSettings(Settings.instance) public constructor(configuration: LiveTailSessionConfiguration) { @@ -89,6 +92,10 @@ export class LiveTailSession { } this.startTime = Date.now() this.endTime = undefined + this.statusBarUpdateTimer = globals.clock.setInterval(() => { + this.updateStatusBarItemText() + }, 500) + return commandOutput.responseStream } catch (e) { throw new ToolkitError('Encountered error while trying to start LiveTail session.') @@ -98,6 +105,7 @@ export class LiveTailSession { public stopLiveTailSession() { this.endTime = Date.now() this.statusBarItem.dispose() + globals.clock.clearInterval(this.statusBarUpdateTimer) this.liveTailClient.abortController.abort() this.liveTailClient.cwlClient.destroy() } @@ -145,4 +153,8 @@ export class LiveTailSession { const sampledString = this._isSampled ? 'Yes' : 'No' this.statusBarItem.text = `Tailing: ${timeString}, ${this._eventRate} events/sec, Sampled: ${sampledString}` } + + public get isAborted() { + return this.liveTailClient.abortController.signal.aborted + } } diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts index 2fcb2731a9c..3d5bc5c59a8 100644 --- a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts +++ b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode' import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants' import { LiveTailSession, LiveTailSessionConfiguration } from './liveTailSession' -export class LiveTailSessionRegistry extends Map { +export class LiveTailSessionRegistry extends Map { static #instance: LiveTailSessionRegistry public static get instance() { diff --git a/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts index 2a5897db4cc..de73c806954 100644 --- a/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts +++ b/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts @@ -125,15 +125,14 @@ describe('TailLogGroup', function () { .callsFake(async function () { return }) - // const fakeClock = installFakeClock() - const timer = setInterval(() => {}, 1000) + const session = new LiveTailSession({ logGroupName: testLogGroup, region: testRegion, }) - registry.set(session.uri, session) + registry.set(session.uri.toString(), session) - closeSession(session.uri, registry, timer) + closeSession(session.uri, registry) assert.strictEqual(0, registry.size) assert.strictEqual(true, stopLiveTailSessionSpy.calledOnce) assert.strictEqual(0, clock.countTimers()) From 1f8d9dd4bdb659dae842d10715ffdd562245a33b Mon Sep 17 00:00:00 2001 From: Keegan Irby Date: Thu, 7 Nov 2024 19:56:07 -0800 Subject: [PATCH 2/5] Remove redundant range variable --- .../document/liveTailCodeLensProvider.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts index 428a472b593..7c7bb1cd74c 100644 --- a/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts +++ b/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts @@ -24,10 +24,7 @@ export class LiveTailCodeLensProvider implements vscode.CodeLensProvider { } private buildClearDocumentCodeLens(document: vscode.TextDocument): vscode.CodeLens { - const range = new vscode.Range( - new vscode.Position(document.lineCount - 1, 0), - new vscode.Position(document.lineCount - 1, 0) - ) + const range = this.getBottomOfDocumentRange(document) const command: vscode.Command = { title: 'Clear document', command: 'aws.cwl.clearDocument', @@ -37,10 +34,7 @@ export class LiveTailCodeLensProvider implements vscode.CodeLensProvider { } private buildStopTailingCodeLens(document: vscode.TextDocument): vscode.CodeLens { - const range = new vscode.Range( - new vscode.Position(document.lineCount - 1, 0), - new vscode.Position(document.lineCount - 1, 0) - ) + const range = this.getBottomOfDocumentRange(document) const command: vscode.Command = { title: 'Stop tailing', command: 'aws.cwl.stopTailingLogGroup', @@ -48,4 +42,11 @@ export class LiveTailCodeLensProvider implements vscode.CodeLensProvider { } return new vscode.CodeLens(range, command) } + + private getBottomOfDocumentRange(document: vscode.TextDocument): vscode.Range { + return new vscode.Range( + new vscode.Position(document.lineCount - 1, 0), + new vscode.Position(document.lineCount - 1, 0) + ) + } } From 104fb2cf2787e0ec5ec95390c73410c849377f43 Mon Sep 17 00:00:00 2001 From: Keegan Irby Date: Fri, 8 Nov 2024 13:32:30 -0800 Subject: [PATCH 3/5] Update packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts Co-authored-by: Justin M. Keyes --- .../core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts index e2d6cd7b972..ed1c0949fd0 100644 --- a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts +++ b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts @@ -108,7 +108,7 @@ async function handleSessionStream( session.stopLiveTailSession() throw ToolkitError.chain( e, - `Unexpected on-stream execption while tailing session: ${session.uri.toString()}` + `Unexpected on-stream exception while tailing session: ${session.uri.toString()}` ) } } From 432ecf1be83266ee2eed4d79e231068ecf593274 Mon Sep 17 00:00:00 2001 From: Keegan Irby Date: Fri, 8 Nov 2024 13:32:40 -0800 Subject: [PATCH 4/5] Update packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts Co-authored-by: Justin M. Keyes --- .../core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts index ed1c0949fd0..fdcb6ba0031 100644 --- a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts +++ b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts @@ -102,7 +102,7 @@ async function handleSessionStream( //AbortSignal interrupts the LiveTail stream, causing error to be thrown here. //Can assume that stopLiveTailSession() has already been called - AbortSignal is only //exposed through that method. - getLogger().info(`Session ${session.uri.toString()} stopped.`) + getLogger().info(`Session stopped: ${session.uri.toString()}`) } else { //Unexpected exception. session.stopLiveTailSession() From 9b2c3a75a13721f39b92cd3de0f1dc8f951d4031 Mon Sep 17 00:00:00 2001 From: Keegan Irby Date: Fri, 8 Nov 2024 14:00:46 -0800 Subject: [PATCH 5/5] use UriToKey --- .../cloudWatchLogs/commands/tailLogGroup.ts | 11 ++++++----- .../cloudWatchLogs/commands/tailLogGroup.test.ts | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts index fdcb6ba0031..74ac67fca33 100644 --- a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts +++ b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts @@ -14,6 +14,7 @@ import { StartLiveTailResponseStream, } from '@aws-sdk/client-cloudwatch-logs' import { getLogger, ToolkitError } from '../../../shared' +import { uriToKey } from '../cloudWatchLogsUtils' export async function tailLogGroup( registry: LiveTailSessionRegistry, @@ -32,11 +33,11 @@ export async function tailLogGroup( region: wizardResponse.regionLogGroupSubmenuResponse.region, } const session = new LiveTailSession(liveTailSessionConfig) - if (registry.has(session.uri.toString())) { + if (registry.has(uriToKey(session.uri))) { await prepareDocument(session) return } - registry.set(session.uri.toString(), session) + registry.set(uriToKey(session.uri), session) const document = await prepareDocument(session) @@ -49,12 +50,12 @@ export async function tailLogGroup( } export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry) { - const session = registry.get(sessionUri.toString()) + const session = registry.get(uriToKey(sessionUri)) if (session === undefined) { throw new ToolkitError(`No LiveTail session found for URI: ${sessionUri.toString()}`) } session.stopLiveTailSession() - registry.delete(sessionUri.toString()) + registry.delete(uriToKey(sessionUri)) } export async function clearDocument(textDocument: vscode.TextDocument) { @@ -102,7 +103,7 @@ async function handleSessionStream( //AbortSignal interrupts the LiveTail stream, causing error to be thrown here. //Can assume that stopLiveTailSession() has already been called - AbortSignal is only //exposed through that method. - getLogger().info(`Session stopped: ${session.uri.toString()}`) + getLogger().info(`Session stopped: ${uriToKey(session.uri)}`) } else { //Unexpected exception. session.stopLiveTailSession() diff --git a/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts index de73c806954..e364c70ce34 100644 --- a/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts +++ b/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts @@ -18,7 +18,7 @@ import { TailLogGroupWizardResponse, } from '../../../../awsService/cloudWatchLogs/wizard/tailLogGroupWizard' import { getTestWindow } from '../../../shared/vscode/window' -import { CloudWatchLogsSettings } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' +import { CloudWatchLogsSettings, uriToKey } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' import { installFakeClock } from '../../../testUtil' describe('TailLogGroup', function () { @@ -130,7 +130,7 @@ describe('TailLogGroup', function () { logGroupName: testLogGroup, region: testRegion, }) - registry.set(session.uri.toString(), session) + registry.set(uriToKey(session.uri), session) closeSession(session.uri, registry) assert.strictEqual(0, registry.size)