Skip to content

Commit 6b9f6d1

Browse files
keeganirbykaranA-aws
authored andcommitted
feat(cwl): LiveTail statusbar aws#5938
Problem As LiveTail session is running, we want to display to the user metadata about their session. Solution While a livetail session is the active editor, in the bottom status bar, we will display the duration the session has been running for, how many events per/s, and if the log data is being sampled or not.
1 parent 6ac28ef commit 6b9f6d1

File tree

3 files changed

+118
-21
lines changed

3 files changed

+118
-21
lines changed

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

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import { TailLogGroupWizard } from '../wizard/tailLogGroupWizard'
88
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
99
import { LiveTailSession, LiveTailSessionConfiguration } from '../registry/liveTailSession'
1010
import { LiveTailSessionRegistry } from '../registry/liveTailSessionRegistry'
11-
import { LiveTailSessionLogEvent, StartLiveTailResponseStream } from '@aws-sdk/client-cloudwatch-logs'
12-
import { ToolkitError } from '../../../shared'
11+
import {
12+
LiveTailSessionLogEvent,
13+
LiveTailSessionUpdate,
14+
StartLiveTailResponseStream,
15+
} from '@aws-sdk/client-cloudwatch-logs'
16+
import { globals, ToolkitError } from '../../../shared'
1317

