Skip to content

Commit 6f77bbd

Browse files
committed
extension/src: remove gopls issue report prompting
When gopls fails to start, we prompt the user to file a GitHub issue. Example: #3865 These reports have not proven very helpful as most users do not fill in any additional information associated with the crash. Also we have gopls telemetry / stacks information to analyze such errors. We should remove VSCode's prompting. Change-Id: I0f8b3ade9484d029b5e5876488032c52ce367423 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/702435 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 721a8ca commit 6f77bbd

File tree

3 files changed

+12
-625
lines changed

3 files changed

+12
-625
lines changed

extension/src/commands/startLanguageServer.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@ import { outputChannel, updateLanguageServerIconGoStatusBar } from '../goStatus'
1212
import {
1313
buildLanguageClient,
1414
buildLanguageServerConfig,
15-
errorKind,
1615
RestartReason,
1716
scheduleGoplsSuggestions,
1817
stopLanguageClient,
19-
suggestGoplsIssueReport,
2018
toServerInfo,
2119
updateRestartHistory
2220
} from '../language/goLanguageServer';
@@ -37,14 +35,6 @@ export const startLanguageServer: CommandFactory = (ctx, goCtx) => {
3735
const unlock = await languageServerStartMutex.lock();
3836
goCtx.latestConfig = cfg;
3937
try {
40-
if (reason === RestartReason.MANUAL) {
41-
await suggestGoplsIssueReport(
42-
goCtx,
43-
cfg,
44-
"Looks like you're about to manually restart the language server.",
45-
errorKind.manualRestart
46-
);
47-
}
4838
outputChannel.info(`Try to start language server - ${reason} (enabled: ${cfg.enabled})`);
4939

5040
// If the client has already been started, make sure to clear existing

extension/src/language/goLanguageServer.ts

Lines changed: 12 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { toolExecutionEnvironment } from '../goEnv';
4242
import { GoDocumentFormattingEditProvider, usingCustomFormatTool } from './legacy/goFormat';
4343
import { installTools, latestModuleVersion, promptForMissingTool, promptForUpdatingTool } from '../goInstallTools';
4444
import { getTool, Tool } from '../goTools';
45-
import { getFromGlobalState, updateGlobalState, updateWorkspaceState } from '../stateUtils';
45+
import { updateGlobalState, updateWorkspaceState } from '../stateUtils';
4646
import {
4747
getBinPath,
4848
getCheckForToolsUpdatesConfig,
@@ -99,14 +99,6 @@ export function updateRestartHistory(goCtx: GoExtensionContext, reason: RestartR
9999
goCtx.restartHistory.push(new Restart(reason, new Date(), enabled));
100100
}
101101

102-
function formatRestartHistory(goCtx: GoExtensionContext): string {
103-
const result: string[] = [];
104-
for (const restart of goCtx.restartHistory ?? []) {
105-
result.push(`${restart.timestamp.toUTCString()}: ${restart.reason} (enabled: ${restart.enabled})`);
106-
}
107-
return result.join('\n');
108-
}
109-
110102
export enum RestartReason {
111103
ACTIVATION = 'activation',
112104
MANUAL = 'manual',
@@ -453,13 +445,7 @@ export async function buildLanguageClient(
453445
},
454446
closed: () => {
455447
if (initializationError !== undefined) {
456-
suggestGoplsIssueReport(
457-
goCtx,
458-
cfg,
459-
'The gopls server failed to initialize.',
460-
errorKind.initializationFailure,
461-
initializationError
462-
);
448+
suggestActionAfterGoplsStartError(goCtx, cfg);
463449
initializationError = undefined;
464450
// In case of initialization failure, do not try to restart.
465451
return {
@@ -478,12 +464,7 @@ export async function buildLanguageClient(
478464
action: CloseAction.Restart
479465
};
480466
}
481-
suggestGoplsIssueReport(
482-
goCtx,
483-
cfg,
484-
'The connection to gopls has been closed. The gopls server may have crashed.',
485-
errorKind.crash
486-
);
467+
suggestActionAfterGoplsStartError(goCtx, cfg);
487468
updateLanguageServerIconGoStatusBar(c, true);
488469
return {
489470
message: '', // suppresses error popups - there will be other popups.
@@ -1371,31 +1352,21 @@ export enum errorKind {
13711352
manualRestart
13721353
}
13731354

1374-
// suggestGoplsIssueReport prompts users to file an issue with gopls.
1375-
export async function suggestGoplsIssueReport(
1355+
// suggestActionAfterStartError potentially suggests actions to the user when gopls fails to start, either
1356+
// updating the language server or double checking their go.languageServerFlags setting.
1357+
export async function suggestActionAfterGoplsStartError(
13761358
goCtx: GoExtensionContext,
1377-
cfg: LanguageServerConfig, // config used when starting this gopls.
1378-
msg: string,
1379-
reason: errorKind,
1380-
initializationError?: ResponseError<InitializeError>
1359+
cfg: LanguageServerConfig // config used when starting this gopls.
13811360
) {
1382-
const issueTime = new Date();
1383-
1384-
// Don't prompt users who manually restart to file issues until gopls/v1.0.
1385-
if (reason === errorKind.manualRestart) {
1386-
return;
1387-
}
1388-
13891361
// cfg is the config used when starting this crashed gopls instance, while
13901362
// goCtx.latestConfig is the config used by the latest gopls instance.
13911363
// They may be different if gopls upgrade occurred in between.
1392-
// Let's not report issue yet if they don't match.
13931364
if (JSON.stringify(goCtx.latestConfig?.version) !== JSON.stringify(cfg.version)) {
13941365
return;
13951366
}
13961367

13971368
// The user may have an outdated version of gopls, in which case we should
1398-
// just prompt them to update, not file an issue.
1369+
// just prompt them to update.
13991370
const tool = getTool('gopls');
14001371
if (tool) {
14011372
const versionToUpdate = await shouldUpdateLanguageServer(tool, goCtx.latestConfig, true);
@@ -1411,30 +1382,13 @@ export async function suggestGoplsIssueReport(
14111382
if (goCtx.latestConfig?.serverName !== 'gopls') {
14121383
return;
14131384
}
1414-
const promptForIssueOnGoplsRestartKey = 'promptForIssueOnGoplsRestart';
1415-
let saved: any;
1416-
try {
1417-
saved = JSON.parse(getFromGlobalState(promptForIssueOnGoplsRestartKey, false));
1418-
} catch (err) {
1419-
console.log(`Failed to parse as JSON ${getFromGlobalState(promptForIssueOnGoplsRestartKey, true)}: ${err}`);
1420-
return;
1421-
}
1422-
// If the user has already seen this prompt, they may have opted-out for
1423-
// the future. Only prompt again if it's been more than a year since.
1424-
if (saved) {
1425-
const dateSaved = new Date(saved['date']);
1426-
const prompt = <boolean>saved['prompt'];
1427-
if (!prompt && daysBetween(new Date(), dateSaved) <= 365) {
1428-
return;
1429-
}
1430-
}
14311385

1432-
const { sanitizedLog, failureReason } = await collectGoplsLog(goCtx);
1386+
const isIncorrectUsage = await isIncorrectCommandUsage(goCtx);
14331387

14341388
// If the user has invalid values for "go.languageServerFlags", we may get
14351389
// this error. Prompt them to double check their flags.
14361390
let selected: string | undefined;
1437-
if (failureReason === GoplsFailureModes.INCORRECT_COMMAND_USAGE) {
1391+
if (isIncorrectUsage) {
14381392
const languageServerFlags = getGoConfig()['languageServerFlags'] as string[];
14391393
if (languageServerFlags && languageServerFlags.length > 0) {
14401394
selected = await vscode.window.showErrorMessage(
@@ -1455,87 +1409,6 @@ Please correct the setting.`,
14551409
}
14561410
}
14571411
}
1458-
const showMessage = sanitizedLog ? vscode.window.showWarningMessage : vscode.window.showInformationMessage;
1459-
selected = await showMessage(
1460-
`${msg} Would you like to report a gopls issue on GitHub?
1461-
You will be asked to provide additional information and logs, so PLEASE READ THE CONTENT IN YOUR BROWSER.`,
1462-
'Yes',
1463-
'Next time',
1464-
'Never'
1465-
);
1466-
switch (selected) {
1467-
case 'Yes':
1468-
{
1469-
// Prefill an issue title and report.
1470-
let errKind: string;
1471-
switch (reason) {
1472-
case errorKind.crash:
1473-
errKind = 'crash';
1474-
break;
1475-
case errorKind.initializationFailure:
1476-
errKind = 'initialization';
1477-
break;
1478-
}
1479-
const settings = goCtx.latestConfig.flags.join(' ');
1480-
const title = `gopls: automated issue report (${errKind})`;
1481-
const goplsStats = await getGoplsStats(goCtx.latestConfig?.path);
1482-
const goplsLog = sanitizedLog
1483-
? `<pre>${sanitizedLog}</pre>`
1484-
: `Please attach the stack trace from the crash.
1485-
A window with the error message should have popped up in the lower half of your screen.
1486-
Please copy the stack trace and error messages from that window and paste it in this issue.
1487-
1488-
<PASTE STACK TRACE HERE>
1489-
1490-
Failed to auto-collect gopls trace: ${failureReason}.
1491-
`;
1492-
1493-
const body = `
1494-
gopls version: ${cfg.version?.version}/${cfg.version?.goVersion}
1495-
gopls flags: ${settings}
1496-
update flags: ${cfg.checkForUpdates}
1497-
extension version: ${extensionInfo.version}
1498-
environment: ${extensionInfo.appName} ${process.platform}
1499-
initialization error: ${initializationError}
1500-
issue timestamp: ${issueTime.toUTCString()}
1501-
restart history:
1502-
${formatRestartHistory(goCtx)}
1503-
1504-
ATTENTION: PLEASE PROVIDE THE DETAILS REQUESTED BELOW.
1505-
1506-
Describe what you observed.
1507-
1508-
<ANSWER HERE>
1509-
1510-
${goplsLog}
1511-
1512-
<details><summary>gopls stats -anon</summary>
1513-
${goplsStats}
1514-
</details>
1515-
1516-
OPTIONAL: If you would like to share more information, you can attach your complete gopls logs.
1517-
1518-
NOTE: THESE MAY CONTAIN SENSITIVE INFORMATION ABOUT YOUR CODEBASE.
1519-
DO NOT SHARE LOGS IF YOU ARE WORKING IN A PRIVATE REPOSITORY.
1520-
1521-
<OPTIONAL: ATTACH LOGS HERE>
1522-
`;
1523-
const url = `https://github.com/golang/vscode-go/issues/new?title=${title}&labels=automatedReport&body=${body}`;
1524-
await vscode.env.openExternal(vscode.Uri.parse(url));
1525-
}
1526-
break;
1527-
case 'Next time':
1528-
break;
1529-
case 'Never':
1530-
updateGlobalState(
1531-
promptForIssueOnGoplsRestartKey,
1532-
JSON.stringify({
1533-
prompt: false,
1534-
date: new Date()
1535-
})
1536-
);
1537-
break;
1538-
}
15391412
}
15401413

15411414
export const showServerOutputChannel: CommandFactory = (ctx, goCtx) => () => {
@@ -1570,7 +1443,7 @@ function sleep(ms: number) {
15701443
return new Promise((resolve) => setTimeout(resolve, ms));
15711444
}
15721445

1573-
async function collectGoplsLog(goCtx: GoExtensionContext): Promise<{ sanitizedLog?: string; failureReason?: string }> {
1446+
async function isIncorrectCommandUsage(goCtx: GoExtensionContext): Promise<boolean> {
15741447
goCtx.serverOutputChannel?.show();
15751448
// Find the logs in the output channel. There is no way to read
15761449
// an output channel directly, but we can find the open text
@@ -1597,78 +1470,7 @@ async function collectGoplsLog(goCtx: GoExtensionContext): Promise<{ sanitizedLo
15971470
// sleep a bit before the next try. The choice of the sleep time is arbitrary.
15981471
await sleep((i + 1) * 100);
15991472
}
1600-
return sanitizeGoplsTrace(logs);
1601-
}
1602-
1603-
enum GoplsFailureModes {
1604-
NO_GOPLS_LOG = 'no gopls log',
1605-
EMPTY_PANIC_TRACE = 'empty panic trace',
1606-
INCORRECT_COMMAND_USAGE = 'incorrect gopls command usage',
1607-
UNRECOGNIZED_CRASH_PATTERN = 'unrecognized crash pattern'
1608-
}
1609-
1610-
// capture only panic stack trace and the initialization error message.
1611-
// exported for testing.
1612-
export function sanitizeGoplsTrace(logs?: string): { sanitizedLog?: string; failureReason?: string } {
1613-
if (!logs) {
1614-
return { failureReason: GoplsFailureModes.NO_GOPLS_LOG };
1615-
}
1616-
const panicMsgBegin = logs.lastIndexOf('panic: ');
1617-
if (panicMsgBegin > -1) {
1618-
// panic message was found.
1619-
let panicTrace = logs.substr(panicMsgBegin);
1620-
const panicMsgEnd = panicTrace.search(/\[(Info|Warning|Error)\s+-\s+/);
1621-
if (panicMsgEnd > -1) {
1622-
panicTrace = panicTrace.substr(0, panicMsgEnd);
1623-
}
1624-
const filePattern = /(\S+\.go):\d+/;
1625-
const sanitized = panicTrace
1626-
.split('\n')
1627-
.map((line: string) => {
1628-
// Even though this is a crash from gopls, the file path
1629-
// can contain user names and user's filesystem directory structure.
1630-
// We can still locate the corresponding file if the file base is
1631-
// available because the full package path is part of the function
1632-
// name. So, leave only the file base.
1633-
const m = line.match(filePattern);
1634-
if (!m) {
1635-
return line;
1636-
}
1637-
const filePath = m[1];
1638-
const fileBase = path.basename(filePath);
1639-
return line.replace(filePath, ' ' + fileBase);
1640-
})
1641-
.join('\n');
1642-
1643-
if (sanitized) {
1644-
return { sanitizedLog: sanitized };
1645-
}
1646-
return { failureReason: GoplsFailureModes.EMPTY_PANIC_TRACE };
1647-
}
1648-
// Capture Fatal
1649-
// foo.go:1: the last message (caveat - we capture only the first log line)
1650-
const m = logs.match(/(^\S+\.go:\d+:.*$)/gm);
1651-
if (m && m.length > 0) {
1652-
return { sanitizedLog: m[0].toString() };
1653-
}
1654-
const initFailMsgBegin = logs.lastIndexOf('gopls client:');
1655-
if (initFailMsgBegin > -1) {
1656-
// client start failed. Capture up to the 'Code:' line.
1657-
const initFailMsgEnd = logs.indexOf('Code: ', initFailMsgBegin);
1658-
if (initFailMsgEnd > -1) {
1659-
const lineEnd = logs.indexOf('\n', initFailMsgEnd);
1660-
return {
1661-
sanitizedLog:
1662-
lineEnd > -1
1663-
? logs.substr(initFailMsgBegin, lineEnd - initFailMsgBegin)
1664-
: logs.substr(initFailMsgBegin)
1665-
};
1666-
}
1667-
}
1668-
if (logs.lastIndexOf('Usage:') > -1) {
1669-
return { failureReason: GoplsFailureModes.INCORRECT_COMMAND_USAGE };
1670-
}
1671-
return { failureReason: GoplsFailureModes.UNRECOGNIZED_CRASH_PATTERN };
1473+
return logs ? logs.lastIndexOf('Usage:') > -1 : false;
16721474
}
16731475

16741476
const GOPLS_FETCH_VULNCHECK_RESULT = 'gopls.fetch_vulncheck_result';
@@ -1719,22 +1521,3 @@ export function maybePromptForTelemetry(goCtx: GoExtensionContext) {
17191521
};
17201522
callback();
17211523
}
1722-
1723-
async function getGoplsStats(binpath?: string) {
1724-
if (!binpath) {
1725-
return 'gopls path unknown';
1726-
}
1727-
const env = toolExecutionEnvironment();
1728-
const cwd = getWorkspaceFolderPath();
1729-
const start = new Date();
1730-
const execFile = util.promisify(cp.execFile);
1731-
try {
1732-
const timeout = 60 * 1000; // 60sec;
1733-
const { stdout } = await execFile(binpath, ['stats', '-anon'], { env, cwd, timeout });
1734-
return stdout;
1735-
} catch (e) {
1736-
const duration = new Date().getTime() - start.getTime();
1737-
console.log(`gopls stats -anon failed: ${JSON.stringify(e)}`);
1738-
return `gopls stats -anon failed after ${duration} ms. Please check if gopls is killed by OS.`;
1739-
}
1740-
}

0 commit comments

Comments
 (0)