Skip to content

Commit b2ebc10

Browse files
feat(logging): topic header for logs (#5610)
## Problem Log message can appear in output channels(such as Amazon Q Logs as shown), IDE Debug console, or write to pre-defined log file destinations. They come from all parts of our services and can amount to large and lengthy chunks quickly. We don’t have a reliable way to identify log messages from various services and modules of our product. Some messages might have headers while others don’t, and the existing headers are hard-coded on a message by message basis, with no checks for consistency. 1. It’s difficult to locate and trace unidentified log messages when the size of logs grow quickly. 2. Without regulation, the hard-coded custom headers can lead to legacy issues down-the-road and inconsistencies within the codebase. ## Solution 1. Make message topic identifiers an input variable of the getLogger() function. 2. Append the topic at the front of each message, for example: `getLogger('S3').error('Something went wrong')` Would print: `S3: Something went wrong` 3. Define a set of topics to be used in message headers, covering all services and modules. ## Implementation notes: 1. The `WinstonToolkitLogger` is renamed to `ToolkitLogger` to avoid confusion, since its our own logger not winston's. The log topic feature is added here. 2. A `baseLogger` abstract class is created to make the existing `NullLogger`, `ConsoleLogger`, and `ToolkitLogger` cleaner and their responsibilities more obvious and compact. 3. Currently the topics are optional, and the values are just `Test` and `Unknown`, this will be expanded during migration of existing logger calls. --- <!--- REMINDER: Ensure that your PR meets the guidelines in CONTRIBUTING.md --> License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Co-authored-by: Nikolas Komonen <[email protected]>
1 parent 4a27d8b commit b2ebc10

File tree

9 files changed

+240
-156
lines changed

9 files changed

+240
-156
lines changed

packages/core/src/shared/errors.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,23 +140,28 @@ export class ToolkitError extends Error implements ErrorInformation {
140140
* sensitive information and should be limited in technical detail.
141141
*/
142142
public override readonly message: string
143-
public readonly code = this.info.code
144-
public readonly details = this.info.details
143+
public readonly code: string | undefined
144+
public readonly details: Record<string, unknown> | undefined
145145

146146
/**
147147
* We guard against mutation to stop a developer from creating a circular chain of errors.
148148
* The alternative is to truncate errors to an arbitrary depth though that doesn't address
149149
* why the error chain is deep.
150150
*/
151-
readonly #cause = this.info.cause
152-
readonly #name = this.info.name ?? super.name
151+
readonly #cause: Error | undefined
152+
readonly #name: string
153+
readonly #documentationUri: any
154+
readonly #cancelled: boolean | undefined
153155

154-
public constructor(
155-
message: string,
156-
protected readonly info: ErrorInformation = {}
157-
) {
156+
public constructor(message: string, info: ErrorInformation = {}) {
158157
super(message)
159158
this.message = message
159+
this.code = info.code
160+
this.details = info.details
161+
this.#cause = info.cause
162+
this.#name = info.name ?? super.name
163+
this.#cancelled = info.cancelled
164+
this.#documentationUri = info.documentationUri
160165
}
161166

162167
/**
@@ -180,14 +185,14 @@ export class ToolkitError extends Error implements ErrorInformation {
180185
* assignment on construction or by finding a 'cancelled' error within its causal chain.
181186
*/
182187
public get cancelled(): boolean {
183-
return this.info.cancelled ?? isUserCancelledError(this.cause)
188+
return this.#cancelled ?? isUserCancelledError(this.cause)
184189
}
185190

186191
/**
187192
* The associated documentation, if it exists. Otherwise undefined.
188193
*/
189194
public get documentationUri(): vscode.Uri | undefined {
190-
return this.info.documentationUri
195+
return this.#documentationUri
191196
}
192197

193198
/**

packages/core/src/shared/logger/activation.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import * as vscode from 'vscode'
77
import { Logger, LogLevel, getLogger } from '.'
88
import { fromVscodeLogLevel, setLogger } from './logger'
9-
import { WinstonToolkitLogger } from './winstonToolkitLogger'
9+
import { ToolkitLogger } from './toolkitLogger'
1010
import { Settings } from '../settings'
1111
import { Logging } from './commands'
1212
import { resolvePath } from '../utilities/pathUtils'
@@ -79,7 +79,7 @@ export function makeLogger(opts: {
7979
outputChannels?: vscode.OutputChannel[]
8080
useConsoleLog?: boolean
8181
}): Logger {
82-
const logger = new WinstonToolkitLogger(opts.logLevel)
82+
const logger = new ToolkitLogger(opts.logLevel)
8383
// debug console can show ANSI colors, output channels can not
8484
for (const logPath of opts.logPaths ?? []) {
8585
logger.logToFile(logPath)

packages/core/src/shared/logger/logger.ts

Lines changed: 94 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,20 @@
44
*/
55

66
import * as vscode from 'vscode'
7+
import { LogTopic, type ToolkitLogger } from './toolkitLogger'
78

89
const toolkitLoggers: {
910
main: Logger | undefined
10-
channel: Logger | undefined
1111
debugConsole: Logger | undefined
12-
} = { main: undefined, channel: undefined, debugConsole: undefined }
12+
} = { main: undefined, debugConsole: undefined }
1313

1414
export interface Logger {
15-
debug(message: string, ...meta: any[]): number
16-
debug(error: Error, ...meta: any[]): number
17-
verbose(message: string, ...meta: any[]): number
18-
verbose(error: Error, ...meta: any[]): number
19-
info(message: string, ...meta: any[]): number
20-
info(error: Error, ...meta: any[]): number
21-
warn(message: string, ...meta: any[]): number
22-
warn(error: Error, ...meta: any[]): number
23-
error(message: string, ...meta: any[]): number
24-
error(error: Error, ...meta: any[]): number
25-
log(logLevel: LogLevel, message: string, ...meta: any[]): number
26-
log(logLevel: LogLevel, error: Error, ...meta: any[]): number
15+
debug(message: string | Error, ...meta: any[]): number
16+
verbose(message: string | Error, ...meta: any[]): number
17+
info(message: string | Error, ...meta: any[]): number
18+
warn(message: string | Error, ...meta: any[]): number
19+
error(message: string | Error, ...meta: any[]): number
20+
log(logLevel: LogLevel, message: string | Error, ...meta: any[]): number
2721
setLogLevel(logLevel: LogLevel): void
2822
/** Returns true if the given log level is being logged. */
2923
logLevelEnabled(logLevel: LogLevel): boolean
@@ -32,6 +26,38 @@ export interface Logger {
3226
enableDebugConsole(): void
3327
}
3428

29+
export abstract class BaseLogger implements Logger {
30+
debug(message: string | Error, ...meta: any[]): number {
31+
return this.sendToLog('debug', message, meta)
32+
}
33+
verbose(message: string | Error, ...meta: any[]): number {
34+
return this.sendToLog('verbose', message, meta)
35+
}
36+
info(message: string | Error, ...meta: any[]): number {
37+
return this.sendToLog('info', message, meta)
38+
}
39+
warn(message: string | Error, ...meta: any[]): number {
40+
return this.sendToLog('warn', message, meta)
41+
}
42+
error(message: string | Error, ...meta: any[]): number {
43+
return this.sendToLog('error', message, meta)
44+
}
45+
log(logLevel: LogLevel, message: string | Error, ...meta: any[]): number {
46+
return this.sendToLog(logLevel, message, ...meta)
47+
}
48+
abstract setLogLevel(logLevel: LogLevel): void
49+
abstract logLevelEnabled(logLevel: LogLevel): boolean
50+
abstract getLogById(logID: number, file: vscode.Uri): string | undefined
51+
/** HACK: Enables logging to vscode Debug Console. */
52+
abstract enableDebugConsole(): void
53+
54+
abstract sendToLog(
55+
logLevel: 'debug' | 'verbose' | 'info' | 'warn' | 'error',
56+
message: string | Error,
57+
...meta: any[]
58+
): number
59+
}
60+
3561
/**
3662
* Log levels ordered for comparison.
3763
*
@@ -85,107 +111,91 @@ export function compareLogLevel(l1: LogLevel, l2: LogLevel): number {
85111
/**
86112
* Gets the logger if it has been initialized
87113
* the logger is of `'main'` or `undefined`: Main logger; default impl: logs to log file and log output channel
114+
* @param topic: topic to be appended in front of the message.
88115
*/
89-
export function getLogger(): Logger {
116+
export function getLogger(topic?: LogTopic): Logger {
90117
const logger = toolkitLoggers['main']
91118
if (!logger) {
92119
return new ConsoleLogger()
93120
}
94-
121+
/**
122+
* need this check so that setTopic can be recognized
123+
* using instanceof would lead to dependency loop error
124+
*/
125+
if (isToolkitLogger(logger)) {
126+
logger.setTopic(topic)
127+
}
95128
return logger
96129
}
97130

98-
export function getDebugConsoleLogger(): Logger {
99-
return toolkitLoggers['debugConsole'] ?? new ConsoleLogger()
131+
/**
132+
* check if the logger is of type `ToolkitLogger`. This avoids Circular Dependencies, but `instanceof ToolkitLogger` is preferred.
133+
* @param logger
134+
* @returns bool, true if is `ToolkitLogger`
135+
*/
136+
function isToolkitLogger(logger: Logger): logger is ToolkitLogger {
137+
return 'setTopic' in logger && typeof logger.setTopic === 'function'
138+
}
139+
140+
export function getDebugConsoleLogger(topic?: LogTopic): Logger {
141+
const logger = toolkitLoggers['debugConsole']
142+
if (!logger) {
143+
return new ConsoleLogger()
144+
}
145+
if (isToolkitLogger(logger)) {
146+
logger.setTopic(topic)
147+
}
148+
return logger
100149
}
101150

102-
export class NullLogger implements Logger {
151+
export class NullLogger extends BaseLogger {
103152
public setLogLevel(logLevel: LogLevel) {}
104153
public logLevelEnabled(logLevel: LogLevel): boolean {
105154
return false
106155
}
107-
public log(logLevel: LogLevel, message: string | Error, ...meta: any[]): number {
108-
return 0
109-
}
110-
public debug(message: string | Error, ...meta: any[]): number {
111-
return 0
112-
}
113-
public verbose(message: string | Error, ...meta: any[]): number {
114-
return 0
115-
}
116-
public info(message: string | Error, ...meta: any[]): number {
117-
return 0
118-
}
119-
public warn(message: string | Error, ...meta: any[]): number {
120-
return 0
121-
}
122-
public error(message: string | Error, ...meta: any[]): number {
123-
return 0
124-
}
125156
public getLogById(logID: number, file: vscode.Uri): string | undefined {
126157
return undefined
127158
}
128159
public enableDebugConsole(): void {}
160+
override sendToLog(
161+
logLevel: 'error' | 'warn' | 'info' | 'verbose' | 'debug',
162+
message: string | Error,
163+
...meta: any[]
164+
): number {
165+
return 0
166+
}
129167
}
130168

131169
/**
132170
* Fallback used if {@link getLogger()} is requested before logging is fully initialized.
133171
*/
134-
export class ConsoleLogger implements Logger {
172+
export class ConsoleLogger extends BaseLogger {
135173
public setLogLevel(logLevel: LogLevel) {}
136174
public logLevelEnabled(logLevel: LogLevel): boolean {
137175
return false
138176
}
139-
public log(logLevel: LogLevel, message: string | Error, ...meta: any[]): number {
140-
switch (logLevel) {
141-
case 'error':
142-
this.error(message, ...meta)
143-
return 0
144-
case 'warn':
145-
this.warn(message, ...meta)
146-
return 0
147-
case 'verbose':
148-
this.verbose(message, ...meta)
149-
return 0
150-
case 'debug':
151-
this.debug(message, ...meta)
152-
return 0
153-
case 'info':
154-
default:
155-
this.info(message, ...meta)
156-
return 0
157-
}
158-
}
159-
public debug(message: string | Error, ...meta: any[]): number {
160-
// eslint-disable-next-line aws-toolkits/no-console-log
161-
console.debug(message, ...meta)
162-
return 0
163-
}
164-
public verbose(message: string | Error, ...meta: any[]): number {
165-
// eslint-disable-next-line aws-toolkits/no-console-log
166-
console.debug(message, ...meta)
167-
return 0
168-
}
169-
public info(message: string | Error, ...meta: any[]): number {
170-
// eslint-disable-next-line aws-toolkits/no-console-log
171-
console.info(message, ...meta)
172-
return 0
173-
}
174-
public warn(message: string | Error, ...meta: any[]): number {
175-
// eslint-disable-next-line aws-toolkits/no-console-log
176-
console.warn(message, ...meta)
177-
return 0
178-
}
179-
/** Note: In nodejs this prints to `stderr` (see {@link Console.error}). */
180-
public error(message: string | Error, ...meta: any[]): number {
181-
// eslint-disable-next-line aws-toolkits/no-console-log
182-
console.error(message, ...meta)
183-
return 0
184-
}
185177
public getLogById(logID: number, file: vscode.Uri): string | undefined {
186178
return undefined
187179
}
188180
public enableDebugConsole(): void {}
181+
override sendToLog(
182+
logLevel: 'error' | 'warn' | 'info' | 'verbose' | 'debug',
183+
message: string | Error,
184+
...meta: any[]
185+
): number {
186+
/**
187+
* This is here because we pipe verbose to debug currentlly
188+
* TODO: remove in the next stage
189+
*/
190+
if (logLevel === 'verbose') {
191+
// eslint-disable-next-line aws-toolkits/no-console-log
192+
console.debug(message, ...meta)
193+
} else {
194+
// eslint-disable-next-line aws-toolkits/no-console-log
195+
console[logLevel](message, ...meta)
196+
}
197+
return 0
198+
}
189199
}
190200

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

packages/core/src/shared/logger/sharedFileTransport.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import fs from '../fs/fs'
88
import * as vscode from 'vscode'
99
import globals from '../extensionGlobals'
1010
import { MESSAGE } from './consoleLogTransport'
11-
import { WinstonToolkitLogger } from './winstonToolkitLogger'
11+
import { ToolkitLogger } from './toolkitLogger'
1212

1313
interface LogEntry {
1414
level: string
1515
message: string
16-
/** This is the formatted message from {@link WinstonToolkitLogger} in the winston.createLogger() call */
16+
/** This is the formatted message from {@link ToolkitLogger} in the winston.createLogger() call */
1717
[MESSAGE]: string
1818
}
1919

0 commit comments

Comments
 (0)