Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"AWS.command.ec2.stopInstance": "Stop EC2 Instance",
"AWS.command.ec2.rebootInstance": "Reboot EC2 Instance",
"AWS.command.ec2.copyInstanceId": "Copy Instance Id",
"AWS.command.ec2.viewLogs": "View Current EC2 System Logs",
"AWS.command.ecr.copyTagUri": "Copy Tag URI",
"AWS.command.ecr.copyRepositoryUri": "Copy Repository URI",
"AWS.command.ecr.createRepository": "Create Repository...",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { CancellationError } from '../../shared/utilities/timeoutUtils'
import { telemetry } from '../../shared/telemetry/telemetry'
import { showInputBox } from '../../shared/ui/inputPrompter'
import { createURIFromArgs, isLogStreamUri, recordTelemetryFilter } from './cloudWatchLogsUtils'
import { cwlUriSchema, isLogStreamUri, recordTelemetryFilter } from './cloudWatchLogsUtils'
import { prepareDocument } from './commands/searchLogGroup'
import { getActiveDocumentUri } from './document/logDataDocumentProvider'
import { CloudWatchLogsData, filterLogEventsFromUri, LogDataRegistry } from './registry/logDataRegistry'
Expand Down Expand Up @@ -98,7 +98,7 @@ export async function changeLogSearchParams(
throw new CancellationError('user')
}

