Skip to content

Commit

Permalink
src/goLanguageServer: improve automated gopls log collection
Browse files Browse the repository at this point in the history
Now we report the log collection failure reason when we fail to
capture and sanitize gopls log automatically.
And, we configure the lsp client to show the output channel in
case of error events, and also try to read the trace output
three times just in case the output window wasn't able to be
shown yet. The choice of all the constants here (trying 3 times,
sleeping, 10 or 20 ms sleep) is all arbitrary. Can work or not.
Testing is hard.

The report includes the extension version and the editor name info
to help us checking whether users are using the latest extension
version and the usual editor application.

For #984

Change-Id: I6e307314e0556520b76a8e444e6b0f861575efd2
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/274449
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Peter Weinberger <pjw@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
  • Loading branch information
hyangah committed Dec 4, 2020
1 parent f379f50 commit 4fee2d6
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 28 deletions.
79 changes: 57 additions & 22 deletions src/goLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ function buildLanguageClientOption(cfg: LanguageServerConfig): BuildLanguageClie
// The returned language client need to be started before use.
export async function buildLanguageClient(cfg: BuildLanguageClientOption): Promise<LanguageClient> {
let goplsWorkspaceConfig = getGoplsConfig() as any;
goplsWorkspaceConfig = filterDefaultConfigValues(goplsWorkspaceConfig, 'gopls', undefined);
goplsWorkspaceConfig = filterDefaultConfigValues(goplsWorkspaceConfig, 'gopls', undefined);
goplsWorkspaceConfig = await adjustGoplsWorkspaceConfiguration(cfg, goplsWorkspaceConfig);
const c = new LanguageClient(
'go', // id
Expand Down Expand Up @@ -267,7 +267,7 @@ export async function buildLanguageClient(cfg: BuildLanguageClientOption): Promi
},
outputChannel: cfg.outputChannel,
traceOutputChannel: cfg.traceOutputChannel,
revealOutputChannelOn: RevealOutputChannelOn.Never,
revealOutputChannelOn: RevealOutputChannelOn.Error,
initializationFailedHandler: (error: WebRequest.ResponseError<InitializeError>): boolean => {
vscode.window.showErrorMessage(
`The language server is not able to serve any features. Initialization failed: ${error}. `
Expand Down Expand Up @@ -1225,22 +1225,26 @@ You will be asked to provide additional information and logs, so PLEASE READ THE
}
// Get the user's version in case the update prompt above failed.
const usersGoplsVersion = await getLocalGoplsVersion(latestConfig);
const extInfo = getExtensionInfo();
const settings = latestConfig.flags.join(' ');
const title = `gopls: automated issue report (${errKind})`;
const sanitizedLog = collectGoplsLog();
const { sanitizedLog, failureReason } = await collectGoplsLog();
const goplsLog = (sanitizedLog) ?
`<pre>${sanitizedLog}</pre>` :
`
Please attach the stack trace from the crash.
`Please attach the stack trace from the crash.
A window with the error message should have popped up in the lower half of your screen.
Please copy the stack trace and error messages from that window and paste it in this issue.
<PASTE STACK TRACE HERE>
Failed to auto-collect gopls trace: ${failureReason}.
`;

const body = `
gopls version: ${usersGoplsVersion}
gopls flags: ${settings}
extension version: ${extInfo.version}
environment: ${extInfo.appName}
ATTENTION: PLEASE PROVIDE THE DETAILS REQUESTED BELOW.
Expand Down Expand Up @@ -1324,35 +1328,48 @@ export function showServerOutputChannel() {
}
}

function collectGoplsLog(): string {
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async function collectGoplsLog(): Promise<{ sanitizedLog?: string; failureReason?: string; }> {
serverOutputChannel.show();
// Find the logs in the output channel. There is no way to read
// an output channel directly, but we can find the open text
// document, since we just surfaced the output channel to the user.
// See https://github.com/microsoft/vscode/issues/65108.
let logs: string;
for (const doc of vscode.workspace.textDocuments) {
if (doc.languageId !== 'Log') {
continue;
}
if (doc.isDirty || doc.isClosed) {
continue;
for (let i = 0; i < 3; i++) {
// try a couple of times until successfully finding the channel.
for (const doc of vscode.workspace.textDocuments) {
if (doc.languageId !== 'Log') {
continue;
}
if (doc.isDirty || doc.isClosed) {
continue;
}
// The document's name should look like 'extension-output-#X'.
if (doc.fileName.indexOf('extension-output-') === -1) {
continue;
}
logs = doc.getText();
break;
}
// The document's name should look like 'extension-output-#X'.
if (doc.fileName.indexOf('extension-output-') === -1) {
continue;
if (!!logs) {
break;
}
logs = doc.getText();
break;
// sleep a bit before the next try. The choice of the sleep time is arbitrary.
await sleep((i + 1) * 10);
}

return sanitizeGoplsTrace(logs);
}

// capture only panic stack trace and the initialization error message.
// exported for testing.
export function sanitizeGoplsTrace(logs?: string): string {
export function sanitizeGoplsTrace(logs?: string): { sanitizedLog?: string, failureReason?: string } {
if (!logs) {
return '';
return { failureReason: 'no gopls log' };
}
const panicMsgBegin = logs.lastIndexOf('panic: ');
if (panicMsgBegin > -1) { // panic message was found.
Expand All @@ -1375,18 +1392,25 @@ export function sanitizeGoplsTrace(logs?: string): string {
}
).join('\n');

return sanitized;
if (sanitized) {
return { sanitizedLog: sanitized };
}
return { failureReason: 'empty panic trace' };
}
return { failureReason: 'incomplete panic trace' };
}
const initFailMsgBegin = logs.lastIndexOf('Starting client failed');
if (initFailMsgBegin > -1) { // client start failed. Capture up to the 'Code:' line.
const initFailMsgEnd = logs.indexOf('Code: ', initFailMsgBegin);
if (initFailMsgEnd > -1) {
const lineEnd = logs.indexOf('\n', initFailMsgEnd);
return lineEnd > -1 ? logs.substr(initFailMsgBegin, lineEnd - initFailMsgBegin) : logs.substr(initFailMsgBegin);
return { sanitizedLog: lineEnd > -1 ? logs.substr(initFailMsgBegin, lineEnd - initFailMsgBegin) : logs.substr(initFailMsgBegin) };
}
}
return '';
if (logs.lastIndexOf('Usage: gopls') > -1) {
return { failureReason: 'incorrect gopls command usage' };
}
return { failureReason: 'unrecognized crash pattern' };
}

export async function promptForLanguageServerDefaultChange(cfg: vscode.WorkspaceConfiguration) {
Expand Down Expand Up @@ -1446,3 +1470,14 @@ Please tell us why you had to disable the language server.
}
updateGlobalState(promptedForLSOptOutSurveyKey, JSON.stringify(value));
}

interface ExtensionInfo {
version: string; // Extension version
appName: string; // The application name of the editor, like 'VS Code'
}

function getExtensionInfo(): ExtensionInfo {
const version = vscode.extensions.getExtension(extensionId)?.packageJSON?.version;
const appName = vscode.env.appName;
return { version, appName };
}
14 changes: 8 additions & 6 deletions test/gopls/report.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ suite('gopls issue report tests', () => {
interface TestCase {
name: string;
in: string;
want: string;
want?: string;
wantReason?: string;
}
const testCases: TestCase[] = [
{
name: `panic trace`,
in: traceFromIssueGo41435,
want: sanitizedTraceFromIssueGo41435
want: sanitizedTraceFromIssueGo41435,
},
{
name: `initialization error message`,
Expand All @@ -27,18 +28,19 @@ suite('gopls issue report tests', () => {
{
name: `incomplete panic trace`,
in: `panic: \nsecret\n`,
want: ''
wantReason: `incomplete panic trace`
},
{
name: `incomplete initialization error message`,
in: `Secret Starting client failed.\nAnoter Secret\n`,
want: ''
wantReason: `unrecognized crash pattern`
}
];

testCases.map((tc: TestCase) => {
const out = sanitizeGoplsTrace(tc.in);
assert.strictEqual(out, tc.want, `sanitizeGoplsTrace(${tc.name}) returned unexpected results`);
const {sanitizedLog, failureReason} = sanitizeGoplsTrace(tc.in);
assert.strictEqual(sanitizedLog, tc.want, `sanitizeGoplsTrace(${tc.name}) returned unexpected sanitizedLog result`);
assert.strictEqual(failureReason, tc.wantReason, `sanitizeGoplsTrace(${tc.name}) returned unexpected failureReason result`);
});

});
Expand Down

0 comments on commit 4fee2d6

Please sign in to comment.