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(logging): use OutputChannel log level #4859

Merged
merged 2 commits into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 8 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,14 @@ The `aws.dev.forceDevMode` setting enables or disables Toolkit "dev mode". Witho

### Logging

- Use the `aws.dev.logfile` setting to set the logfile path to a fixed location, so you can easily
follow and filter the logfile using shell tools like `tail` and `grep`. For example in
settings.json,
- Use `getLogger()` to log debugging messages, warnings, etc.
- Example: `getLogger().error('topic: widget failed: %O', { foo: 'bar', baz: 42 })`
- Log messages are written to the extension Output channel, which you can view in vscode by visiting the "Output" panel and selecting `AWS Toolkit Logs` or `Amazon Q Logs`.
- While viewing the Output channel (`AWS Toolkit Logs` or `Amazon Q Logs`) in vscode:
- Click the "gear" icon to [select a log level](https://github.com/aws/aws-toolkit-vscode/pull/4859) ("Debug", "Info", "Error", …).
- Click the "..." icon to open the log file.
- Use the `aws.dev.logfile` setting to set the logfile path to a fixed location, so you can follow
and filter logs using shell tools like `tail` and `grep`. For example in settings.json,
```
"aws.dev.logfile": "~/awstoolkit.log",
```
Expand All @@ -328,7 +333,6 @@ The `aws.dev.forceDevMode` setting enables or disables Toolkit "dev mode". Witho
- Use the `AWS (Developer): Watch Logs` command to watch and filter Toolkit logs (including
telemetry) in VSCode.
- Only available if you enabled "dev mode" (`aws.dev.forceDevMode` setting, see above).
- Sets `aws.logLevel` to "debug".
- Enter text in the Debug Console filter box to show only log messages with that text. <br/>
<img src="./docs/images/debug-console-filter.png" alt="VSCode Debug Console" width="320"/>

Expand Down
24 changes: 0 additions & 24 deletions packages/amazonq/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,6 @@
}
},
"properties": {
"aws.logLevel": {
"type": "string",
"default": "debug",
"enum": [
"error",
"warn",
"info",
"verbose",
"debug"
],
"enumDescriptions": [
"Errors Only",
"Errors and Warnings",
"Errors, Warnings, and Info",
"Errors, Warnings, Info, and Verbose",
"Errors, Warnings, Info, Verbose, and Debug"
],
"markdownDescription": "%AWS.configuration.description.logLevel%",
"cloud9": {
"cn": {
"markdownDescription": "%AWS.configuration.description.logLevel.cn%"
}
}
},
"amazonQ.telemetry": {
"type": "boolean",
"default": true,
Expand Down
24 changes: 0 additions & 24 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,30 +90,6 @@
"default": false,
"markdownDescription": "%AWS.configuration.description.samcli.legacyDeploy%"
},
"aws.logLevel": {
"type": "string",
"default": "debug",
"enum": [
"error",
"warn",
"info",
"verbose",
"debug"
],
"enumDescriptions": [
"Errors Only",
"Errors and Warnings",
"Errors, Warnings, and Info",
"Errors, Warnings, Info, and Verbose",
"Errors, Warnings, Info, Verbose, and Debug"
],
"markdownDescription": "%AWS.configuration.description.logLevel%",
"cloud9": {
"cn": {
"markdownDescription": "%AWS.configuration.description.logLevel.cn%"
}
}
},
"aws.telemetry": {
"type": "boolean",
"default": true,
Expand Down
2 changes: 0 additions & 2 deletions packages/core/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
"AWS.configuration.profileDescription": "The name of the credential profile to obtain credentials from.",
"AWS.configuration.description.lambda.recentlyUploaded": "Recently selected Lambda upload targets.",
"AWS.configuration.description.ecs.openTerminalCommand": "The command to run when starting a new interactive terminal session.",
"AWS.configuration.description.logLevel": "AWS IDE Extensions log level (changes reflected on restart)",
"AWS.configuration.description.logLevel.cn": "The Amazon Toolkit's log level (changes reflected on restart)",
"AWS.configuration.description.iot.maxItemsPerPage": "Controls how many IoT Things, Certificates, or Policies are listed before showing a node to `Load More...`.",
"AWS.configuration.description.s3.maxItemsPerPage": "Controls how many S3 items are listed before showing a node to `Load More...`.\nThis corresponds to the `MaxKeys` requested in a single call to S3. [Learn More](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html#AmazonS3-ListObjectsV2-response-MaxKeys)",
"AWS.configuration.description.samcli.lambdaTimeout": "Maximum time (in milliseconds) to wait for SAM output while starting a Local Lambda session",
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/codewhisperer/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { debounceStartSecurityScan } from './commands/startSecurityScan'
import { securityScanLanguageContext } from './util/securityScanLanguageContext'
import { registerWebviewErrorHandler } from '../webviews/server'
import { logAndShowWebviewError } from '../shared/utilities/logAndShowUtils'
import { openSettings } from '../shared/settings'

let localize: nls.LocalizeFunc

Expand Down Expand Up @@ -190,7 +191,7 @@ export async function activate(context: ExtContext): Promise<void> {
`@id:amazonQ.showInlineCodeSuggestionsWithCodeReferences`
)
} else {
await vscode.commands.executeCommand('workbench.action.openSettings', `amazonQ`)
await openSettings('amazonQ')
}
}),
Commands.register('aws.amazonq.refreshAnnotation', async (forceProceed: boolean = false) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const stopScanButton = localize('aws.codewhisperer.stopscan', 'Stop Scan'
const getLogOutputChan = once(() => {
const codeScanOutpuChan = vscode.window.createOutputChannel('Amazon Q Security Scan Logs')
const codeScanLogger = makeLogger({
logLevel: 'info',
outputChannels: [codeScanOutpuChan],
})
return [codeScanLogger, codeScanOutpuChan] as const
Expand Down
93 changes: 33 additions & 60 deletions packages/core/src/shared/logger/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@

import * as vscode from 'vscode'
import { Logger, LogLevel, getLogger } from '.'
import { setLogger } from './logger'
import { fromVscodeLogLevel, setLogger } from './logger'
import { WinstonToolkitLogger } from './winstonToolkitLogger'
import { Settings } from '../settings'
import { Logging } from './commands'
import { resolvePath } from '../utilities/pathUtils'
import { fsCommon } from '../../srcShared/fs'
import globals, { isWeb } from '../extensionGlobals'

export const defaultLogLevel: LogLevel = 'debug'
import { isWeb } from '../extensionGlobals'

/**
* Activate Logger functionality for the extension.
Expand All @@ -27,43 +25,41 @@ export async function activate(
const settings = Settings.instance.getSection('aws')
const devLogfile = settings.get('dev.logfile', '')
const logUri = devLogfile ? vscode.Uri.file(resolvePath(devLogfile)) : undefined
const chanLogLevel = fromVscodeLogLevel(logChannel.logLevel)

await fsCommon.mkdir(extensionContext.logUri)

const mainLogger = makeLogger(
{
logPaths: logUri ? [logUri] : undefined,
outputChannels: [logChannel],
useConsoleLog: isWeb(),
},
extensionContext.subscriptions
)
const mainLogger = makeLogger({
logLevel: chanLogLevel,
logPaths: logUri ? [logUri] : undefined,
outputChannels: [logChannel],
useConsoleLog: isWeb(),
})
logChannel.onDidChangeLogLevel?.(logLevel => {
const newLogLevel = fromVscodeLogLevel(logLevel)
mainLogger.setLogLevel(newLogLevel) // Also logs a message.
})

setLogger(mainLogger)
getLogger().info(`log level: ${getLogLevel()}`)
getLogger().info(`Log level: ${chanLogLevel}`)

// Logs to "AWS Toolkit" output channel.
setLogger(
makeLogger(
{
logPaths: logUri ? [logUri] : undefined,
outputChannels: [outputChannel, logChannel],
},
extensionContext.subscriptions
),
makeLogger({
logLevel: chanLogLevel,
logPaths: logUri ? [logUri] : undefined,
outputChannels: [outputChannel, logChannel],
}),
'channel'
)

// Logs to vscode Debug Console.
setLogger(
makeLogger(
{
staticLogLevel: 'debug',
outputChannels: [outputChannel, logChannel],
useDebugConsole: true,
},
extensionContext.subscriptions
),
makeLogger({
logLevel: chanLogLevel,
outputChannels: [outputChannel, logChannel],
useDebugConsole: true,
}),
'debugConsole'
)

Expand All @@ -75,25 +71,20 @@ export async function activate(

/**
* Creates a logger off of specified params
* @param opts Specified parameters, all optional:
* @param opts.staticLogLevel Static log level, overriding config value. Will persist overridden config value even if the config value changes.
* @param opts.logLevel Log messages at or above this level
* @param opts.logPaths Array of paths to output log entries to
* @param opts.outputChannels Array of output channels to log entries to
* @param opts.useDebugConsole If true, outputs log entries to `vscode.debug.activeDebugConsole`
* @param opts.useConsoleLog If true, outputs log entries to the nodejs or browser devtools console.
* @param disposables Array of disposables to add a subscription to
*/
export function makeLogger(
opts: {
staticLogLevel?: LogLevel
logPaths?: vscode.Uri[]
outputChannels?: vscode.OutputChannel[]
useDebugConsole?: boolean
useConsoleLog?: boolean
},
disposables?: vscode.Disposable[]
): Logger {
const logger = new WinstonToolkitLogger(opts.staticLogLevel ?? getLogLevel())
export function makeLogger(opts: {
logLevel: LogLevel
logPaths?: vscode.Uri[]
outputChannels?: vscode.OutputChannel[]
useDebugConsole?: boolean
useConsoleLog?: boolean
}): Logger {
const logger = new WinstonToolkitLogger(opts.logLevel)
// debug console can show ANSI colors, output channels can not
const stripAnsi = opts.useDebugConsole ?? false
for (const logPath of opts.logPaths ?? []) {
Expand All @@ -109,23 +100,5 @@ export function makeLogger(
logger.logToConsole()
}

if (!opts.staticLogLevel) {
vscode.workspace.onDidChangeConfiguration(
configurationChangeEvent => {
if (configurationChangeEvent.affectsConfiguration('aws.logLevel')) {
const newLogLevel = getLogLevel()
logger.setLogLevel(newLogLevel)
}
},
undefined,
disposables
)
}

return logger
}

function getLogLevel(): LogLevel {
const configuration = Settings.instance.getSection('aws')
return configuration.get(`${globals.contextPrefix}logLevel`, defaultLogLevel)
}
29 changes: 25 additions & 4 deletions packages/core/src/shared/logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Uri } from 'vscode'
import * as vscode from 'vscode'
import globals from '../extensionGlobals'

const toolkitLoggers: {
Expand All @@ -26,7 +26,7 @@ export interface Logger {
setLogLevel(logLevel: LogLevel): void
/** Returns true if the given log level is being logged. */
logLevelEnabled(logLevel: LogLevel): boolean
getLogById(logID: number, file: Uri): string | undefined
getLogById(logID: number, file: vscode.Uri): string | undefined
/** HACK: Enables logging to vscode Debug Console. */
enableDebugConsole(): void
}
Expand All @@ -48,6 +48,27 @@ const logLevels = new Map<LogLevel, number>([

export type LogLevel = 'error' | 'warn' | 'info' | 'verbose' | 'debug'

export function fromVscodeLogLevel(logLevel: vscode.LogLevel): LogLevel {
if (!vscode.LogLevel) {
// vscode version <= 1.73
return 'info'
}

switch (logLevel) {
case vscode.LogLevel.Trace:
case vscode.LogLevel.Debug:
return 'debug'
case vscode.LogLevel.Info:
return 'info'
case vscode.LogLevel.Warning:
return 'warn'
case vscode.LogLevel.Error:
case vscode.LogLevel.Off:
default:
return 'error'
}
}

/**
* Compares log levels.
*
Expand Down Expand Up @@ -96,7 +117,7 @@ export class NullLogger implements Logger {
public error(message: string | Error, ...meta: any[]): number {
return 0
}
public getLogById(logID: number, file: Uri): string | undefined {
public getLogById(logID: number, file: vscode.Uri): string | undefined {
return undefined
}
public enableDebugConsole(): void {}
Expand Down Expand Up @@ -131,7 +152,7 @@ export class ConsoleLogger implements Logger {
console.error(message, ...meta)
return 0
}
public getLogById(logID: number, file: Uri): string | undefined {
public getLogById(logID: number, file: vscode.Uri): string | undefined {
return undefined
}
public enableDebugConsole(): void {}
Expand Down
8 changes: 1 addition & 7 deletions packages/core/src/shared/logger/outputChannelTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,7 @@ export class OutputChannelTransport extends Transport {
} else if (loglevel === 'warn') {
c.warn(msg)
} else if (loglevel === 'debug' || loglevel === 'verbose') {
// XXX: `vscode.LogOutputChannel` loglevel is currently readonly:
// https://github.com/microsoft/vscode/issues/170450
// https://github.com/PowerShell/vscode-powershell/issues/4441
// So debug() will just drop messages unless the user configures vscode (via
// `code --log …` or `.vscode/argv.json` https://stackoverflow.com/a/77257398/152142).
// Use info() until vscode adds a way to set the loglevel.
c.info(msg)
c.debug(msg)
} else {
c.info(msg)
}
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/shared/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -894,11 +894,16 @@ export async function migrateSetting<T, U = T>(
await migrateForScope(vscode.ConfigurationTarget.Global)
}

/** Opens the settings UI filtered by the given prefix. */
export async function openSettings(prefix: string): Promise<void> {
await vscode.commands.executeCommand('workbench.action.openSettings', prefix)
}

/**
* Opens the settings UI at the specified key.
*
* This only works for keys that are considered "top-level", e.g. keys of {@link settingsProps}.
*/
export async function openSettings<K extends keyof SettingsProps>(key: K): Promise<void> {
export async function openSettingsId<K extends keyof SettingsProps>(key: K): Promise<void> {
await vscode.commands.executeCommand('workbench.action.openSettings', `@id:${key}`)
}
4 changes: 2 additions & 2 deletions packages/core/src/shared/telemetry/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { AwsContext } from '../awsContext'
import { DefaultTelemetryService } from './telemetryService'
import { getLogger } from '../logger'
import { getComputeRegion, isAmazonQ, isCloud9, productName } from '../extensionUtilities'
import { openSettings, Settings } from '../settings'
import { openSettingsId, Settings } from '../settings'
import { TelemetryConfig, setupTelemetryId } from './util'
import { isAutomation, isReleaseVersion } from '../vscode/env'
import { AWSProduct } from './clienttelemetry'
Expand Down Expand Up @@ -133,7 +133,7 @@ export async function handleTelemetryNoticeResponse(
// noticeResponseOk is a no-op

if (response === noticeResponseViewSettings) {
await openSettings(isAmazonQ() ? 'amazonQ.telemetry' : 'aws.telemetry')
await openSettingsId(isAmazonQ() ? 'amazonQ.telemetry' : 'aws.telemetry')
}
} catch (err) {
getLogger().error('Error while handling response from telemetry notice: %O', err as Error)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/test/shared/logger/activation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('makeLogger', function () {
before(async function () {
tempFolder = await makeTemporaryToolkitFolder()
const logPath = vscode.Uri.joinPath(vscode.Uri.file(tempFolder), 'log.txt')
testLogger = makeLogger({ staticLogLevel: 'debug', logPaths: [logPath] })
testLogger = makeLogger({ logLevel: 'debug', logPaths: [logPath] })
})

after(async function () {
Expand Down
Loading
Loading