Skip to content

Commit be4816e

Browse files
authored
feat(sam): improve rendering of SAM output #4285
Problem: `vscode.LogOutputChannel` is now available via `createOutputChannel(..., { log: true })`, for improved highlighting and presentation of log-like output in VSCode, but AWS Toolkit still uses the old `vscode.OutputChannel` interface. Solution: - Use `vscode.LogOutputChannel` methods when possible. - samCliLocalInvoke.ts: log `onStderr` to "info" log-level instead of "error".
1 parent 9143363 commit be4816e

File tree

8 files changed

+50
-11
lines changed

8 files changed

+50
-11
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "SAM lambda debugging: SAM output and other logs are now presented with VSCode's log highlighting"
4+
}

src/codewhisperer/models/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export const connectionExpired = `Connection expired. To continue using Amazon Q
266266
export const DoNotShowAgain = `Don\'t Show Again`
267267

268268
export const codeScanLogsOutputChannelId =
269-
'workbench.action.output.show.extension-output-amazonwebservices.aws-toolkit-vscode-#2-CodeWhisperer Security Scan Logs'
269+
'workbench.action.output.show.extension-output-amazonwebservices.aws-toolkit-vscode-#1-CodeWhisperer Security Scan Logs'
270270

271271
export const stopScanMessage =
272272
'Stop security scan? This scan will be counted as one complete scan towards your monthly security scan limits.'

src/extensionWeb.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export async function activate(context: vscode.ExtensionContext) {
2727
setupGlobalStubs()
2828

2929
// Setup the logger
30-
const toolkitOutputChannel = vscode.window.createOutputChannel('AWS Toolkit')
30+
const toolkitOutputChannel = vscode.window.createOutputChannel('AWS Toolkit', { log: true })
3131
await activateLogger(context, toolkitOutputChannel)
3232

3333
await initializeComputeRegion()

src/shared/logger/activation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const defaultLogLevel: LogLevel = 'info'
2727
*/
2828
export async function activate(
2929
extensionContext: vscode.ExtensionContext,
30-
outputChannel: vscode.OutputChannel
30+
outputChannel: vscode.LogOutputChannel
3131
): Promise<void> {
3232
const chan = logOutputChannel
3333
const settings = Settings.instance.getSection('aws')

src/shared/logger/outputChannel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import * as vscode from 'vscode'
77

8-
export const logOutputChannel: vscode.OutputChannel = vscode.window.createOutputChannel('AWS Toolkit Logs')
8+
export const logOutputChannel = vscode.window.createOutputChannel('AWS Toolkit Logs', { log: true })
99

1010
/**
1111
* Shows the log output channel.

src/shared/logger/outputChannelTransport.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as vscode from 'vscode'
77
import Transport from 'winston-transport'
88
import globals from '../extensionGlobals'
99
import { removeAnsi } from '../utilities/textUtilities'
10+
import { LogLevel } from './logger'
1011

1112
export const MESSAGE = Symbol.for('message') // eslint-disable-line @typescript-eslint/naming-convention
1213

@@ -19,6 +20,8 @@ interface LogEntry {
1920

2021
export class OutputChannelTransport extends Transport {
2122
private readonly outputChannel: Pick<vscode.OutputChannel, 'append' | 'appendLine'>
23+
// True if `outputChannel` is a `vscode.LogOutputChannel`.
24+
private readonly isLogChan: boolean
2225
private readonly stripAnsi: boolean
2326

2427
public constructor(
@@ -32,16 +35,46 @@ export class OutputChannelTransport extends Transport {
3235

3336
this.outputChannel = options.outputChannel
3437
this.stripAnsi = options.stripAnsi ?? false
38+
39+
const c = this.outputChannel
40+
this.isLogChan = !!((c as any).info && (c as any).debug && (c as any).warn && (c as any).error)
41+
// Else: we got `vscode.debug.activeDebugConsole` which does not yet implement `vscode.LogOutputChannel`.
3542
}
3643

3744
public override log(info: LogEntry, next: () => void): void {
3845
globals.clock.setImmediate(() => {
39-
const msg = this.stripAnsi ? removeAnsi(info[MESSAGE]) : info[MESSAGE]
46+
if (this.isLogChan) {
47+
const c = this.outputChannel as vscode.LogOutputChannel
48+
// Example input:
49+
// message: 'Preparing to debug locally: Lambda "index.handler"'
50+
// raw: true
51+
// Symbol(level): 'info'
52+
// Symbol(message): '2024-01-16 08:54:30 [INFO]: Preparing to debug locally: Lambda "index.handler"'
53+
// Symbol(splat): (1) [{…}]
54+
// timestamp: '2024-01-16 08:54:30'
55+
// We want the "raw" message without the frontmatter, because
56+
// `vscode.LogOutputChannel` presents its own timestamp + loglevel.
57+
const raw = this.stripAnsi ? removeAnsi(info.message) : info.message
58+
// Avoid extra line breaks.
59+
const msg = raw.trim()
4060

41-
if (info.raw) {
42-
this.outputChannel.append(msg)
61+
const loglevel = info.level as LogLevel
62+
if (loglevel === 'error') {
63+
c.error(msg)
64+
} else if (loglevel === 'warn') {
65+
c.warn(msg)
66+
} else if (loglevel === 'debug' || loglevel === 'verbose') {
67+
c.debug(msg)
68+
} else {
69+
c.info(msg)
70+
}
4371
} else {
44-
this.outputChannel.appendLine(msg)
72+
const msg = this.stripAnsi ? removeAnsi(info[MESSAGE]) : info[MESSAGE]
73+
if (info.raw) {
74+
this.outputChannel.append(msg)
75+
} else {
76+
this.outputChannel.appendLine(msg)
77+
}
4578
}
4679

4780
this.emit('logged', info)

src/shared/sam/cli/samCliLocalInvoke.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export class DefaultSamLocalInvokeCommand implements SamLocalInvokeCommand {
8080
this.logger.verbose('SAM: pid %d: stdout: %s', childProcess.pid(), removeAnsi(text))
8181
},
8282
onStderr: (text: string): void => {
83-
getLogger('debugConsole').error(text, { raw: true })
83+
getLogger('debugConsole').info(text, { raw: true })
8484
// If we have a timeout (as we do on debug) refresh the timeout as we receive text
8585
params.timeout?.refresh()
8686
this.logger.verbose('SAM: pid %d: stderr: %s', childProcess.pid(), removeAnsi(text))

src/test/testUtil.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,11 @@ export async function closeAllEditors(): Promise<void> {
341341
// Note: `workbench.action.closeAllEditors` is unreliable.
342342
const closeAllCmd = 'openEditors.closeAll'
343343

344-
// Output channels are named with prefix "extension-output". https://github.com/microsoft/vscode/issues/148993#issuecomment-1167654358
344+
// Ignore these "editors" not closed by "openEditors.closeAll":
345+
// - `vscode.OutputChannel` name prefixed with "extension-output". https://github.com/microsoft/vscode/issues/148993#issuecomment-1167654358
346+
// - `vscode.LogOutputChannel` name (created with `vscode.window.createOutputChannel(…,{log:true})`
345347
// Maybe we can close these with a command?
346-
const ignorePatterns = [/extension-output/, /tasks/]
348+
const ignorePatterns = [/extension-output/, /tasks/, /amazonwebservices\.aws-toolkit-vscode\./]
347349
const editors: vscode.TextEditor[] = []
348350

349351
const noVisibleEditor: boolean | undefined = await waitUntil(

0 commit comments

Comments
 (0)