|
3 | 3 | * SPDX-License-Identifier: Apache-2.0 |
4 | 4 | */ |
5 | 5 |
|
| 6 | +import * as vscode from 'vscode' |
6 | 7 | import { TailLogGroupWizard } from '../wizard/tailLogGroupWizard' |
7 | | -import { getLogger } from '../../../shared' |
8 | 8 | 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' |
9 | 13 |
|
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> { |
11 | 18 | const wizard = new TailLogGroupWizard(logData) |
12 | 19 | const wizardResponse = await wizard.run() |
13 | 20 | if (!wizardResponse) { |
14 | 21 | throw new CancellationError('user') |
15 | 22 | } |
16 | 23 |
|
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 |
19 | 156 | } |
0 commit comments