Skip to content

Commit bfc5bc0

Browse files
authored
feat(logs): "Watch Logs" command #4176
Problem: - Not easy to observe telemetry as it happens. - Telemetry logs could be much more readable with some formatting. Solution: - Use new vscode API [LogOutputChannel](https://github.com/microsoft/vscode/blob/aa835c938d2518832a934a7e3124fa761c6f50a0/src/vscode-dts/vscode.d.ts#L10766) - Add "AWS (Developer): Watch Logs" command which shows logs the vscode Debug Console (which allows filtering, unlike the Output panel). microsoft/vscode#52134 - Cleanup the metrics `Metadata` field before logging it. - Format with "%O" instead of JSON stringify. Before: telemetry: emitted metric "aws_expandExplorerNode" -> {"Metadata":[{"Key":"serviceType","Value":"ecs"},{"Key":"awsAccount","Value":"541201481031"},{"Key":"awsRegion","Value":"us-west-2"}],"MetricName":"aws_expandExplorerNode","Value":1,"Unit":"None","Passive":false,"EpochTimestamp":1697147194606} After: 2023-10-19 14:22:25 [DEBUG]: telemetry: aws_expandExplorerNode { Metadata: { serviceType: 'logs', awsAccount: '541201481031', awsRegion: 'us-west-2' }, Value: 1, Unit: 'None', Passive: false }
1 parent 194ccbc commit bfc5bc0

File tree

11 files changed

+87
-23
lines changed

11 files changed

+87
-23
lines changed

CONTRIBUTING.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -286,28 +286,36 @@ generating SDKs, etc.
286286
287287
### Toolkit developer settings (`aws.dev.*`)
288288
289-
The [DevSettngs](https://github.com/aws/aws-toolkit-vscode/blob/479b9d45b5f5ad30fc10567e649b59801053aeba/src/shared/settings.ts#L553) class defines various developer-only settings that change the behavior of the
289+
The [DevSettings](https://github.com/aws/aws-toolkit-vscode/blob/479b9d45b5f5ad30fc10567e649b59801053aeba/src/shared/settings.ts#L553) class defines various developer-only settings that change the behavior of the
290290
Toolkit for testing and development purposes. To use a setting just add it to
291291
your `settings.json`. At runtime, if the Toolkit reads any of these settings,
292292
the "AWS" statusbar item will [change its color](https://github.com/aws/aws-toolkit-vscode/blob/479b9d45b5f5ad30fc10567e649b59801053aeba/src/credentials/awsCredentialsStatusBarItem.ts#L45).
293293
294-
The setting `aws.dev.forceDevMode` will take precedence over all dev settings and enable dev mode on `"aws.dev.forceDevMode": true` or disable on `"aws.dev.forceDevMode": false`.
294+
The `aws.dev.forceDevMode` setting enables or disables Toolkit "dev mode". Without this setting, the presence of any other `aws.dev.*` setting defined in `DevSettings` implicitly enables "dev mode".
295295
296296
### Logging
297297
298-
The `aws.dev.logfile` setting allows you to set the path of the logfile. This makes it easy to
299-
follow and filter the logfile using shell tools like `tail` and `grep`. For example in
300-
settings.json,
301-
298+
- Use the `aws.dev.logfile` setting to set the logfile path to a fixed location, so you can easily
299+
follow and filter the logfile using shell tools like `tail` and `grep`. For example in
300+
settings.json,
301+
```
302302
"aws.dev.logfile": "~/awstoolkit.log",
303-
304-
then following the log with:
305-
303+
```
304+
then you can tail the logfile in your terminal:
305+
```
306306
tail -F ~/awstoolkit.log
307+
```
308+
- Use the `AWS (Developer): Watch Logs` command to watch and filter Toolkit logs (including
309+
telemetry) in VSCode.
310+
- Only available if you enabled "dev mode" (`aws.dev.forceDevMode` setting, see above).
311+
- Sets `aws.logLevel` to "debug".
312+
- Enter text in the Debug Console filter box to show only log messages with that text. <br/>
313+
<img src="./docs/images/debug-console-filter.png" alt="VSCode Debug Console" width="320"/>
307314
308315
### Telemetry
309316
310-
See [docs/telemetry.md](./docs/telemetry.md) for notes on emitting telemetry in this project.
317+
- See [docs/telemetry.md](./docs/telemetry.md) for guidelines on developing telemetry in this project.
318+
- To watch Toolkit telemetry events, use the `AWS (Developer): Watch Logs` command (see [Logging](#logging) above) and enter "telemetry" in the Debug Console filter box.
311319
312320
### Service Endpoints
313321

docs/images/debug-console-filter.png

22.9 KB
Loading

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3751,6 +3751,11 @@
37513751
"category": "AWS (Developer)",
37523752
"enablement": "aws.isDevMode"
37533753
},
3754+
{
3755+
"command": "aws.dev.viewLogs",
3756+
"title": "Watch Logs",
3757+
"category": "AWS (Developer)"
3758+
},
37543759
{
37553760
"command": "aws.openInApplicationComposerDialog",
37563761
"title": "%AWS.command.applicationComposer.openDialog%",

src/dev/activation.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { entries } from '../shared/utilities/tsUtils'
2020
import { isReleaseVersion } from '../shared/vscode/env'
2121
import { isAnySsoConnection } from '../auth/connection'
2222
import { Auth } from '../auth/auth'
23+
import { getLogger } from '../shared/logger'
2324

2425
interface MenuOption {
2526
readonly label: string
@@ -55,9 +56,9 @@ const menuOptions: Record<string, MenuOption> = {
5556
executor: deleteDevEnvCommand,
5657
},
5758
editStorage: {
58-
label: 'Show or Edit Global Storage',
59+
label: 'Show or Edit globalState',
5960
description: 'VS Code',
60-
detail: 'Shows all globalState values, or edit a specific globalState/secret key as a JSON document',
61+
detail: 'Shows all globalState values, or edit a globalState/secret item',
6162
executor: openStorageFromInput,
6263
},
6364
showEnvVars: {
@@ -124,8 +125,23 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
124125

125126
ctx.subscriptions.push(
126127
devSettings.onDidChangeActiveSettings(updateMode),
128+
vscode.workspace.registerTextDocumentContentProvider('aws-dev2', new DevDocumentProvider(ctx)),
129+
// "AWS (Developer): Open Developer Menu"
127130
vscode.commands.registerCommand('aws.dev.openMenu', () => openMenu(ctx, menuOptions)),
128-
vscode.workspace.registerTextDocumentContentProvider('aws-dev2', new DevDocumentProvider(ctx))
131+
// "AWS (Developer): Watch Logs"
132+
Commands.register('aws.dev.viewLogs', async () => {
133+
// HACK: Use startDebugging() so we can use the DEBUG CONSOLE (which supports
134+
// user-defined filtering, unlike the OUTPUT panel).
135+
await vscode.debug.startDebugging(undefined, {
136+
name: 'aws-dev-log',
137+
request: 'launch',
138+
type: 'node', // Nonsense, to force the debugger to start.
139+
})
140+
getLogger().enableDebugConsole()
141+
if (!getLogger().logLevelEnabled('debug')) {
142+
getLogger().setLogLevel('debug')
143+
}
144+
})
129145
)
130146

131147
await updateMode()

src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ export async function activate(context: vscode.ExtensionContext) {
9292
initializeManifestPaths(context)
9393

9494
const toolkitOutputChannel = vscode.window.createOutputChannel(
95-
localize('AWS.channel.aws.toolkit', '{0} Toolkit', getIdeProperties().company)
95+
localize('AWS.channel.aws.toolkit', '{0} Toolkit', getIdeProperties().company),
96+
{ log: true }
9697
)
9798
await activateLogger(context, toolkitOutputChannel)
9899
const remoteInvokeOutputChannel = vscode.window.createOutputChannel(

src/shared/logger/activation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export async function activate(
5050
setLogger(mainLogger)
5151
getLogger().info(`log level: ${getLogLevel()}`)
5252

53-
// channel logger
53+
// Logs to "AWS Toolkit" output channel.
5454
setLogger(
5555
makeLogger(
5656
{
@@ -62,11 +62,11 @@ export async function activate(
6262
'channel'
6363
)
6464

65-
// debug channel logger
65+
// Logs to vscode Debug Console.
6666
setLogger(
6767
makeLogger(
6868
{
69-
staticLogLevel: 'verbose', // verbose will log anything
69+
staticLogLevel: 'debug',
7070
outputChannels: [outputChannel, chan],
7171
useDebugConsole: true,
7272
},

src/shared/logger/logger.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface Logger {
2727
/** Returns true if the given log level is being logged. */
2828
logLevelEnabled(logLevel: LogLevel): boolean
2929
getLogById(logID: number, file: Uri): string | undefined
30+
/** HACK: Enables logging to vscode Debug Console. */
31+
enableDebugConsole(): void
3032
}
3133

3234
/**
@@ -97,6 +99,7 @@ export class NullLogger implements Logger {
9799
public getLogById(logID: number, file: Uri): string | undefined {
98100
return undefined
99101
}
102+
public enableDebugConsole(): void {}
100103
}
101104

102105
/**
@@ -131,6 +134,7 @@ export class ConsoleLogger implements Logger {
131134
public getLogById(logID: number, file: Uri): string | undefined {
132135
return undefined
133136
}
137+
public enableDebugConsole(): void {}
134138
}
135139

136140
export function getNullLogger(type?: 'channel' | 'debugConsole' | 'main'): Logger {

src/shared/logger/winstonToolkitLogger.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export class WinstonToolkitLogger implements Logger, vscode.Disposable {
4343
})
4444
}
4545

46+
public enableDebugConsole(): void {
47+
this.logToDebugConsole()
48+
}
49+
4650
public setLogLevel(logLevel: LogLevel) {
4751
if (this.logger.level === logLevel) {
4852
return

src/shared/telemetry/telemetryLogger.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import { getLogger } from '../logger'
7+
import { DevSettings } from '../settings'
78
import { isReleaseVersion } from '../vscode/env'
89
import { MetricDatum, MetadataEntry } from './clienttelemetry'
910

@@ -28,7 +29,14 @@ function isValidEntry(datum: MetadataEntry): datum is Required<MetadataEntry> {
2829
}
2930

3031
/**
31-
* Telemetry currently sends metadata as an array of key/value pairs, but this is unintuitive for JS
32+
* Takes a Telemetry metadata key-value pairs array:
33+
* ```
34+
* [ {Key:'a',Value:…}, {Key:'b',Value:…}, … ]
35+
* ```
36+
* and produces an object:
37+
* ```
38+
* {a:…, b:…}
39+
* ```
3240
*/
3341
export const mapMetadata = (excludeKeys: string[]) => (metadata: Required<MetricDatum>['Metadata']) => {
3442
const result: Metadata = {}
@@ -38,13 +46,29 @@ export const mapMetadata = (excludeKeys: string[]) => (metadata: Required<Metric
3846
.reduce((a, b) => ((a[b.Key] = b.Value), a), result)
3947
}
4048

49+
/** Transforms a metric for human readability in logs, etc. */
50+
function scrubMetric(metric: MetricDatum): any {
51+
const metaMap = mapMetadata(['MetricName'])(metric.Metadata ?? [])
52+
metaMap.awsAccount = metaMap.awsAccount && metaMap.awsAccount.length > 10 ? '[omitted]' : metaMap.awsAccount
53+
const metricCopy = { ...metric, Metadata: metaMap } as any
54+
delete metricCopy.MetricName // Redundant.
55+
delete metricCopy.EpochTimestamp // Not interesting.
56+
return metricCopy
57+
}
58+
4159
/**
4260
* Simple class to log queryable metrics.
4361
*
4462
* Does not track any other telemetry APIs such as feedback.
4563
*/
4664
export class TelemetryLogger {
4765
private readonly _metrics: MetricDatum[] = []
66+
private readonly isDevMode: boolean
67+
68+
public constructor() {
69+
const devSettings = DevSettings.instance
70+
this.isDevMode = devSettings.isDevMode()
71+
}
4872

4973
public get metricCount(): number {
5074
return this._metrics.length
@@ -55,13 +79,13 @@ export class TelemetryLogger {
5579
}
5680

5781
public log(metric: MetricDatum): void {
58-
const msg = `telemetry: emitted metric "${metric.MetricName}"`
82+
const msg = `telemetry: ${metric.MetricName}`
5983

60-
if (!isReleaseVersion()) {
84+
if (this.isDevMode || !isReleaseVersion()) {
6185
this._metrics.push(metric)
6286
if (getLogger().logLevelEnabled('debug')) {
63-
const stringified = JSON.stringify(metric)
64-
getLogger().debug(`${msg} -> ${stringified}`)
87+
const metricCopy = scrubMetric(metric)
88+
getLogger().debug(`${msg} %O`, metricCopy)
6589
} else {
6690
getLogger().verbose(msg)
6791
}

src/shared/utilities/textUtilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { getLogger } from '../logger'
1515
* If `n` is negative, truncates at start instead of end.
1616
*
1717
* @param s String to truncate
18-
* @param n Truncate top-level string properties exceeding this length
18+
* @param n Truncate after this length
1919
* @param suffix String appended to truncated value (default: "…")
2020
*/
2121
export function truncate(s: string, n: number, suffix?: string): string {

0 commit comments

Comments
 (0)