-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Use optional module "@microsoft/typescript-etw" for ETW logging #32612
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
Conversation
src/compiler/moduleNameResolver.ts
Outdated
} | ||
finally { | ||
if (etwLogger && result && result.resolvedModule) etwLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`); |
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.
Likewise, maybe in our PerfLogger
wrapper, we could have a logInfoEventIf<T extends any[]>(condition: boolean, logText: (...args: T) => string, ...args: T)
, so we can avoid introducing control flow statements for logging into code not related to the logging?
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.
I'm not sure I understand this suggestion.
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.
Rather than
if (etwLogger && result && result.resolvedModule) etwLogger.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`)
write
logger.logInfoEventIf(result && result.resolvedModule, (moduleName, result) => `Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`, moduleName, result);
like how our ts.Debug.assert
signature is setup. (This way the control flow for the logger is within the logger and if the message isn't logged, the string isn't calculated)
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.
How is that preferable?
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.
It moves the control flow for the conditional-ness of the logging into the logger, so anyone reading the implementation doesn't need to reason over the effects of the control flow that's only there for the logger. The function-instead-of-string bit is just an optimization to avoid doing expensive string building (like typeToString
) if the branch isn't hit - it's not strictly needed, and is why ts.Debug.assert
takes both a string and a function argument.
@weswigham I added a PerfLogger and NullLogger class which addresses your first concern and makes things cleaner. Let me know if it should be organized differently (e.g. is that the correct file and namespace?) I considered the second suggestion (moving conditional logic inside the logger class), but that is very unwieldy given the strong typing that is currently in place. We would need logInfoEventIf(), logStartCommandIf(), logStopCommandIf(), etc... It seems like overkill for this problem. |
We'll also need to add |
src/compiler/perfLogger.ts
Outdated
etwModule = undefined; | ||
} | ||
|
||
export const perfLogger: PerfLogger = etwModule ? etwModule : new NullLogger(); |
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.
I considered the second suggestion (moving conditional logic inside the logger class), but that is very unwieldy given the strong typing that is currently in place. We would need logInfoEventIf(), logStartCommandIf(), logStopCommandIf(), etc... It seems like overkill for this problem.
Here's a version using a fluent API to avoid remaking conditional methods with similar definitions (you could construct the passthru instance reflectively, but eh):
export const perfLogger: PerfLogger = etwModule ? etwModule : new NullLogger(); | |
const nullLogger = new NullLogger(); | |
class PassThruLogger implements PerfLogger { | |
constructor(protected base: PerfLogger) {} | |
logEvent(msg: string): void { | |
return this.base.logEvent(msg); | |
} | |
logErrEvent(msg: string): void { | |
return this.base.logErrEvent(msg); | |
} | |
logPerfEvent(msg: string): void { | |
return this.base.logPerfEvent(msg); | |
} | |
logInfoEvent(msg: string): void { | |
return this.base.logInfoEvent(msg); | |
} | |
logStartCommand(command: string, msg: string): void { | |
return this.base.logStartCommand(command, msg); | |
} | |
logStopCommand(command: string, msg: string): void { | |
return this.base.logStopCommand(command, msg); | |
} | |
logStartUpdateProgram(msg: string): void { | |
return this.base.startUpdateProgram(msg); | |
} | |
logStopUpdateProgram(msg: string): void { | |
return this.base.logStopUpdateProgram(msg); | |
} | |
logStartUpdateGraph(): void { | |
return this.base.logStartUpdateGraph(); | |
} | |
logStopUpdateGraph(): void { | |
return this.base.logStopUpdateGraph(); | |
} | |
logStartResolveModule(name: string): void { | |
return this.base.logStartResolveModule(name); | |
} | |
logStopResolveModule(success: string): void { | |
return this.base.logStopResolveModule(success); | |
} | |
logStartParseSourceFile(filename: string): void { | |
return this.base.logStartParseSourceFile(filename); | |
} | |
logStopParseSourceFile(): void { | |
return this.base.logStopParseSourceFile(); | |
} | |
logStartReadFile(filename: string): void { | |
return this.base.logStartReadFile(filename); | |
} | |
logStopReadFile(): void { | |
return this.base.logStopReadFile(); | |
} | |
logStartBindFile(filename: string): void { | |
return this.base.logStartBindFile(filename); | |
} | |
logStopBindFile(): void { | |
return this.base.logStopBindFile(); | |
} | |
logStartScheduledOperation(operationId: string): void { | |
return this.base.logStartScheduledOperation(operationId); | |
} | |
logStopScheduledOperation(): void { | |
return this.base.logStopScheduledOperation(); | |
} | |
} | |
class ConditionalPerfLogger extends PassThruLogger { | |
constructor(base: PerfLogger) { | |
super(base); | |
} | |
if(condition: boolean): PerfLogger { | |
if (condition) { | |
return this.base; | |
} | |
return nullLogger; | |
} | |
} | |
export const perfLogger: ConditionalPerfLogger = new ConditionalPerfLogger(etwModule ? etwModule : nullLogger); |
usage:
perfLogger.if(moduleResolution).logEvent("message");
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.
I tried this out but it's still missing the piece where the message is a function instead of a string. Without that the string interpolation could fail with a null reference.
At this point I'd like to move forward with the code that I have which is pretty straightforward and has addressed all of the other comments. What do you think @sheetalkamat ?
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.
You can redefine if
to be
if(condition: boolean, cb: (logger: PerfLogger) => void): void {
if (condition) {
return cb(this.base);
}
}
with usage
perfLogger.if(moduleResolution, log => log.logEvent("message"));
if that's the concern.
@typescript-bot perf test this |
Heya @RyanCavanaugh, I've started to run the perf test suite on this PR at 5e19753. You can monitor the build here. It should now contribute to this PR's status checks. Update: The results are in! |
@RyanCavanaugh Here they are:Comparison Report - master..32612
System
Hosts
Scenarios
|
Use the @microsoft/typescript-etw native module for ETW logging.
@microsoft/typescript-etw (and ETW in general) are only supported on Windows, so it is not a good idea to add it to the "dependencies" in package.json. Even on Windows we still do not want this module installed by default. Consumers who are interested (such as Visual Studio) should have to opt in. Therefore ETW logging will only be supported if @microsoft/typescript-etw is installed manually. The code will work if the module is found, otherwise it will do nothing.
Types are added from @types/microsoft__typescript-etw so that we can have type checking even if the actual package is not installed.