-
Notifications
You must be signed in to change notification settings - Fork 434
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
refactor(logging): topicLogger for headers #5586
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,25 +5,20 @@ | |
|
||
import * as vscode from 'vscode' | ||
|
||
/** define log topics */ | ||
type LogTopic = 'Unknown' | 'Test' | ||
|
||
const toolkitLoggers: { | ||
main: Logger | undefined | ||
channel: Logger | undefined | ||
debugConsole: Logger | undefined | ||
} = { main: undefined, channel: undefined, debugConsole: undefined } | ||
} = { main: undefined, debugConsole: undefined } | ||
|
||
export interface Logger { | ||
debug(message: string, ...meta: any[]): number | ||
debug(error: Error, ...meta: any[]): number | ||
verbose(message: string, ...meta: any[]): number | ||
verbose(error: Error, ...meta: any[]): number | ||
info(message: string, ...meta: any[]): number | ||
info(error: Error, ...meta: any[]): number | ||
warn(message: string, ...meta: any[]): number | ||
warn(error: Error, ...meta: any[]): number | ||
error(message: string, ...meta: any[]): number | ||
error(error: Error, ...meta: any[]): number | ||
log(logLevel: LogLevel, message: string, ...meta: any[]): number | ||
log(logLevel: LogLevel, error: Error, ...meta: any[]): number | ||
debug(message: string | Error, ...meta: any[]): number | ||
verbose(message: string | Error, ...meta: any[]): number | ||
info(message: string | Error, ...meta: any[]): number | ||
warn(message: string | Error, ...meta: any[]): number | ||
error(message: string | Error, ...meta: any[]): number | ||
setLogLevel(logLevel: LogLevel): void | ||
/** Returns true if the given log level is being logged. */ | ||
logLevelEnabled(logLevel: LogLevel): boolean | ||
|
@@ -32,6 +27,85 @@ export interface Logger { | |
enableDebugConsole(): void | ||
} | ||
|
||
/** | ||
* Base Logger class | ||
* Used as a wrapper around the logger interface for appending messages | ||
* Also more compatible with future change | ||
*/ | ||
abstract class baseLogger implements Logger { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Our base logger is To summarize, the near-term steps we can take are:
In the future if we need other logger implementations, those can be added at near-zero cost because callers are already using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this Base class can apply to the existing loggers Winston logger would still have the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure that makes sense |
||
abstract coreLogger: Logger | ||
debug(message: string | Error, ...meta: any[]): number { | ||
return this.sendToLog('debug', message, meta) | ||
} | ||
verbose(message: string | Error, ...meta: any[]): number { | ||
return this.sendToLog('verbose', message, meta) | ||
} | ||
info(message: string | Error, ...meta: any[]): number { | ||
return this.sendToLog('info', message, meta) | ||
} | ||
warn(message: string | Error, ...meta: any[]): number { | ||
return this.sendToLog('warn', message, meta) | ||
} | ||
error(message: string | Error, ...meta: any[]): number { | ||
return this.sendToLog('error', message, meta) | ||
} | ||
setLogLevel(logLevel: LogLevel): void { | ||
this.coreLogger.setLogLevel(logLevel) | ||
} | ||
logLevelEnabled(logLevel: LogLevel): boolean { | ||
return this.coreLogger.logLevelEnabled(logLevel) | ||
} | ||
getLogById(logID: number, file: vscode.Uri): string | undefined { | ||
return this.coreLogger.getLogById(logID, file) | ||
} | ||
/** HACK: Enables logging to vscode Debug Console. */ | ||
enableDebugConsole(): void { | ||
this.coreLogger.enableDebugConsole() | ||
} | ||
abstract sendToLog( | ||
type: 'debug' | 'verbose' | 'info' | 'warn' | 'error', | ||
message: string | Error, | ||
...meta: any[] | ||
): number | ||
} | ||
/** | ||
* Logger with topic headers | ||
* | ||
* @param topic identifies the message topic, appended to the front of message followed by a colen. | ||
* @param coreLogger the actual logger it wraps around, in this case the 'main' logger | ||
*/ | ||
export class TopicLogger extends baseLogger { | ||
private topic: LogTopic | ||
override coreLogger: Logger | ||
/** Default topic is 'Unknown' */ | ||
constructor(logger: Logger, topic: LogTopic = 'Unknown') { | ||
super() | ||
this.coreLogger = logger | ||
this.topic = topic | ||
} | ||
/** Format the message with topic header */ | ||
private addTopicToMessage(message: string | Error): string | Error { | ||
const topicPrefix = `${this.topic}: ` | ||
if (typeof message === 'string') { | ||
return topicPrefix + message | ||
} else if (message instanceof Error) { | ||
/** Create a new Error object to avoid modifying the original */ | ||
const topicError = new Error(topicPrefix + message.message) | ||
topicError.name = message.name | ||
topicError.stack = message.stack | ||
return topicError | ||
} | ||
return message | ||
} | ||
override sendToLog( | ||
type: 'debug' | 'verbose' | 'info' | 'warn' | 'error', | ||
message: string | Error, | ||
...meta: any[] | ||
): number { | ||
return this.coreLogger[type](this.addTopicToMessage(message), meta) | ||
} | ||
} | ||
|
||
/** | ||
* Log levels ordered for comparison. | ||
* | ||
|
@@ -85,18 +159,37 @@ export function compareLogLevel(l1: LogLevel, l2: LogLevel): number { | |
/** | ||
* Gets the logger if it has been initialized | ||
* the logger is of `'main'` or `undefined`: Main logger; default impl: logs to log file and log output channel | ||
* @param topic identifies the message topic, appended to the front of message followed by a colen. | ||
*/ | ||
export function getLogger(): Logger { | ||
const logger = toolkitLoggers['main'] | ||
if (!logger) { | ||
export function getLogger(topic?: LogTopic): Logger { | ||
const coreLogger = toolkitLoggers['main'] | ||
if (!coreLogger) { | ||
return new ConsoleLogger() | ||
} | ||
|
||
return logger | ||
/** | ||
* Temperpory check so we don't get a million Unknown logs | ||
* TODO: remove once the majority of log calls are migrated | ||
*/ | ||
if (!topic) { | ||
return coreLogger | ||
} | ||
return new TopicLogger(coreLogger, topic) | ||
} | ||
|
||
export function getDebugConsoleLogger(): Logger { | ||
return toolkitLoggers['debugConsole'] ?? new ConsoleLogger() | ||
/** | ||
* Gets the logger if it has been initialized | ||
* the logger is of `'debugConsole', logs to IDE debug console channel | ||
* @param topic identifies the message topic, appended to the front of message followed by a colen. | ||
*/ | ||
export function getDebugConsoleLogger(topic?: LogTopic): Logger { | ||
const baseLogger = toolkitLoggers['debugConsole'] | ||
if (!baseLogger) { | ||
return new ConsoleLogger() | ||
} | ||
if (!topic) { | ||
return baseLogger | ||
} | ||
return new TopicLogger(baseLogger, topic) | ||
} | ||
|
||
export class NullLogger implements Logger { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/*! | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import assert from 'assert' | ||
import { stub, SinonStubbedInstance } from 'sinon' | ||
import { TopicLogger, Logger, LogLevel } from '../../../shared/logger/logger' | ||
|
||
describe('TopicLogger', () => { | ||
let mockCoreLogger: SinonStubbedInstance<Logger> | ||
let topicLogger: TopicLogger | ||
|
||
beforeEach(() => { | ||
mockCoreLogger = { | ||
debug: stub(), | ||
verbose: stub(), | ||
info: stub(), | ||
warn: stub(), | ||
error: stub(), | ||
setLogLevel: stub(), | ||
logLevelEnabled: stub(), | ||
getLogById: stub(), | ||
enableDebugConsole: stub(), | ||
} as SinonStubbedInstance<Logger> | ||
topicLogger = new TopicLogger(mockCoreLogger as Logger, 'Test') | ||
}) | ||
|
||
it('adds topic prefix to string type messages', () => { | ||
topicLogger.info('Test message') | ||
assert(mockCoreLogger.info.calledWith('Test: Test message')) | ||
}) | ||
|
||
it('adds topic prefix to Error type messages', () => { | ||
const testError = new Error('Test error') | ||
topicLogger.error(testError) | ||
assert(mockCoreLogger.error.calledOnce) | ||
const calledArg = mockCoreLogger.error.firstCall.args[0] | ||
assert(calledArg instanceof Error) | ||
assert.strictEqual(calledArg.message, 'Test: Test error') | ||
assert.strictEqual(calledArg.name, testError.name) | ||
assert.strictEqual(calledArg.stack, testError.stack) | ||
}) | ||
|
||
it('topic is "Unknown" when not specified', () => { | ||
const defaultTopicLogger = new TopicLogger(mockCoreLogger as Logger) | ||
defaultTopicLogger.debug('Verbose message') | ||
assert(mockCoreLogger.debug.calledWith('Unknown: Verbose message')) | ||
}) | ||
|
||
it('delegates setLogLevel to core logger', () => { | ||
topicLogger.setLogLevel('debug' as LogLevel) | ||
assert(mockCoreLogger.setLogLevel.calledWith('debug')) | ||
}) | ||
|
||
it('delegates logLevelEnabled to core logger', () => { | ||
topicLogger.logLevelEnabled('info' as LogLevel) | ||
assert(mockCoreLogger.logLevelEnabled.calledWith('info')) | ||
}) | ||
|
||
it('delegates getLogById to core logger', () => { | ||
const mockUri = {} as any | ||
topicLogger.getLogById(123, mockUri) | ||
assert(mockCoreLogger.getLogById.calledWith(123, mockUri)) | ||
}) | ||
|
||
it('delegates enableDebugConsole to core logger', () => { | ||
topicLogger.enableDebugConsole() | ||
assert(mockCoreLogger.enableDebugConsole.called) | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.