const newUri = createURIFromArgs(newData.logGroupInfo, newData.parameters)
const newUri = cwlUriSchema.form({ logGroupInfo: newData.logGroupInfo, parameters: newData.parameters })
await prepareDocument(newUri, newData, registry)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { telemetry } from '../../shared/telemetry/telemetry'
import * as vscode from 'vscode'
import { CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants'
import { fromExtensionManifest } from '../../shared/settings'
import { CloudWatchLogsData, CloudWatchLogsGroupInfo } from './registry/logDataRegistry'
import { CloudWatchLogsArgs, CloudWatchLogsData, CloudWatchLogsGroupInfo } from './registry/logDataRegistry'
import { CloudWatchLogsParameters } from './registry/logDataRegistry'
import { UriSchema } from '../../shared/utilities/uriUtils'

// URIs are the only vehicle for delivering information to a TextDocumentContentProvider.
// The following functions are used to structure and destructure relevant information to/from a URI.
Expand All @@ -32,8 +33,7 @@ export function recordTelemetryFilter(logData: CloudWatchLogsData): void {
export function uriToKey(uri: vscode.Uri): string {
if (uri.query) {
try {
const { filterPattern, startTime, endTime, limit, streamNameOptions } =
parseCloudWatchLogsUri(uri).parameters
const { filterPattern, startTime, endTime, limit, streamNameOptions } = cwlUriSchema.parse(uri).parameters
const parts = [uri.path, filterPattern, startTime, endTime, limit, streamNameOptions]
return parts.map((p) => p ?? '').join(':')
} catch {
Expand All @@ -52,18 +52,15 @@ export function uriToKey(uri: vscode.Uri): string {
* message as the actual log group search. That results in a more fluid UX.
*/
export function msgKey(logGroupInfo: CloudWatchLogsGroupInfo): string {
const uri = createURIFromArgs(logGroupInfo, {})
const uri = cwlUriSchema.form({ logGroupInfo: logGroupInfo, parameters: {} })
return uri.toString()
}

/**
* Destructures an awsCloudWatchLogs URI into its component pieces.
* @param uri URI for a Cloudwatch Logs file
*/
export function parseCloudWatchLogsUri(uri: vscode.Uri): {
logGroupInfo: CloudWatchLogsGroupInfo
parameters: CloudWatchLogsParameters
} {
function parseCloudWatchLogsUri(uri: vscode.Uri): CloudWatchLogsArgs {
const parts = uri.path.split(':')

if (uri.scheme !== CLOUDWATCH_LOGS_SCHEME) {
Expand All @@ -85,18 +82,8 @@ export function parseCloudWatchLogsUri(uri: vscode.Uri): {
}
}

/** True if given URI is a valid Cloud Watch Logs Uri */
export function isCwlUri(uri: vscode.Uri): boolean {
try {
parseCloudWatchLogsUri(uri)
return true
} catch {
return false
}
}

export function patternFromCwlUri(uri: vscode.Uri): CloudWatchLogsParameters['filterPattern'] {
return parseCloudWatchLogsUri(uri).parameters.filterPattern
return cwlUriSchema.parse(uri).parameters.filterPattern
}

/**
Expand All @@ -105,7 +92,7 @@ export function patternFromCwlUri(uri: vscode.Uri): CloudWatchLogsParameters['fi
* @returns
*/
export function isLogStreamUri(uri: vscode.Uri): boolean {
const logGroupInfo = parseCloudWatchLogsUri(uri).logGroupInfo
const logGroupInfo = cwlUriSchema.parse(uri).logGroupInfo
return logGroupInfo.streamName !== undefined
}

Expand All @@ -115,15 +102,13 @@ export function isLogStreamUri(uri: vscode.Uri): boolean {
* @param streamName Log stream name
* @param regionName AWS region
*/
export function createURIFromArgs(
logGroupInfo: CloudWatchLogsGroupInfo,
parameters: CloudWatchLogsParameters
): vscode.Uri {
let uriStr = `${CLOUDWATCH_LOGS_SCHEME}:${logGroupInfo.regionName}:${logGroupInfo.groupName}`
uriStr += logGroupInfo.streamName ? `:${logGroupInfo.streamName}` : ''
function createURIFromArgs(args: CloudWatchLogsArgs): vscode.Uri {
let uriStr = `${CLOUDWATCH_LOGS_SCHEME}:${args.logGroupInfo.regionName}:${args.logGroupInfo.groupName}`
uriStr += args.logGroupInfo.streamName ? `:${args.logGroupInfo.streamName}` : ''

uriStr += `?${encodeURIComponent(JSON.stringify(parameters))}`
uriStr += `?${encodeURIComponent(JSON.stringify(args.parameters))}`
return vscode.Uri.parse(uriStr)
}
export const cwlUriSchema = new UriSchema<CloudWatchLogsArgs>(parseCloudWatchLogsUri, createURIFromArgs)

export class CloudWatchLogsSettings extends fromExtensionManifest('aws.cwl', { limit: Number }) {}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as nls from 'vscode-nls'
const localize = nls.loadMessageBundle()

import * as vscode from 'vscode'
import { isLogStreamUri, parseCloudWatchLogsUri } from '../cloudWatchLogsUtils'
import { cwlUriSchema, isLogStreamUri } from '../cloudWatchLogsUtils'
import { copyToClipboard } from '../../../shared/utilities/messages'

export async function copyLogResource(uri?: vscode.Uri): Promise<void> {
Expand All @@ -20,7 +20,7 @@ export async function copyLogResource(uri?: vscode.Uri): Promise<void> {
throw new Error('no active text editor, or undefined URI')
}
}
const parsedUri = parseCloudWatchLogsUri(uri)
const parsedUri = cwlUriSchema.parse(uri)
const resourceName = isLogStreamUri(uri) ? parsedUri.logGroupInfo.streamName : parsedUri.logGroupInfo.groupName

if (!resourceName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as vscode from 'vscode'
import * as nls from 'vscode-nls'
const localize = nls.loadMessageBundle()

import { isLogStreamUri, parseCloudWatchLogsUri } from '../cloudWatchLogsUtils'
import { cwlUriSchema, isLogStreamUri } from '../cloudWatchLogsUtils'
import { telemetry, CloudWatchResourceType, Result } from '../../../shared/telemetry/telemetry'
import fs from '../../../shared/fs/fs'

Expand All @@ -28,7 +28,7 @@ export async function saveCurrentLogDataContent(): Promise<void> {
const workspaceDir = vscode.workspace.workspaceFolders
? vscode.workspace.workspaceFolders[0].uri
: vscode.Uri.file(fs.getUserHomeDir())
const uriComponents = parseCloudWatchLogsUri(uri)
const uriComponents = cwlUriSchema.parse(uri)
const logGroupInfo = uriComponents.logGroupInfo

const localizedLogFile = localize('AWS.command.saveCurrentLogDataContent.logfile', 'Log File')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '../registry/logDataRegistry'
import { DataQuickPickItem } from '../../../shared/ui/pickerPrompter'
import { isValidResponse, isWizardControl, Wizard, WIZARD_RETRY } from '../../../shared/wizards/wizard'
import { createURIFromArgs, msgKey, parseCloudWatchLogsUri, recordTelemetryFilter } from '../cloudWatchLogsUtils'
import { cwlUriSchema, msgKey, recordTelemetryFilter } from '../cloudWatchLogsUtils'
import { DefaultCloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogsClient'
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
import { getLogger } from '../../../shared/logger'
Expand All @@ -29,6 +29,7 @@ import { createBackButton, createExitButton, createHelpButton } from '../../../s
import { PromptResult } from '../../../shared/ui/prompter'
import { ToolkitError } from '../../../shared/errors'
import { Messages } from '../../../shared/utilities/messages'
import { showFile } from '../../../shared/utilities/textDocumentUtilities'

const localize = nls.loadMessageBundle()

Expand Down Expand Up @@ -65,9 +66,7 @@ export async function prepareDocument(uri: vscode.Uri, logData: CloudWatchLogsDa
try {
// Gets the data: calls filterLogEventsFromUri().
await registry.fetchNextLogEvents(uri)
const doc = await vscode.workspace.openTextDocument(uri)
await vscode.window.showTextDocument(doc, { preview: false })
await vscode.languages.setTextDocumentLanguage(doc, 'log')
await showFile(uri)
} catch (err) {
if (CancellationError.isUserCancelled(err)) {
throw err
Expand All @@ -78,7 +77,7 @@ export async function prepareDocument(uri: vscode.Uri, logData: CloudWatchLogsDa
localize(
'AWS.cwl.searchLogGroup.errorRetrievingLogs',
'Failed to get logs for {0}',
parseCloudWatchLogsUri(uri).logGroupInfo.groupName
cwlUriSchema.parse(uri).logGroupInfo.groupName
)
)
}
Expand All @@ -105,7 +104,7 @@ export async function searchLogGroup(
}

const userResponse = handleWizardResponse(response, registry)
const uri = createURIFromArgs(userResponse.logGroupInfo, userResponse.parameters)
const uri = cwlUriSchema.form({ logGroupInfo: userResponse.logGroupInfo, parameters: userResponse.parameters })
await prepareDocument(uri, userResponse, registry)
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import {
initLogData as initLogData,
filterLogEventsFromUri,
} from '../registry/logDataRegistry'
import { createURIFromArgs } from '../cloudWatchLogsUtils'
import { prepareDocument, searchLogGroup } from './searchLogGroup'
import { telemetry, Result } from '../../../shared/telemetry/telemetry'
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
import { formatLocalized } from '../../../shared/utilities/textUtilities'
import { cwlUriSchema } from '../cloudWatchLogsUtils'

export async function viewLogStream(node: LogGroupNode, registry: LogDataRegistry): Promise<void> {
await telemetry.cloudwatchlogs_open.run(async (span) => {
Expand All @@ -52,7 +52,7 @@ export async function viewLogStream(node: LogGroupNode, registry: LogDataRegistr
limit: registry.configuration.get('limit', 10000),
}

const uri = createURIFromArgs(logGroupInfo, parameters)
const uri = cwlUriSchema.form({ logGroupInfo: logGroupInfo, parameters: parameters })
const logData = initLogData(logGroupInfo, parameters, filterLogEventsFromUri)
await prepareDocument(uri, logData, registry)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import * as vscode from 'vscode'
import { CloudWatchLogsGroupInfo, LogDataRegistry, UriString } from '../registry/logDataRegistry'
import { getLogger } from '../../../shared/logger'
import { isCwlUri } from '../cloudWatchLogsUtils'
import { generateTextFromLogEvents, LineToLogStreamMap } from './textContent'
import { cwlUriSchema } from '../cloudWatchLogsUtils'

export class LogDataDocumentProvider implements vscode.TextDocumentContentProvider {
/** Resolves the correct {@link LineToLogStreamMap} instance for a given URI */
Expand All @@ -26,7 +26,7 @@ export class LogDataDocumentProvider implements vscode.TextDocumentContentProvid
}

public provideTextDocumentContent(uri: vscode.Uri): string {
if (!isCwlUri(uri)) {
if (!cwlUriSchema.isValid(uri)) {
throw new Error(`Uri is not a CWL Uri, so no text can be provided: ${uri.toString()}`)
}
const events = this.registry.fetchCachedLogEvents(uri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@
import * as vscode from 'vscode'
import { CLOUDWATCH_LOGS_SCHEME } from '../../../shared/constants'
import { CloudWatchLogsGroupInfo, LogDataRegistry } from '../registry/logDataRegistry'
import {
CloudWatchLogsSettings,
createURIFromArgs,
isLogStreamUri,
parseCloudWatchLogsUri,
} from '../cloudWatchLogsUtils'
import { CloudWatchLogsSettings, cwlUriSchema, isLogStreamUri } from '../cloudWatchLogsUtils'
import { LogDataDocumentProvider } from './logDataDocumentProvider'

type IdWithLine = { streamId: string; lineNum: number }
Expand Down Expand Up @@ -43,7 +38,7 @@ export class LogStreamCodeLensProvider implements vscode.CodeLensProvider {
return []
}

const logGroupInfo = parseCloudWatchLogsUri(uri).logGroupInfo
const logGroupInfo = cwlUriSchema.parse(uri).logGroupInfo

if (logGroupInfo.streamName) {
// This means we have a stream file not a log search.
Expand All @@ -64,7 +59,11 @@ export class LogStreamCodeLensProvider implements vscode.CodeLensProvider {
createLogStreamCodeLens(logGroupInfo: CloudWatchLogsGroupInfo, idWithLine: IdWithLine): vscode.CodeLens {
const settings = new CloudWatchLogsSettings()
const limit = settings.get('limit', 1000)
const streamUri = createURIFromArgs({ ...logGroupInfo, streamName: idWithLine.streamId }, { limit: limit })
const cwlArgs = {
logGroupInfo: { ...logGroupInfo, streamName: idWithLine.streamId },
parameters: { limit: limit },
}
const streamUri = cwlUriSchema.form(cwlArgs)
const cmd: vscode.Command = {
command: 'aws.loadLogStreamFile',
arguments: [streamUri, this.registry],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as vscode from 'vscode'
import { CloudWatchLogs } from 'aws-sdk'
import { CloudWatchLogsSettings, parseCloudWatchLogsUri, uriToKey, msgKey } from '../cloudWatchLogsUtils'
import { CloudWatchLogsSettings, uriToKey, msgKey, cwlUriSchema } from '../cloudWatchLogsUtils'
import { DefaultCloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogsClient'
import { waitTimeout } from '../../../shared/utilities/timeoutUtils'
import { Messages } from '../../../shared/utilities/messages'
Expand Down Expand Up @@ -190,7 +190,7 @@ export class LogDataRegistry {
if (this.isRegistered(uri)) {
throw new Error(`Already registered: ${uri.toString()}`)
}
const data = parseCloudWatchLogsUri(uri)
const data = cwlUriSchema.parse(uri)
this.setLogData(uri, initLogData(data.logGroupInfo, data.parameters, retrieveLogsFunction))
}

Expand Down Expand Up @@ -281,6 +281,11 @@ export function initLogData(
}
}

export type CloudWatchLogsArgs = {
logGroupInfo: CloudWatchLogsGroupInfo
parameters: CloudWatchLogsParameters
}

export type CloudWatchLogsGroupInfo = {
groupName: string
regionName: string
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/awsService/ec2/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import * as vscode from 'vscode'
import { ExtContext } from '../../shared/extensions'
import { Commands } from '../../shared/vscode/commands2'
import { telemetry } from '../../shared/telemetry/telemetry'
Expand All @@ -15,10 +16,16 @@ import {
startInstance,
stopInstance,
refreshExplorer,
openLogDocument,
linkToLaunchInstance,
} from './commands'
import { ec2LogsScheme } from '../../shared/constants'
import { Ec2LogDocumentProvider } from './ec2LogDocumentProvider'

export async function activate(ctx: ExtContext): Promise<void> {
ctx.extensionContext.subscriptions.push(
vscode.workspace.registerTextDocumentContentProvider(ec2LogsScheme, new Ec2LogDocumentProvider())
)
ctx.extensionContext.subscriptions.push(
Commands.register('aws.ec2.openTerminal', async (node?: Ec2InstanceNode) => {
await telemetry.ec2_connectToInstance.run(async (span) => {
Expand All @@ -30,6 +37,9 @@ export async function activate(ctx: ExtContext): Promise<void> {
Commands.register('aws.ec2.copyInstanceId', async (node: Ec2InstanceNode) => {
await copyTextCommand(node, 'id')
}),
Commands.register('aws.ec2.viewLogs', async (node?: Ec2InstanceNode) => {
await openLogDocument(node)
}),

Commands.register('aws.ec2.openRemoteConnection', async (node?: Ec2Node) => {
await openRemoteConnection(node)
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/awsService/ec2/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { Ec2InstanceNode } from './explorer/ec2InstanceNode'
import { Ec2Node } from './explorer/ec2ParentNode'
import { Ec2ConnectionManager } from './model'
import { Ec2Prompter, instanceFilter, Ec2Selection } from './prompter'
import { SafeEc2Instance, Ec2Client } from '../../shared/clients/ec2Client'
import { copyToClipboard } from '../../shared/utilities/messages'
import { getLogger } from '../../shared/logger'
import { ec2LogSchema } from './ec2LogDocumentProvider'
import { getAwsConsoleUrl } from '../../shared/awsConsole'
import { showRegionPrompter } from '../../auth/utils'
import { openUrl } from '../../shared/utilities/vsCodeUtils'
import { showFile } from '../../shared/utilities/textDocumentUtilities'

export function refreshExplorer(node?: Ec2Node) {
if (node) {
Expand Down Expand Up @@ -71,3 +72,7 @@ async function getSelection(node?: Ec2Node, filter?: instanceFilter): Promise<Ec
export async function copyInstanceId(instanceId: string): Promise<void> {
await copyToClipboard(instanceId, 'Id')
}

export async function openLogDocument(node?: Ec2InstanceNode): Promise<void> {
return await showFile(ec2LogSchema.form(await getSelection(node)))
}
Loading
Loading