Skip to content

Commit 4d42685

Browse files
keeganirbykaranA-aws
authored andcommitted
feat(cwl): "clear screen", "stop session" actions aws#5958
Users may want to clear their screen during a tailing session. The liveTail document is read only. Users may want to stop their session without closing the document. Provide a codeLens to clear the screen (make document empty) Provide a codeLens to stop the session without closing the document. Moves Interval Timer for updating the StatusBar to the LiveTailSession object so `stopLiveTailSession()` can interrupt it, and be guaranteed to be cleaned up. The TextDocument's URI and Session URI seem to not be equal. Looking up in the LiveTailSessionRegistry with a document URI causes a session to not be found. Converting with `toString` allows these URIs to match and the registry to work as intended. Improves Exception handling on-stream. Previously, stopping the session in an expected fashion (codeLens, Closing editors) would cause an exception to bubble up and appear to the User. New change recognizes when the Abort Controller triggers, signifying an error has not occured, and logs the event as opposed to surfacing an error. Exposing only `isAborted` so that a caller has to use `stopLiveTailSession` to trigger the abortController and guarantee that other clean up actions have taken place.
1 parent 6b9f6d1 commit 4d42685

File tree

6 files changed

+121
-31
lines changed

6 files changed

+121
-31
lines changed

packages/core/src/awsService/cloudWatchLogs/activation.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ import { searchLogGroup } from './commands/searchLogGroup'
1919
import { changeLogSearchParams } from './changeLogSearch'
2020
import { CloudWatchLogsNode } from './explorer/cloudWatchLogsNode'
2121
import { loadAndOpenInitialLogStreamFile, LogStreamCodeLensProvider } from './document/logStreamsCodeLensProvider'
22+
import { tailLogGroup } from './commands/tailLogGroup'
23+
import { LiveTailDocumentProvider } from './document/liveTailDocumentProvider'
24+
import { LiveTailSessionRegistry } from './registry/liveTailSessionRegistry'
2225
import { DeployedResourceNode } from '../appBuilder/explorer/nodes/deployedNode'
2326
import { isTreeNode } from '../../shared/treeview/resourceTreeDataProvider'
2427
import { getLogger } from '../../shared/logger/logger'
2528
import { ToolkitError } from '../../shared'
26-
import { clearDocument, closeSession, tailLogGroup } from './commands/tailLogGroup'
27-
import { LiveTailDocumentProvider } from './document/liveTailDocumentProvider'
28-
import { LiveTailSessionRegistry } from './registry/liveTailSessionRegistry'
2929
import { LiveTailCodeLensProvider } from './document/liveTailCodeLensProvider'
30-
import { tailLogGroup } from './commands/tailLogGroup'
3130

