Skip to content

Commit e05b4c7

Browse files
keeganirbykaranA-aws
authored andcommitted
feat(cwl): initialize tailLogGroup command. starts stream and prints logEvents to textDocument (aws#5790)
TailLogGroup currently is just logging the Users WizardResponse. It does not start a LiveTail stream or print results to the VSCode editor * Creates a `LiveTailSession` with the input params from the TailLogGroupWizard * Registers the LiveTailSession in the `LiveTailSessionRegistry` * Iterates the Response Stream and prints new LogEvents to a TextDocument * If there are no open tabs for a given session's TextDocument, the stream will be closed by triggering its AbortController and disposing its client. We will use a new URI Scheme for LiveTail text documents so we can identify a LiveTail session vs a Cloudwatch SearchLogGroup/ViewLogStream document. In the future, we will register LiveTail specific CodeLenses that depend upon this URI scheme. Defining a new URI scheme for a Text Document requires a Document Provider. In this use case, it is empty given that the response handling that writes to the document lives within the TailLogGroup command itself.
1 parent 676b28a commit e05b4c7

File tree

5 files changed

+332
-9
lines changed

5 files changed

+332
-9
lines changed

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import * as vscode from 'vscode'
7-
import { CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants'
7+
import { CLOUDWATCH_LOGS_LIVETAIL_SCHEME, CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants'
88
import { Settings } from '../../shared/settings'
99
import { addLogEvents } from './commands/addLogEvents'
1010
import { copyLogResource } from './commands/copyLogResource'
@@ -27,11 +27,14 @@ import { clearDocument, closeSession, tailLogGroup } from './commands/tailLogGr
2727
import { LiveTailDocumentProvider } from './document/liveTailDocumentProvider'
2828
import { LiveTailSessionRegistry } from './registry/liveTailSessionRegistry'
2929
import { LiveTailCodeLensProvider } from './document/liveTailCodeLensProvider'
30+
import { tailLogGroup } from './commands/tailLogGroup'
3031

3132
export async function activate(context: vscode.ExtensionContext, configuration: Settings): Promise<void> {
3233
const registry = LogDataRegistry.instance
34+
const liveTailRegistry = LiveTailSessionRegistry.instance
3335

3436
const documentProvider = new LogDataDocumentProvider(registry)
37+
const liveTailDocumentProvider = new LiveTailDocumentProvider()
3538

3639
context.subscriptions.push(
3740
vscode.languages.registerCodeLensProvider(
@@ -47,6 +50,10 @@ export async function activate(context: vscode.ExtensionContext, configuration:
4750
vscode.workspace.registerTextDocumentContentProvider(CLOUDWATCH_LOGS_SCHEME, documentProvider)
4851
)
4952

53+
context.subscriptions.push(
54+
vscode.workspace.registerTextDocumentContentProvider(CLOUDWATCH_LOGS_LIVETAIL_SCHEME, liveTailDocumentProvider)
55+
)
56+
5057
context.subscriptions.push(
5158
vscode.workspace.onDidCloseTextDocument((doc) => {
5259
if (doc.isClosed && doc.uri.scheme === CLOUDWATCH_LOGS_SCHEME) {
@@ -105,7 +112,7 @@ export async function activate(context: vscode.ExtensionContext, configuration:
105112
node instanceof LogGroupNode
106113
? { regionName: node.regionCode, groupName: node.logGroup.logGroupName! }
107114
: undefined
108-
await tailLogGroup(logGroupInfo)
115+
await tailLogGroup(liveTailRegistry, logGroupInfo)
109116
})
110117
Commands.register('aws.appBuilder.searchLogs', async (node: DeployedResourceNode) => {
111118
try {

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

Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,154 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import * as vscode from 'vscode'
67
import { TailLogGroupWizard } from '../wizard/tailLogGroupWizard'
7-
import { getLogger } from '../../../shared'
88
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
9+
import { LiveTailSession, LiveTailSessionConfiguration } from '../registry/liveTailSession'
10+
import { LiveTailSessionRegistry } from '../registry/liveTailSessionRegistry'
11+
import { LiveTailSessionLogEvent, StartLiveTailResponseStream } from '@aws-sdk/client-cloudwatch-logs'
12+
import { ToolkitError } from '../../../shared'
913

10-
export async function tailLogGroup(logData?: { regionName: string; groupName: string }): Promise<void> {
14+
export async function tailLogGroup(
15+
registry: LiveTailSessionRegistry,
16+
logData?: { regionName: string; groupName: string }
17+
): Promise<void> {
1118
const wizard = new TailLogGroupWizard(logData)
1219
const wizardResponse = await wizard.run()
1320
if (!wizardResponse) {
1421
throw new CancellationError('user')
1522
}
1623

17-
//TODO: Remove Log. For testing while we aren't yet consuming the wizardResponse.
18-
getLogger().info(JSON.stringify(wizardResponse))
24+
const liveTailSessionConfig: LiveTailSessionConfiguration = {
25+
logGroupName: wizardResponse.regionLogGroupSubmenuResponse.data,
26+
logStreamFilter: wizardResponse.logStreamFilter,
27+
logEventFilterPattern: wizardResponse.filterPattern,
28+
region: wizardResponse.regionLogGroupSubmenuResponse.region,
29+
}
30+
const session = new LiveTailSession(liveTailSessionConfig)
31+
if (registry.has(session.uri)) {
32+
await prepareDocument(session)
33+
return
34+
}
35+
registry.set(session.uri, session)
36+
37+
const document = await prepareDocument(session)
38+
registerTabChangeCallback(session, registry, document)
39+
const stream = await session.startLiveTailSession()
40+
41+
await handleSessionStream(stream, document, session)
42+
}
43+
44+
export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry) {
45+
const session = registry.get(sessionUri)
46+
if (session === undefined) {
47+
throw new ToolkitError(`No LiveTail session found for URI: ${sessionUri.toString()}`)
48+
}
49+
session.stopLiveTailSession()
50+
registry.delete(sessionUri)
51+
}
52+
53+
export async function clearDocument(textDocument: vscode.TextDocument) {
54+
const edit = new vscode.WorkspaceEdit()
55+
const startPosition = new vscode.Position(0, 0)
56+
const endPosition = new vscode.Position(textDocument.lineCount, 0)
57+
edit.delete(textDocument.uri, new vscode.Range(startPosition, endPosition))
58+
await vscode.workspace.applyEdit(edit)
59+
}
60+
61+
async function prepareDocument(session: LiveTailSession): Promise<vscode.TextDocument> {
62+
const textDocument = await vscode.workspace.openTextDocument(session.uri)
63+
await clearDocument(textDocument)
64+
await vscode.window.showTextDocument(textDocument, { preview: false })
65+
await vscode.languages.setTextDocumentLanguage(textDocument, 'log')
66+
return textDocument
67+
}
68+
69+
async function handleSessionStream(
70+
stream: AsyncIterable<StartLiveTailResponseStream>,
71+
document: vscode.TextDocument,
72+
session: LiveTailSession
73+
) {
74+
try {
75+
for await (const event of stream) {
76+
if (event.sessionUpdate !== undefined && event.sessionUpdate.sessionResults !== undefined) {
77+
const formattedLogEvents = event.sessionUpdate.sessionResults.map<string>((logEvent) =>
78+
formatLogEvent(logEvent)
79+
)
80+
if (formattedLogEvents.length !== 0) {
81+
await updateTextDocumentWithNewLogEvents(formattedLogEvents, document, session.maxLines)
82+
}
83+
}
84+
}
85+
} catch (err) {
86+
throw new ToolkitError('Caught on-stream exception')
87+
}
88+
}
89+
90+
function formatLogEvent(logEvent: LiveTailSessionLogEvent): string {
91+
if (!logEvent.timestamp || !logEvent.message) {
92+
return ''
93+
}
94+
const timestamp = new Date(logEvent.timestamp).toLocaleTimeString('en', {
95+
timeStyle: 'medium',
96+
hour12: false,
97+
timeZone: 'UTC',
98+
})
99+
let line = timestamp.concat('\t', logEvent.message)
100+
if (!line.endsWith('\n')) {
101+
line = line.concat('\n')
102+
}
103+
return line
104+
}
105+
106+
async function updateTextDocumentWithNewLogEvents(
107+
formattedLogEvents: string[],
108+
document: vscode.TextDocument,
109+
maxLines: number
110+
) {
111+
const edit = new vscode.WorkspaceEdit()
112+
formattedLogEvents.forEach((formattedLogEvent) =>
113+
edit.insert(document.uri, new vscode.Position(document.lineCount, 0), formattedLogEvent)
114+
)
115+
await vscode.workspace.applyEdit(edit)
116+
}
117+
118+
/**
119+
* The LiveTail session should be automatically closed if the user does not have the session's
120+
* document in any Tab in their editor.
121+
*
122+
* `onDidCloseTextDocument` doesn't work for our case because the tailLogGroup command will keep the stream
123+
* writing to the doc even when all its tabs/editors are closed, seemingly keeping the doc 'open'.
124+
* Also there is no guarantee that this event fires when an editor tab is closed
125+
*
126+
* `onDidChangeVisibleTextEditors` returns editors that the user can see its contents. An editor that is open, but hidden
127+
* from view, will not be returned. Meaning a Tab that is created (shown in top bar), but not open, will not be returned. Even if
128+
* the tab isn't visible, we want to continue writing to the doc, and keep the session alive.
129+
*/
130+
function registerTabChangeCallback(
131+
session: LiveTailSession,
132+
registry: LiveTailSessionRegistry,
133+
document: vscode.TextDocument
134+
) {
135+
vscode.window.tabGroups.onDidChangeTabs((tabEvent) => {
136+
const isOpen = isLiveTailSessionOpenInAnyTab(session)
137+
if (!isOpen) {
138+
closeSession(session.uri, registry)
139+
void clearDocument(document)
140+
}
141+
})
142+
}
143+
144+
function isLiveTailSessionOpenInAnyTab(liveTailSession: LiveTailSession) {
145+
let isOpen = false
146+
vscode.window.tabGroups.all.forEach(async (tabGroup) => {
147+
tabGroup.tabs.forEach((tab) => {
148+
if (tab.input instanceof vscode.TabInputText) {
149+
if (liveTailSession.uri.toString() === tab.input.uri.toString()) {
150+
isOpen = true
151+
}
152+
}
153+
})
154+
})
155+
return isOpen
19156
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
8+
export class LiveTailDocumentProvider implements vscode.TextDocumentContentProvider {
9+
provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult<string> {
10+
//Content will be written to the document via handling a LiveTail response stream in the TailLogGroup command.
11+
return ''
12+
}
13+
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55
import * as vscode from 'vscode'
6-
import { CloudWatchLogsClient, StartLiveTailCommand, StartLiveTailCommandOutput } from '@aws-sdk/client-cloudwatch-logs'
6+
import {
7+
CloudWatchLogsClient,
8+
StartLiveTailCommand,
9+
StartLiveTailResponseStream,
10+
} from '@aws-sdk/client-cloudwatch-logs'
711
import { LogStreamFilterResponse } from '../wizard/liveTailLogStreamSubmenu'
812
import { CloudWatchLogsSettings } from '../cloudWatchLogsUtils'
913
import { Settings, ToolkitError } from '../../../shared'
@@ -58,12 +62,16 @@ export class LiveTailSession {
5862
return this._logGroupName
5963
}
6064

61-
public startLiveTailSession(): Promise<StartLiveTailCommandOutput> {
65+
public async startLiveTailSession(): Promise<AsyncIterable<StartLiveTailResponseStream>> {
6266
const command = this.buildStartLiveTailCommand()
6367
try {
64-
return this.liveTailClient.cwlClient.send(command, {
68+
const commandOutput = await this.liveTailClient.cwlClient.send(command, {
6569
abortSignal: this.liveTailClient.abortController.signal,
6670
})
71+
if (!commandOutput.responseStream) {
72+
throw new ToolkitError('LiveTail session response stream is undefined.')
73+
}
74+
return commandOutput.responseStream
6775
} catch (e) {
6876
throw new ToolkitError('Encountered error while trying to start LiveTail session.')
6977
}

0 commit comments

Comments
 (0)