Skip to content

Commit

Permalink
Rework multiple classes into a parser function injection
Browse files Browse the repository at this point in the history
  • Loading branch information
JustinGrote committed Nov 15, 2024
1 parent fd5ee38 commit 8a46532
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 77 deletions.
126 changes: 52 additions & 74 deletions src/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,16 @@ export class Logger implements ILogger {
export class LanguageClientOutputChannelAdapter implements LogOutputChannel {
private channel: LogOutputChannel;

constructor(public channelName: string) {
/**
* Creates an instance of the logging class.
*
* @param channelName - The name of the output channel.
* @param parser - A function that parses a log message and returns a tuple containing the parsed message and its log level, or undefined if the log should be filtered.
*/
constructor(
channelName: string,
private parser: (message: string) => [string, LogLevel] | undefined = LanguageClientOutputChannelAdapter.omnisharpLspParser.bind(this)
) {
this.channel = window.createOutputChannel(channelName, {log: true});
}

Expand All @@ -149,41 +158,19 @@ export class LanguageClientOutputChannelAdapter implements LogOutputChannel {
}

public append(message: string): void {
const [parsedMessage, level] = this.parse(message);
this.sendLogMessage(parsedMessage, level);
const parseResult = this.parser(message);
if (parseResult !== undefined) {this.sendLogMessage(...parseResult);}
}

// We include the log level inline from PSES for VSCode because our LanguageClient doesn't support middleware for logMessages yet.
// BUG:
protected parse(message: string): [string, LogLevel] {
const logLevelMatch = /^<(?<level>Trace|Debug|Info|Warning|Error)>(?<message>.+)/.exec(message);
if (logLevelMatch) {
const { level, message } = logLevelMatch.groups!;
let logLevel: LogLevel;
switch (level) {
case "Trace":
logLevel = LogLevel.Trace;
break;
case "Debug":
logLevel = LogLevel.Debug;
break;
case "Info":
logLevel = LogLevel.Info;
break;
case "Warning":
logLevel = LogLevel.Warning;
break;
case "Error":
logLevel = LogLevel.Error;
break;
default:
logLevel = LogLevel.Info;
break;
}
return [message, logLevel];
} else {
return [message, LogLevel.Info];
}
/** Converts from Omnisharp logs since middleware for LogMessage does not currently exist **/
public static omnisharpLspParser(message: string): [string, LogLevel] {
const logLevelMatch = /^\[(?<level>Trace|Debug|Info|Warn|Error) +- \d+:\d+:\d+ [AP]M\] (?<message>.+)/.exec(message);
const logLevel: LogLevel = logLevelMatch?.groups?.level
? LogLevel[logLevelMatch.groups.level as keyof typeof LogLevel]
: LogLevel.Info;
const logMessage = logLevelMatch?.groups?.message ?? message;

return [logMessage, logLevel];
}

protected sendLogMessage(message: string, level: LogLevel): void {
Expand Down Expand Up @@ -257,49 +244,40 @@ export class LanguageClientOutputChannelAdapter implements LogOutputChannel {
// #endregion
}

/** Appends additional */
export class PsesMergedOutputChannel extends LanguageClientOutputChannelAdapter {
public override appendLine(message: string): void {
this.append(message);
}

public override append(message: string): void {
const [parsedMessage, level] = this.parse(message);

// Append PSES prefix to log messages to differentiate them from Client messages
this.sendLogMessage("[PSES] " + parsedMessage, level);
}
/** Special parsing for PowerShell Editor Services LSP messages since the LogLevel cannot be read due to vscode
* LanguageClient Limitations (https://github.com/microsoft/vscode-languageserver-node/issues/1116)
*/
export function PsesParser(message: string): [string, LogLevel] {
const logLevelMatch = /^<(?<level>Trace|Debug|Info|Warning|Error)>(?<message>.+)/.exec(message);
const logLevel: LogLevel = logLevelMatch?.groups?.level
? LogLevel[logLevelMatch.groups.level as keyof typeof LogLevel]
: LogLevel.Info;
const logMessage = logLevelMatch?.groups?.message ?? message;

return ["[PSES] " + logMessage, logLevel];
}

/** Overrides the severity of some LSP traces to be more logical */
export class LanguageClientTraceFormatter extends LanguageClientOutputChannelAdapter {
public override appendLine(message: string): void {
this.append(message);
/** Lsp Trace Parser that does some additional parsing and formatting to make it look nicer */
export function LspTraceParser(message: string): [string, LogLevel] {
let [parsedMessage, level] = LanguageClientOutputChannelAdapter.omnisharpLspParser(message);
if (parsedMessage.startsWith("Sending ")) {
parsedMessage = parsedMessage.replace("Sending", "➡️");
level = LogLevel.Debug;
}
if (parsedMessage.startsWith("Received ")) {
parsedMessage = parsedMessage.replace("Received", "⬅️");
level = LogLevel.Debug;
}
if (parsedMessage.startsWith("Params:")
|| parsedMessage.startsWith("Result:")
) {
level = LogLevel.Trace;
}

public override append(message: string): void {
// eslint-disable-next-line prefer-const
let [parsedMessage, level] = this.parse(message);

if (parsedMessage.startsWith("Sending ")) {
parsedMessage = parsedMessage.replace("Sending", "▶️");
level = LogLevel.Debug;
}
if (parsedMessage.startsWith("Received ")) {
parsedMessage = parsedMessage.replace("Received", "◀️");
level = LogLevel.Debug;
}
if (parsedMessage.startsWith("Params:")
|| parsedMessage.startsWith("Result:")
) {
level = LogLevel.Trace;
}

// These are PSES messages we don't really need to see so we drop these to trace
if (parsedMessage.startsWith("◀️ notification 'window/logMessage'")) {
level = LogLevel.Trace;
}

this.sendLogMessage(parsedMessage.trimEnd(), level);
// These are PSES messages we don't really need to see so we drop these to trace
if (parsedMessage.startsWith("⬅️ notification 'window/logMessage'")) {
level = LogLevel.Trace;
}

return [parsedMessage.trimEnd(), level];
}
6 changes: 3 additions & 3 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import path = require("path");
import vscode = require("vscode");
import TelemetryReporter, { TelemetryEventProperties, TelemetryEventMeasurements } from "@vscode/extension-telemetry";
import { Message, Trace } from "vscode-jsonrpc";
import { ILogger, LanguageClientTraceFormatter, PsesMergedOutputChannel } from "./logging";
import { ILogger, LanguageClientOutputChannelAdapter, LspTraceParser, PsesParser } from "./logging";
import { PowerShellProcess } from "./process";
import { Settings, changeSetting, getSettings, getEffectiveConfigurationTarget, validateCwdSetting } from "./settings";
import utils = require("./utils");
Expand Down Expand Up @@ -688,9 +688,9 @@ export class SessionManager implements Middleware {
},
},
middleware: this,
traceOutputChannel: new LanguageClientTraceFormatter("PowerShell: Trace LSP"),
traceOutputChannel: new LanguageClientOutputChannelAdapter("PowerShell: Trace LSP", LspTraceParser),
// This is named the same as the Client log to merge the logs, but will be handled and disposed separately.
outputChannel: new PsesMergedOutputChannel("PowerShell"),
outputChannel: new LanguageClientOutputChannelAdapter("PowerShell", PsesParser),
revealOutputChannelOn: RevealOutputChannelOn.Never
};

Expand Down

0 comments on commit 8a46532

Please sign in to comment.