3231
export async function activate(context: vscode.ExtensionContext, configuration: Settings): Promise<void> {
3332
const registry = LogDataRegistry.instance
@@ -50,6 +49,16 @@ export async function activate(context: vscode.ExtensionContext, configuration:
5049
vscode.workspace.registerTextDocumentContentProvider(CLOUDWATCH_LOGS_SCHEME, documentProvider)
5150
)
5251

52+
context.subscriptions.push(
53+
vscode.languages.registerCodeLensProvider(
54+
{
55+
language: 'log',
56+
scheme: cloudwatchLogsLiveTailScheme,
57+
},
58+
new LiveTailCodeLensProvider()
59+
)
60+
)
61+
5362
context.subscriptions.push(
5463
vscode.workspace.registerTextDocumentContentProvider(cloudwatchLogsLiveTailScheme, liveTailDocumentProvider)
5564
)
@@ -113,7 +122,16 @@ export async function activate(context: vscode.ExtensionContext, configuration:
113122
? { regionName: node.regionCode, groupName: node.logGroup.logGroupName! }
114123
: undefined
115124
await tailLogGroup(liveTailRegistry, logGroupInfo)
116-
})
125+
}),
126+
127+
Commands.register('aws.cwl.stopTailingLogGroup', async (document: vscode.TextDocument) => {
128+
closeSession(document.uri, liveTailRegistry)
129+
}),
130+
131+
Commands.register('aws.cwl.clearDocument', async (document: vscode.TextDocument) => {
132+
await clearDocument(document)
133+
}),
134+
117135
Commands.register('aws.appBuilder.searchLogs', async (node: DeployedResourceNode) => {
118136
try {
119137
const logGroupInfo = isTreeNode(node)

packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
LiveTailSessionUpdate,
1414
StartLiveTailResponseStream,
1515
} from '@aws-sdk/client-cloudwatch-logs'
16-
import { globals, ToolkitError } from '../../../shared'
16+
import { getLogger, ToolkitError } from '../../../shared'
17+
import { uriToKey } from '../cloudWatchLogsUtils'
1718

1819
export async function tailLogGroup(
1920
registry: LiveTailSessionRegistry,
@@ -32,32 +33,29 @@ export async function tailLogGroup(
3233
region: wizardResponse.regionLogGroupSubmenuResponse.region,
3334
}
3435
const session = new LiveTailSession(liveTailSessionConfig)
35-
if (registry.has(session.uri)) {
36+
if (registry.has(uriToKey(session.uri))) {
3637
await prepareDocument(session)
3738
return
3839
}
39-
registry.set(session.uri, session)
40+
registry.set(uriToKey(session.uri), session)
4041

4142
const document = await prepareDocument(session)
42-
const timer = globals.clock.setInterval(() => {
43-
session.updateStatusBarItemText()
44-
}, 500)
43+
4544
hideShowStatusBarItemsOnActiveEditor(session, document)
46-
registerTabChangeCallback(session, registry, document, timer)
45+
registerTabChangeCallback(session, registry, document)
4746

4847
const stream = await session.startLiveTailSession()
4948

50-
await handleSessionStream(stream, document, session, timer)
49+
await handleSessionStream(stream, document, session)
5150
}
5251

53-
export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry, timer: NodeJS.Timer) {
54-
globals.clock.clearInterval(timer)
55-
const session = registry.get(sessionUri)
52+
export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry) {
53+
const session = registry.get(uriToKey(sessionUri))
5654
if (session === undefined) {
5755
throw new ToolkitError(`No LiveTail session found for URI: ${sessionUri.toString()}`)
5856
}
5957
session.stopLiveTailSession()
60-
registry.delete(sessionUri)
58+
registry.delete(uriToKey(sessionUri))
6159
}
6260

6361
export async function clearDocument(textDocument: vscode.TextDocument) {
@@ -80,8 +78,7 @@ async function prepareDocument(session: LiveTailSession): Promise<vscode.TextDoc
8078
async function handleSessionStream(
8179
stream: AsyncIterable<StartLiveTailResponseStream>,
8280
document: vscode.TextDocument,
83-
session: LiveTailSession,
84-
timer: NodeJS.Timer
81+
session: LiveTailSession
8582
) {
8683
try {
8784
for await (const event of stream) {
@@ -100,8 +97,21 @@ async function handleSessionStream(
10097
session.isSampled = isSampled(event.sessionUpdate)
10198
}
10299
}
103-
} finally {
104-
globals.clock.clearInterval(timer)
100+
} catch (e) {
101+
if (session.isAborted) {
102+
//Expected case. User action cancelled stream (CodeLens, Close Editor, etc.).
103+
//AbortSignal interrupts the LiveTail stream, causing error to be thrown here.
104+
//Can assume that stopLiveTailSession() has already been called - AbortSignal is only
105+
//exposed through that method.
106+
getLogger().info(`Session stopped: ${uriToKey(session.uri)}`)
107+
} else {
108+
//Unexpected exception.
109+
session.stopLiveTailSession()
110+
throw ToolkitError.chain(
111+
e,
112+
`Unexpected on-stream exception while tailing session: ${session.uri.toString()}`
113+
)
114+
}
105115
}
106116
}
107117

@@ -196,13 +206,12 @@ function hideShowStatusBarItemsOnActiveEditor(session: LiveTailSession, document
196206
function registerTabChangeCallback(
197207
session: LiveTailSession,
198208
registry: LiveTailSessionRegistry,
199-
document: vscode.TextDocument,
200-
timer: NodeJS.Timer
209+
document: vscode.TextDocument
201210
) {
202211
vscode.window.tabGroups.onDidChangeTabs((tabEvent) => {
203212
const isOpen = isLiveTailSessionOpenInAnyTab(session)
204213
if (!isOpen) {
205-
closeSession(session.uri, registry, timer)
214+
closeSession(session.uri, registry)
206215
void clearDocument(document)
207216
}
208217
})
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as vscode from 'vscode'
7+
import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants'
8+
9+
export class LiveTailCodeLensProvider implements vscode.CodeLensProvider {
10+
onDidChangeCodeLenses?: vscode.Event<void> | undefined
11+
12+
provideCodeLenses(
13+
document: vscode.TextDocument,
14+
token: vscode.CancellationToken
15+
): vscode.ProviderResult<vscode.CodeLens[]> {
16+
const uri = document.uri
17+
if (uri.scheme !== cloudwatchLogsLiveTailScheme) {
18+
return []
19+
}
20+
const codeLenses: vscode.CodeLens[] = []
21+
codeLenses.push(this.buildClearDocumentCodeLens(document))
22+
codeLenses.push(this.buildStopTailingCodeLens(document))
23+
return codeLenses
24+
}
25+
26+
private buildClearDocumentCodeLens(document: vscode.TextDocument): vscode.CodeLens {
27+
const range = this.getBottomOfDocumentRange(document)
28+
const command: vscode.Command = {
29+
title: 'Clear document',
30+
command: 'aws.cwl.clearDocument',
31+
arguments: [document],
32+
}
33+
return new vscode.CodeLens(range, command)
34+
}
35+
36+
private buildStopTailingCodeLens(document: vscode.TextDocument): vscode.CodeLens {
37+
const range = this.getBottomOfDocumentRange(document)
38+
const command: vscode.Command = {
39+
title: 'Stop tailing',
40+
command: 'aws.cwl.stopTailingLogGroup',
41+
arguments: [document],
42+
}
43+
return new vscode.CodeLens(range, command)
44+
}
45+
46+
private getBottomOfDocumentRange(document: vscode.TextDocument): vscode.Range {
47+
return new vscode.Range(
48+
new vscode.Position(document.lineCount - 1, 0),
49+
new vscode.Position(document.lineCount - 1, 0)
50+
)
51+
}
52+
}

packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '@aws-sdk/client-cloudwatch-logs'
1111
import { LogStreamFilterResponse } from '../wizard/liveTailLogStreamSubmenu'
1212
import { CloudWatchLogsSettings } from '../cloudWatchLogsUtils'
13-
import { convertToTimeString, Settings, ToolkitError } from '../../../shared'
13+
import { convertToTimeString, globals, Settings, ToolkitError } from '../../../shared'
1414
import { createLiveTailURIFromArgs } from './liveTailSessionRegistry'
1515
import { getUserAgent } from '../../../shared/telemetry/util'
1616

@@ -39,6 +39,9 @@ export class LiveTailSession {
3939
private _eventRate: number
4040
private _isSampled: boolean
4141

42+
//While session is running, used to update the StatusBar each half second.
43+
private statusBarUpdateTimer: NodeJS.Timer | undefined
44+
4245
static settings = new CloudWatchLogsSettings(Settings.instance)
4346

4447
public constructor(configuration: LiveTailSessionConfiguration) {
@@ -89,6 +92,10 @@ export class LiveTailSession {
8992
}
9093
this.startTime = Date.now()
9194
this.endTime = undefined
95+
this.statusBarUpdateTimer = globals.clock.setInterval(() => {
96+
this.updateStatusBarItemText()
97+
}, 500)
98+
9299
return commandOutput.responseStream
93100
} catch (e) {
94101
throw new ToolkitError('Encountered error while trying to start LiveTail session.')
@@ -98,6 +105,7 @@ export class LiveTailSession {
98105
public stopLiveTailSession() {
99106
this.endTime = Date.now()
100107
this.statusBarItem.dispose()
108+
globals.clock.clearInterval(this.statusBarUpdateTimer)
101109
this.liveTailClient.abortController.abort()
102110
this.liveTailClient.cwlClient.destroy()
103111
}
@@ -145,4 +153,8 @@ export class LiveTailSession {
145153
const sampledString = this._isSampled ? 'Yes' : 'No'
146154
this.statusBarItem.text = `Tailing: ${timeString}, ${this._eventRate} events/sec, Sampled: ${sampledString}`
147155
}
156+
157+
public get isAborted() {
158+
return this.liveTailClient.abortController.signal.aborted
159+
}
148160
}

packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as vscode from 'vscode'
66
import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants'
77
import { LiveTailSession, LiveTailSessionConfiguration } from './liveTailSession'
88

9-
export class LiveTailSessionRegistry extends Map<vscode.Uri, LiveTailSession> {
9+
export class LiveTailSessionRegistry extends Map<string, LiveTailSession> {
1010
static #instance: LiveTailSessionRegistry
1111

1212
public static get instance() {

packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
TailLogGroupWizardResponse,
1919
} from '../../../../awsService/cloudWatchLogs/wizard/tailLogGroupWizard'
2020
import { getTestWindow } from '../../../shared/vscode/window'
21-
import { CloudWatchLogsSettings } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils'
21+
import { CloudWatchLogsSettings, uriToKey } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils'
2222
import { installFakeClock } from '../../../testUtil'
2323

2424
describe('TailLogGroup', function () {
@@ -125,15 +125,14 @@ describe('TailLogGroup', function () {
125125
.callsFake(async function () {
126126
return
127127
})
128-
// const fakeClock = installFakeClock()
129-
const timer = setInterval(() => {}, 1000)
128+
130129
const session = new LiveTailSession({
131130
logGroupName: testLogGroup,
132131
region: testRegion,
133132
})
134-
registry.set(session.uri, session)
133+
registry.set(uriToKey(session.uri), session)
135134

136-
closeSession(session.uri, registry, timer)
135+
closeSession(session.uri, registry)
137136
assert.strictEqual(0, registry.size)
138137
assert.strictEqual(true, stopLiveTailSessionSpy.calledOnce)
139138
assert.strictEqual(0, clock.countTimers())

0 commit comments

Comments
 (0)