Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ec2): provide link for customer to view system logs #5633

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
20 changes: 20 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,10 @@
{
"command": "aws.toolkit.auth.manageConnections"
},
{
"command": "aws.ec2.viewLogs",
"when": "aws.isDevMode"
},
{
"command": "aws.ec2.openRemoteConnection",
"when": "aws.isDevMode"
Expand Down Expand Up @@ -1556,6 +1560,11 @@
"when": "view == aws.explorer && viewItem =~ /^(awsEc2(Parent|Running|Stopped)Node)$/",
"group": "2@0"
},
{
"command": "aws.ec2.viewLogs",
"when": "view == aws.explorer && viewItem =~ /^(awsEc2(Pending|Running|Stopped)Node)$/",
"group": "3@0"
},
{
"command": "aws.ecr.copyTagUri",
"when": "view == aws.explorer && viewItem == awsEcrTagNode",
Expand Down Expand Up @@ -2241,6 +2250,17 @@
}
}
},
{
"command": "aws.ec2.viewLogs",
"title": "%AWS.command.ec2.viewLogs%",
"category": "%AWS.title%",
"enablement": "isCloud9 || !aws.isWebExtHost",
"cloud9": {
"cn": {
"category": "%AWS.title.cn%"
}
}
},
{
"command": "aws.ec2.openTerminal",
"title": "%AWS.command.ec2.openTerminal%",
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,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 Down Expand Up @@ -78,7 +78,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 +105,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,9 +16,15 @@ import {
startInstance,
stopInstance,
refreshExplorer,
openLogDocument,
} from './commands'
import { EC2_LOGS_SCHEME } from '../../shared/constants'
import { Ec2LogDocumentProvider } from './ec2LogDocumentProvider'

export async function activate(ctx: ExtContext): Promise<void> {
ctx.extensionContext.subscriptions.push(
vscode.workspace.registerTextDocumentContentProvider(EC2_LOGS_SCHEME, new Ec2LogDocumentProvider())
)
ctx.extensionContext.subscriptions.push(
Commands.register('aws.ec2.openTerminal', async (node?: Ec2InstanceNode) => {
await telemetry.ec2_connectToInstance.run(async (span) => {
Expand All @@ -29,6 +36,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
10 changes: 9 additions & 1 deletion packages/core/src/awsService/ec2/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import * as vscode from 'vscode'
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'

export function refreshExplorer(node?: Ec2Node) {
if (node) {
Expand Down Expand Up @@ -62,3 +63,10 @@ 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> {
const uri = ec2LogSchema.form(await getSelection(node))
const doc = await vscode.workspace.openTextDocument(uri)
await vscode.window.showTextDocument(doc, { preview: false })
await vscode.languages.setTextDocumentLanguage(doc, 'log')
}
Loading
Loading