Skip to content

Commit 495538b

Browse files
author
Keegan Irby
committed
Create LiveTailSession object and registry. Adds MaxLine configuration
1 parent e231ec4 commit 495538b

File tree

10 files changed

+3656
-2184
lines changed

10 files changed

+3656
-2184
lines changed

package-lock.json

Lines changed: 3351 additions & 2183 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@
459459
},
460460
"dependencies": {
461461
"@amzn/codewhisperer-streaming": "file:../../src.gen/@amzn/codewhisperer-streaming",
462+
"@aws-sdk/client-cloudwatch-logs": "^3.215.0",
462463
"@aws-sdk/client-cognito-identity": "^3.637.0",
463464
"@aws-sdk/client-lambda": "^3.637.0",
464465
"@aws-sdk/client-sso": "^3.342.0",

packages/core/package.nls.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
"AWS.command.downloadSchemaItemCode": "Download Code Bindings",
155155
"AWS.command.viewLogs": "View Logs",
156156
"AWS.command.cloudWatchLogs.searchLogGroup": "Search Log Group",
157+
"AWS.command.cloudWatchLogs.tailLogGroup": "Tail Log Group",
157158
"AWS.command.sam.newTemplate": "Create new SAM Template",
158159
"AWS.command.cloudFormation.newTemplate": "Create new CloudFormation Template",
159160
"AWS.command.quickStart": "View Quick Start",
@@ -231,6 +232,7 @@
231232
"AWS.cdk.explorerTitle": "CDK",
232233
"AWS.codecatalyst.explorerTitle": "CodeCatalyst",
233234
"AWS.cwl.limit.desc": "Maximum amount of log entries pulled per request from CloudWatch Logs (max 10000)",
235+
"AWS.cwl.liveTailMaxEvents.desc": "Maximum amount of log entries that can be kept in a single live tail session. When the limit is reached, the oldest events will be removed to accomodate new events (min 1000; max 10000)",
234236
"AWS.samcli.deploy.bucket.recentlyUsed": "Buckets recently used for SAM deployments",
235237
"AWS.submenu.amazonqEditorContextSubmenu.title": "Amazon Q",
236238
"AWS.submenu.auth.title": "Authentication",

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,7 @@ export function createURIFromArgs(
126126
return vscode.Uri.parse(uriStr)
127127
}
128128

129-
export class CloudWatchLogsSettings extends fromExtensionManifest('aws.cwl', { limit: Number }) {}
129+
export class CloudWatchLogsSettings extends fromExtensionManifest('aws.cwl', {
130+
limit: Number,
131+
liveTailMaxEvents: Number,
132+
}) {}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import * as vscode from 'vscode'
6+
import { CloudWatchLogsClient, StartLiveTailCommand, StartLiveTailCommandOutput } from '@aws-sdk/client-cloudwatch-logs'
7+
import { LogStreamFilterResponse } from '../liveTailLogStreamSubmenu'
8+
import { CloudWatchLogsSettings } from '../cloudWatchLogsUtils'
9+
import { Settings } from '../../../shared'
10+
import { createLiveTailURIFromArgs } from './liveTailSessionRegistry'
11+
12+
export type LiveTailSessionConfiguration = {
13+
logGroupName: string
14+
logStreamFilter?: LogStreamFilterResponse
15+
logEventFilterPattern?: string
16+
region: string
17+
}
18+
19+
export type LiveTailSessionClient = {
20+
cwlClient: CloudWatchLogsClient
21+
abortController: AbortController
22+
}
23+
24+
export class LiveTailSession {
25+
private liveTailClient: LiveTailSessionClient
26+
private _logGroupName: string
27+
private logStreamFilter?: LogStreamFilterResponse
28+
private logEventFilterPattern?: string
29+
private _maxLines: number
30+
private _uri: vscode.Uri
31+
32+
static settings = new CloudWatchLogsSettings(Settings.instance)
33+
34+
public constructor(configuration: LiveTailSessionConfiguration) {
35+
this._logGroupName = configuration.logGroupName
36+
this.logStreamFilter = configuration.logStreamFilter
37+
this.liveTailClient = {
38+
cwlClient: new CloudWatchLogsClient({ region: configuration.region }),
39+
abortController: new AbortController(),
40+
}
41+
this._maxLines = LiveTailSession.settings.get('liveTailMaxEvents', 10000)
42+
this._uri = createLiveTailURIFromArgs(configuration)
43+
}
44+
45+
public get maxLines() {
46+
return this._maxLines
47+
}
48+
49+
public get uri() {
50+
return this._uri
51+
}
52+
53+
public get logGroupName() {
54+
return this._logGroupName
55+
}
56+
57+
public startLiveTailSession(): Promise<StartLiveTailCommandOutput> {
58+
const command = this.buildStartLiveTailCommand()
59+
return this.liveTailClient.cwlClient.send(command, {
60+
abortSignal: this.liveTailClient.abortController.signal,
61+
})
62+
}
63+
64+
public stopLiveTailSession() {
65+
this.liveTailClient.abortController.abort()
66+
this.liveTailClient.cwlClient.destroy()
67+
}
68+
69+
private buildStartLiveTailCommand(): StartLiveTailCommand {
70+
let logStreamNamePrefix = undefined
71+
let logStreamName = undefined
72+
if (this.logStreamFilter) {
73+
if (this.logStreamFilter.type === 'prefix') {
74+
logStreamNamePrefix = this.logStreamFilter.filter
75+
logStreamName = undefined
76+
} else if (this.logStreamFilter.type === 'specific') {
77+
logStreamName = this.logStreamFilter.filter
78+
logStreamNamePrefix = undefined
79+
}
80+
}
81+
82+
return new StartLiveTailCommand({
83+
logGroupIdentifiers: [this.logGroupName],
84+
logStreamNamePrefixes: logStreamNamePrefix ? [logStreamNamePrefix] : undefined,
85+
logStreamNames: logStreamName ? [logStreamName] : undefined,
86+
logEventFilterPattern: this.logEventFilterPattern ? this.logEventFilterPattern : undefined,
87+
})
88+
}
89+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import * as vscode from 'vscode'
6+
import { CLOUDWATCH_LOGS_LIVETAIL_SCHEME } from '../../../shared/constants'
7+
import { LiveTailSession, LiveTailSessionConfiguration } from './liveTailSession'
8+
import { ToolkitError } from '../../../shared'
9+
10+
export class LiveTailSessionRegistry {
11+
static #instance: LiveTailSessionRegistry
12+
13+
public static get instance() {
14+
return (this.#instance ??= new this())
15+
}
16+
17+
public constructor(private readonly registry: Map<string, LiveTailSession> = new Map()) {}
18+
19+
public registerLiveTailSession(session: LiveTailSession) {
20+
if (this.doesRegistryContainLiveTailSession(session.uri)) {
21+
throw new ToolkitError(`There is already a LiveTail session registered with uri: ${session.uri}`)
22+
}
23+
this.registry.set(this.uriToKey(session.uri), session)
24+
}
25+
26+
public getLiveTailSessionFromUri(uri: vscode.Uri): LiveTailSession {
27+
const session = this.registry.get(this.uriToKey(uri))
28+
if (!session) {
29+
throw new ToolkitError(`No LiveTail session registered for uri: ${uri} found.`)
30+
}
31+
return session
32+
}
33+
34+
public removeLiveTailSessionFromRegistry(uri: vscode.Uri) {
35+
this.registry.delete(this.uriToKey(uri))
36+
}
37+
38+
public doesRegistryContainLiveTailSession(uri: vscode.Uri): boolean {
39+
return this.registry.has(this.uriToKey(uri))
40+
}
41+
42+
private uriToKey(uri: vscode.Uri): string {
43+
return uri.toString()
44+
}
45+
}
46+
47+
export function createLiveTailURIFromArgs(sessionData: LiveTailSessionConfiguration): vscode.Uri {
48+
let uriStr = `${CLOUDWATCH_LOGS_LIVETAIL_SCHEME}:${sessionData.region}:${sessionData.logGroupName}`
49+
50+
if (sessionData.logStreamFilter) {
51+
if (sessionData.logStreamFilter.type !== 'all') {
52+
uriStr += `:${sessionData.logStreamFilter.type}:${sessionData.logStreamFilter.filter}`
53+
} else {
54+
uriStr += `:${sessionData.logStreamFilter.type}`
55+
}
56+
}
57+
uriStr += sessionData.logEventFilterPattern ? `:${sessionData.logEventFilterPattern}` : ''
58+
59+
return vscode.Uri.parse(uriStr)
60+
}

packages/core/src/shared/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ export const ecsIamPermissionsUrl = vscode.Uri.parse(
128128
* URI scheme for CloudWatch Logs Virtual Documents
129129
*/
130130
export const CLOUDWATCH_LOGS_SCHEME = 'aws-cwl' // eslint-disable-line @typescript-eslint/naming-convention
131+
export const CLOUDWATCH_LOGS_LIVETAIL_SCHEME = 'aws-cwl-lt' // eslint-disable-line @typescript-eslint/naming-convention
132+
131133
export const AWS_SCHEME = 'aws' // eslint-disable-line @typescript-eslint/naming-convention
132134

133135
export const startLiveTailHelpUrl =

packages/core/src/shared/settings-toolkit.gen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const toolkitSettings = {
2222
"aws.stepfunctions.asl.maxItemsComputed": {},
2323
"aws.ssmDocument.ssm.maxItemsComputed": {},
2424
"aws.cwl.limit": {},
25+
"aws.cwl.liveTailMaxEvents": {},
2526
"aws.samcli.manuallySelectedBuckets": {},
2627
"aws.samcli.enableCodeLenses": {},
2728
"aws.suppressPrompts": {
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import * as vscode from 'vscode'
6+
import assert from 'assert'
7+
import {
8+
LiveTailSession,
9+
LiveTailSessionConfiguration,
10+
} from '../../../../awsService/cloudWatchLogs/registry/liveTailSession'
11+
import {
12+
createLiveTailURIFromArgs,
13+
LiveTailSessionRegistry,
14+
} from '../../../../awsService/cloudWatchLogs/registry/liveTailSessionRegistry'
15+
16+
describe('LiveTailRegistry', async function () {
17+
const session = new LiveTailSession({
18+
logGroupName: 'test-log-group',
19+
region: 'test-region',
20+
})
21+
22+
let liveTailSessionRegistry: LiveTailSessionRegistry
23+
24+
beforeEach(function () {
25+
liveTailSessionRegistry = new LiveTailSessionRegistry()
26+
})
27+
28+
it('returns LiveTailSession after setting it', async function () {
29+
liveTailSessionRegistry.registerLiveTailSession(session)
30+
const retrievedSession = liveTailSessionRegistry.getLiveTailSessionFromUri(session.uri)
31+
assert.strictEqual(retrievedSession, session)
32+
})
33+
34+
it('contains returns true after setting session', async function () {
35+
liveTailSessionRegistry.registerLiveTailSession(session)
36+
const doesContain = liveTailSessionRegistry.doesRegistryContainLiveTailSession(session.uri)
37+
assert.strictEqual(doesContain, true)
38+
})
39+
40+
it('contains returns false if session not set', async function () {
41+
const doesContain = liveTailSessionRegistry.doesRegistryContainLiveTailSession(session.uri)
42+
assert.strictEqual(doesContain, false)
43+
})
44+
45+
it('removeLiveTailSessionFromRegistry removes session from registry ', async function () {
46+
liveTailSessionRegistry.registerLiveTailSession(session)
47+
assert.strictEqual(liveTailSessionRegistry.doesRegistryContainLiveTailSession(session.uri), true)
48+
liveTailSessionRegistry.removeLiveTailSessionFromRegistry(session.uri)
49+
assert.strictEqual(liveTailSessionRegistry.doesRegistryContainLiveTailSession(session.uri), false)
50+
})
51+
52+
it('throws registering the same session twice', async function () {
53+
liveTailSessionRegistry.registerLiveTailSession(session)
54+
assert.throws(() => liveTailSessionRegistry.registerLiveTailSession(session))
55+
})
56+
57+
it('throws cant find session', async function () {
58+
assert.throws(() => liveTailSessionRegistry.getLiveTailSessionFromUri(session.uri))
59+
})
60+
})
61+
62+
describe('LiveTailSession URI', async function () {
63+
it('is correct with no logStream filter, no filter pattern', function () {
64+
const config: LiveTailSessionConfiguration = {
65+
logGroupName: 'test-log-group',
66+
region: 'test-region',
67+
}
68+
const expectedUri = vscode.Uri.parse('aws-cwl-lt:test-region:test-log-group')
69+
const uri = createLiveTailURIFromArgs(config)
70+
assert.deepEqual(uri, expectedUri)
71+
})
72+
73+
it('is correct with no logStream filter, with filter pattern', function () {
74+
const config: LiveTailSessionConfiguration = {
75+
logGroupName: 'test-log-group',
76+
region: 'test-region',
77+
logEventFilterPattern: 'test-filter',
78+
}
79+
const expectedUri = vscode.Uri.parse('aws-cwl-lt:test-region:test-log-group:test-filter')
80+
const uri = createLiveTailURIFromArgs(config)
81+
assert.deepEqual(uri, expectedUri)
82+
})
83+
84+
it('is correct with ALL logStream filter', function () {
85+
const config: LiveTailSessionConfiguration = {
86+
logGroupName: 'test-log-group',
87+
region: 'test-region',
88+
logStreamFilter: {
89+
type: 'all',
90+
},
91+
}
92+
const expectedUri = vscode.Uri.parse('aws-cwl-lt:test-region:test-log-group:all')
93+
const uri = createLiveTailURIFromArgs(config)
94+
assert.deepEqual(uri, expectedUri)
95+
})
96+
97+
it('is correct with prefix logStream filter', function () {
98+
const config: LiveTailSessionConfiguration = {
99+
logGroupName: 'test-log-group',
100+
region: 'test-region',
101+
logStreamFilter: {
102+
type: 'prefix',
103+
filter: 'test-prefix',
104+
},
105+
}
106+
const expectedUri = vscode.Uri.parse('aws-cwl-lt:test-region:test-log-group:prefix:test-prefix')
107+
const uri = createLiveTailURIFromArgs(config)
108+
assert.deepEqual(uri, expectedUri)
109+
})
110+
111+
it('is correct with specific logStream filter', function () {
112+
const config: LiveTailSessionConfiguration = {
113+
logGroupName: 'test-log-group',
114+
region: 'test-region',
115+
logStreamFilter: {
116+
type: 'specific',
117+
filter: 'test-stream',
118+
},
119+
}
120+
const expectedUri = vscode.Uri.parse('aws-cwl-lt:test-region:test-log-group:specific:test-stream')
121+
const uri = createLiveTailURIFromArgs(config)
122+
assert.deepEqual(uri, expectedUri)
123+
})
124+
125+
it('is correct with specific logStream filter and filter pattern', function () {
126+
const config: LiveTailSessionConfiguration = {
127+
logGroupName: 'test-log-group',
128+
region: 'test-region',
129+
logStreamFilter: {
130+
type: 'specific',
131+
filter: 'test-stream',
132+
},
133+
logEventFilterPattern: 'test-filter',
134+
}
135+
const expectedUri = vscode.Uri.parse('aws-cwl-lt:test-region:test-log-group:specific:test-stream:test-filter')
136+
const uri = createLiveTailURIFromArgs(config)
137+
assert.deepEqual(uri, expectedUri)
138+
})
139+
})

packages/toolkit/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@
162162
"description": "%AWS.cwl.limit.desc%",
163163
"maximum": 10000
164164
},
165+
"aws.cwl.liveTailMaxEvents": {
166+
"type": "number",
167+
"default": 10000,
168+
"description": "%AWS.cwl.liveTailMaxEvents.desc%",
169+
"minimum": 1000,
170+
"maximum": 10000
171+
},
165172
"aws.samcli.manuallySelectedBuckets": {
166173
"type": "object",
167174
"description": "%AWS.samcli.deploy.bucket.recentlyUsed%",

0 commit comments

Comments
 (0)