1418
export async function tailLogGroup(
1519
registry: LiveTailSessionRegistry,
@@ -35,13 +39,19 @@ export async function tailLogGroup(
3539
registry.set(session.uri, session)
3640

3741
const document = await prepareDocument(session)
38-
registerTabChangeCallback(session, registry, document)
42+
const timer = globals.clock.setInterval(() => {
43+
session.updateStatusBarItemText()
44+
}, 500)
45+
hideShowStatusBarItemsOnActiveEditor(session, document)
46+
registerTabChangeCallback(session, registry, document, timer)
47+
3948
const stream = await session.startLiveTailSession()
4049

41-
await handleSessionStream(stream, document, session)
50+
await handleSessionStream(stream, document, session, timer)
4251
}
4352

44-
export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry) {
53+
export function closeSession(sessionUri: vscode.Uri, registry: LiveTailSessionRegistry, timer: NodeJS.Timer) {
54+
globals.clock.clearInterval(timer)
4555
const session = registry.get(sessionUri)
4656
if (session === undefined) {
4757
throw new ToolkitError(`No LiveTail session found for URI: ${sessionUri.toString()}`)
@@ -63,27 +73,35 @@ async function prepareDocument(session: LiveTailSession): Promise<vscode.TextDoc
6373
await clearDocument(textDocument)
6474
await vscode.window.showTextDocument(textDocument, { preview: false })
6575
await vscode.languages.setTextDocumentLanguage(textDocument, 'log')
76+
session.showStatusBarItem(true)
6677
return textDocument
6778
}
6879

6980
async function handleSessionStream(
7081
stream: AsyncIterable<StartLiveTailResponseStream>,
7182
document: vscode.TextDocument,
72-
session: LiveTailSession
83+
session: LiveTailSession,
84+
timer: NodeJS.Timer
7385
) {
74-
for await (const event of stream) {
75-
if (event.sessionUpdate !== undefined && event.sessionUpdate.sessionResults !== undefined) {
76-
const formattedLogEvents = event.sessionUpdate.sessionResults.map<string>((logEvent) =>
77-
formatLogEvent(logEvent)
78-
)
79-
if (formattedLogEvents.length !== 0) {
80-
//Determine should scroll before adding new lines to doc because adding large
81-
//amount of new lines can push bottom of file out of view before scrolling.
82-
const editorsToScroll = getTextEditorsToScroll(document)
83-
await updateTextDocumentWithNewLogEvents(formattedLogEvents, document, session.maxLines)
84-
editorsToScroll.forEach(scrollTextEditorToBottom)
86+
try {
87+
for await (const event of stream) {
88+
if (event.sessionUpdate !== undefined && event.sessionUpdate.sessionResults !== undefined) {
89+
const formattedLogEvents = event.sessionUpdate.sessionResults.map<string>((logEvent) =>
90+
formatLogEvent(logEvent)
91+
)
92+
if (formattedLogEvents.length !== 0) {
93+
//Determine should scroll before adding new lines to doc because adding large
94+
//amount of new lines can push bottom of file out of view before scrolling.
95+
const editorsToScroll = getTextEditorsToScroll(document)
96+
await updateTextDocumentWithNewLogEvents(formattedLogEvents, document, session.maxLines)
97+
editorsToScroll.forEach(scrollTextEditorToBottom)
98+
}
99+
session.eventRate = eventRate(event.sessionUpdate)
100+
session.isSampled = isSampled(event.sessionUpdate)
85101
}
86102
}
103+
} finally {
104+
globals.clock.clearInterval(timer)
87105
}
88106
}
89107

@@ -147,6 +165,22 @@ function trimOldestLines(
147165
edit.delete(document.uri, range)
148166
}
149167

168+
function isSampled(event: LiveTailSessionUpdate): boolean {
169+
return event.sessionMetadata === undefined || event.sessionMetadata.sampled === undefined
170+
? false
171+
: event.sessionMetadata.sampled
172+
}
173+
174+
function eventRate(event: LiveTailSessionUpdate): number {
175+
return event.sessionResults === undefined ? 0 : event.sessionResults.length
176+
}
177+
178+
function hideShowStatusBarItemsOnActiveEditor(session: LiveTailSession, document: vscode.TextDocument) {
179+
vscode.window.onDidChangeActiveTextEditor((editor) => {
180+
session.showStatusBarItem(editor?.document === document)
181+
})
182+
}
183+
150184
/**
151185
* The LiveTail session should be automatically closed if the user does not have the session's
152186
* document in any Tab in their editor.
@@ -162,12 +196,13 @@ function trimOldestLines(
162196
function registerTabChangeCallback(
163197
session: LiveTailSession,
164198
registry: LiveTailSessionRegistry,
165-
document: vscode.TextDocument
199+
document: vscode.TextDocument,
200+
timer: NodeJS.Timer
166201
) {
167202
vscode.window.tabGroups.onDidChangeTabs((tabEvent) => {
168203
const isOpen = isLiveTailSessionOpenInAnyTab(session)
169204
if (!isOpen) {
170-
closeSession(session.uri, registry)
205+
closeSession(session.uri, registry, timer)
171206
void clearDocument(document)
172207
}
173208
})

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

Lines changed: 44 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 { Settings, ToolkitError } from '../../../shared'
13+
import { convertToTimeString, Settings, ToolkitError } from '../../../shared'
1414
import { createLiveTailURIFromArgs } from './liveTailSessionRegistry'
1515
import { getUserAgent } from '../../../shared/telemetry/util'
1616

@@ -33,6 +33,11 @@ export class LiveTailSession {
3333
private logEventFilterPattern?: string
3434
private _maxLines: number
3535
private _uri: vscode.Uri
36+
private statusBarItem: vscode.StatusBarItem
37+
private startTime: number | undefined
38+
private endTime: number | undefined
39+
private _eventRate: number
40+
private _isSampled: boolean
3641

3742
static settings = new CloudWatchLogsSettings(Settings.instance)
3843

@@ -48,6 +53,9 @@ export class LiveTailSession {
4853
}
4954
this._maxLines = LiveTailSession.settings.get('limit', 10000)
5055
this._uri = createLiveTailURIFromArgs(configuration)
56+
this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 0)
57+
this._eventRate = 0
58+
this._isSampled = false
5159
}
5260

5361
public get maxLines() {
@@ -62,6 +70,14 @@ export class LiveTailSession {
6270
return this._logGroupName
6371
}
6472

73+
public set eventRate(rate: number) {
74+
this._eventRate = rate
75+
}
76+
77+
public set isSampled(isSampled: boolean) {
78+
this._isSampled = isSampled
79+
}
80+
6581
public async startLiveTailSession(): Promise<AsyncIterable<StartLiveTailResponseStream>> {
6682
const command = this.buildStartLiveTailCommand()
6783
try {
@@ -71,17 +87,33 @@ export class LiveTailSession {
7187
if (!commandOutput.responseStream) {
7288
throw new ToolkitError('LiveTail session response stream is undefined.')
7389
}
90+
this.startTime = Date.now()
91+
this.endTime = undefined
7492
return commandOutput.responseStream
7593
} catch (e) {
7694
throw new ToolkitError('Encountered error while trying to start LiveTail session.')
7795
}
7896
}
7997

8098
public stopLiveTailSession() {
99+
this.endTime = Date.now()
100+
this.statusBarItem.dispose()
81101
this.liveTailClient.abortController.abort()
82102
this.liveTailClient.cwlClient.destroy()
83103
}
84104

105+
public getLiveTailSessionDuration(): number {
106+
//Never started
107+
if (this.startTime === undefined) {
108+
return 0
109+
}
110+
//Currently running
111+
if (this.endTime === undefined) {
112+
return Date.now() - this.startTime
113+
}
114+
return this.endTime - this.startTime
115+
}
116+
85117
private buildStartLiveTailCommand(): StartLiveTailCommand {
86118
let logStreamNamePrefix = undefined
87119
let logStreamName = undefined
@@ -102,4 +134,15 @@ export class LiveTailSession {
102134
logEventFilterPattern: this.logEventFilterPattern ? this.logEventFilterPattern : undefined,
103135
})
104136
}
137+
138+
public showStatusBarItem(shouldShow: boolean) {
139+
shouldShow ? this.statusBarItem.show() : this.statusBarItem.hide()
140+
}
141+
142+
public updateStatusBarItemText() {
143+
const elapsedTime = this.getLiveTailSessionDuration()
144+
const timeString = convertToTimeString(elapsedTime)
145+
const sampledString = this._isSampled ? 'Yes' : 'No'
146+
this.statusBarItem.text = `Tailing: ${timeString}, ${this._eventRate} events/sec, Sampled: ${sampledString}`
147+
}
105148
}

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import * as sinon from 'sinon'
7+
import * as FakeTimers from '@sinonjs/fake-timers'
78
import * as vscode from 'vscode'
89

910
import assert from 'assert'
@@ -18,6 +19,7 @@ import {
1819
} from '../../../../awsService/cloudWatchLogs/wizard/tailLogGroupWizard'
1920
import { getTestWindow } from '../../../shared/vscode/window'
2021
import { CloudWatchLogsSettings } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils'
22+
import { installFakeClock } from '../../../testUtil'
2123

2224
describe('TailLogGroup', function () {
2325
const testLogGroup = 'test-log-group'
@@ -31,11 +33,22 @@ describe('TailLogGroup', function () {
3133
let cloudwatchSettingsSpy: sinon.SinonSpy
3234
let wizardSpy: sinon.SinonSpy
3335

36+
let clock: FakeTimers.InstalledClock
37+
38+
before(function () {
39+
clock = installFakeClock()
40+
})
41+
3442
beforeEach(function () {
43+
clock.reset()
3544
sandbox = sinon.createSandbox()
3645
registry = new LiveTailSessionRegistry()
3746
})
3847

48+
after(function () {
49+
clock.uninstall()
50+
})
51+
3952
afterEach(function () {
4053
sandbox.restore()
4154
})
@@ -112,15 +125,18 @@ describe('TailLogGroup', function () {
112125
.callsFake(async function () {
113126
return
114127
})
128+
// const fakeClock = installFakeClock()
129+
const timer = setInterval(() => {}, 1000)
115130
const session = new LiveTailSession({
116131
logGroupName: testLogGroup,
117132
region: testRegion,
118133
})
119134
registry.set(session.uri, session)
120135

121-
closeSession(session.uri, registry)
136+
closeSession(session.uri, registry, timer)
122137
assert.strictEqual(0, registry.size)
123138
assert.strictEqual(true, stopLiveTailSessionSpy.calledOnce)
139+
assert.strictEqual(0, clock.countTimers())
124140
})
125141

126142
it('clearDocument clears all text from document', async function () {
@@ -172,6 +188,9 @@ describe('TailLogGroup', function () {
172188
const updateFrames: StartLiveTailResponseStream[] = logEvents.map((event) => {
173189
return {
174190
sessionUpdate: {
191+
sessionMetadata: {
192+
sampled: false,
193+
},
175194
sessionResults: [event],
176195
},
177196
}

0 commit comments

Comments
 (0)