|
1 | 1 | // Copyright (c) Microsoft Corporation.
|
2 | 2 | // Licensed under the MIT License.
|
3 | 3 |
|
4 |
| -import { LogOutputChannel, Uri, Disposable, LogLevel, window, commands } from "vscode"; |
| 4 | +import { LogOutputChannel, Uri, Disposable, LogLevel, window, commands, Event } from "vscode"; |
5 | 5 |
|
6 | 6 | /** Interface for logging operations. New features should use this interface for the "type" of logger.
|
7 | 7 | * This will allow for easy mocking of the logger during unit tests.
|
@@ -160,3 +160,159 @@ export class Logger implements ILogger {
|
160 | 160 | });
|
161 | 161 | }
|
162 | 162 | }
|
| 163 | + |
| 164 | +/** Parses logs received via the legacy OutputChannel to LogOutputChannel with proper severity. |
| 165 | + * |
| 166 | + * HACK: This is for legacy compatability and can be removed when https://github.com/microsoft/vscode-languageserver-node/issues/1116 is merged and replaced with a normal LogOutputChannel. We don't use a middleware here because any direct logging calls like client.warn() would not be captured by middleware. |
| 167 | + */ |
| 168 | +export class LanguageClientOutputChannelAdapter implements LogOutputChannel { |
| 169 | + private channel: LogOutputChannel; |
| 170 | + constructor(channel: LogOutputChannel) { |
| 171 | + this.channel = channel; |
| 172 | + } |
| 173 | + |
| 174 | + public appendLine(message: string): void { |
| 175 | + this.append(message); |
| 176 | + } |
| 177 | + |
| 178 | + public append(message: string): void { |
| 179 | + const [parsedMessage, level] = this.parse(message); |
| 180 | + this.sendLogMessage(parsedMessage, level); |
| 181 | + } |
| 182 | + |
| 183 | + protected parse(message: string): [string, LogLevel] { |
| 184 | + const logLevelMatch = /^\[(?<level>Trace|Debug|Info|Warn|Error) +- \d+:\d+:\d+ [AP]M\] (?<message>.+)/.exec(message); |
| 185 | + if (logLevelMatch) { |
| 186 | + const { level, message } = logLevelMatch.groups!; |
| 187 | + let logLevel: LogLevel; |
| 188 | + switch (level) { |
| 189 | + case "Trace": |
| 190 | + logLevel = LogLevel.Trace; |
| 191 | + break; |
| 192 | + case "Debug": |
| 193 | + logLevel = LogLevel.Debug; |
| 194 | + break; |
| 195 | + case "Info": |
| 196 | + logLevel = LogLevel.Info; |
| 197 | + break; |
| 198 | + case "Warn": |
| 199 | + logLevel = LogLevel.Warning; |
| 200 | + break; |
| 201 | + case "Error": |
| 202 | + logLevel = LogLevel.Error; |
| 203 | + break; |
| 204 | + default: |
| 205 | + logLevel = LogLevel.Info; |
| 206 | + break; |
| 207 | + } |
| 208 | + return [message, logLevel]; |
| 209 | + } else { |
| 210 | + return [message, LogLevel.Info]; |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + protected sendLogMessage(message: string, level: LogLevel): void { |
| 215 | + switch (level) { |
| 216 | + case LogLevel.Trace: |
| 217 | + this.channel.trace(message); |
| 218 | + break; |
| 219 | + case LogLevel.Debug: |
| 220 | + this.channel.debug(message); |
| 221 | + break; |
| 222 | + case LogLevel.Info: |
| 223 | + this.channel.info(message); |
| 224 | + break; |
| 225 | + case LogLevel.Warning: |
| 226 | + this.channel.warn(message); |
| 227 | + break; |
| 228 | + case LogLevel.Error: |
| 229 | + this.channel.error(message); |
| 230 | + break; |
| 231 | + default: |
| 232 | + this.channel.trace(message); |
| 233 | + break; |
| 234 | + } |
| 235 | + } |
| 236 | + |
| 237 | + // #region Passthru Implementation |
| 238 | + public get name(): string { |
| 239 | + return this.channel.name; |
| 240 | + } |
| 241 | + public get logLevel(): LogLevel { |
| 242 | + return this.channel.logLevel; |
| 243 | + } |
| 244 | + replace(value: string): void { |
| 245 | + this.channel.replace(value); |
| 246 | + } |
| 247 | + show(_column?: undefined, preserveFocus?: boolean): void { |
| 248 | + this.channel.show(preserveFocus); |
| 249 | + } |
| 250 | + public get onDidChangeLogLevel(): Event<LogLevel> { |
| 251 | + return this.channel.onDidChangeLogLevel; |
| 252 | + } |
| 253 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 254 | + public trace(message: string, ...args: any[]): void { |
| 255 | + this.channel.trace(message, ...args); |
| 256 | + } |
| 257 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 258 | + public debug(message: string, ...args: any[]): void { |
| 259 | + this.channel.debug(message, ...args); |
| 260 | + } |
| 261 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 262 | + public info(message: string, ...args: any[]): void { |
| 263 | + this.channel.info(message, ...args); |
| 264 | + } |
| 265 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 266 | + public warn(message: string, ...args: any[]): void { |
| 267 | + this.channel.warn(message, ...args); |
| 268 | + } |
| 269 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 270 | + public error(message: string, ...args: any[]): void { |
| 271 | + this.channel.error(message, ...args); |
| 272 | + } |
| 273 | + public clear(): void { |
| 274 | + this.channel.clear(); |
| 275 | + } |
| 276 | + public hide(): void { |
| 277 | + this.channel.hide(); |
| 278 | + } |
| 279 | + public dispose(): void { |
| 280 | + this.channel.dispose(); |
| 281 | + } |
| 282 | + // #endregion |
| 283 | +} |
| 284 | + |
| 285 | +/** Overrides the severity of some LSP traces to be more logical */ |
| 286 | +export class LanguageClientTraceFormatter extends LanguageClientOutputChannelAdapter { |
| 287 | + |
| 288 | + public override appendLine(message: string): void { |
| 289 | + this.append(message); |
| 290 | + } |
| 291 | + |
| 292 | + public override append(message: string): void { |
| 293 | + // eslint-disable-next-line prefer-const |
| 294 | + let [parsedMessage, level] = this.parse(message); |
| 295 | + |
| 296 | + // Override some LSP traces to be more logical |
| 297 | + if (parsedMessage.startsWith("Sending ")) { |
| 298 | + parsedMessage = parsedMessage.replace("Sending", "▶️"); |
| 299 | + level = LogLevel.Debug; |
| 300 | + } |
| 301 | + if (parsedMessage.startsWith("Received ")) { |
| 302 | + parsedMessage = parsedMessage.replace("Received", "◀️"); |
| 303 | + level = LogLevel.Debug; |
| 304 | + } |
| 305 | + if (parsedMessage.startsWith("Params:") |
| 306 | + || parsedMessage.startsWith("Result:") |
| 307 | + ) { |
| 308 | + level = LogLevel.Trace; |
| 309 | + } |
| 310 | + |
| 311 | + // These are PSES messages we don't really need to see so we drop these to trace |
| 312 | + if (parsedMessage.startsWith("◀️ notification 'window/logMessage'")) { |
| 313 | + level = LogLevel.Trace; |
| 314 | + } |
| 315 | + |
| 316 | + this.sendLogMessage(parsedMessage.trimEnd(), level); |
| 317 | + } |
| 318 | +} |
0 commit comments