Skip to content

Commit 6b80a2a

Browse files
committed
Include logs when reporting an issue
1 parent 434a49d commit 6b80a2a

File tree

8 files changed

+105
-50
lines changed

8 files changed

+105
-50
lines changed

SUPPORT.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ The template has a section to include the `C#` output window logs. These logs ar
4040

4141
##### C# LSP Trace Logs
4242
- To capture detailed requests sent to the Roslyn language server:
43-
1. Set the `C#` output window log level to `Trace` (as described above).
44-
2. Open the `C# LSP Trace Logs` output window.
43+
1. Open the `C# LSP Trace Logs` output window.
44+
2. Set the output window log level to `Trace`.
4545
3. Reproduce the issue.
4646
4. Copy the contents of the `C# LSP Trace Logs` output window.
47+
5. After collecting the logs, reset the log level to `Info`.
4748

4849

4950
##### Other Ways to Set the Log Level

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5627,6 +5627,11 @@
56275627
"when": "editorLangId == csharp && (dotnet.server.activationContext == 'Roslyn' || dotnet.server.activationContext == 'OmniSharp')",
56285628
"group": "2_dotnet@2"
56295629
}
5630+
],
5631+
"issue/reporter": [
5632+
{
5633+
"command": "csharp.reportIssue"
5634+
}
56305635
]
56315636
},
56325637
"viewsWelcome": [

src/lsptoolshost/activate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export async function activateRoslynLanguageServer(
8383
registerCopilotContextProviders(context, languageServer, _channel);
8484

8585
// Register any commands that need to be handled by the extension.
86-
registerCommands(context, languageServer, hostExecutableResolver, _channel);
86+
registerCommands(context, languageServer, hostExecutableResolver, _channel, _traceChannel);
8787
registerNestedCodeActionCommands(context, languageServer, _channel);
8888
registerCodeActionFixAllCommands(context, languageServer, _channel);
8989

src/lsptoolshost/commands.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ export function registerCommands(
1515
context: vscode.ExtensionContext,
1616
languageServer: RoslynLanguageServer,
1717
hostExecutableResolver: IHostExecutableResolver,
18-
outputChannel: vscode.LogOutputChannel
18+
outputChannel: vscode.LogOutputChannel,
19+
csharpTraceChannel: vscode.LogOutputChannel
1920
) {
20-
registerExtensionCommands(context, languageServer, hostExecutableResolver, outputChannel);
21+
registerExtensionCommands(context, hostExecutableResolver, outputChannel, csharpTraceChannel);
2122
registerWorkspaceCommands(context, languageServer);
2223
registerServerCommands(context, languageServer, outputChannel);
2324
}
@@ -27,16 +28,17 @@ export function registerCommands(
2728
*/
2829
function registerExtensionCommands(
2930
context: vscode.ExtensionContext,
30-
languageServer: RoslynLanguageServer,
3131
hostExecutableResolver: IHostExecutableResolver,
32-
outputChannel: vscode.LogOutputChannel
32+
outputChannel: vscode.LogOutputChannel,
33+
csharpTraceChannel: vscode.LogOutputChannel
3334
) {
3435
context.subscriptions.push(
3536
vscode.commands.registerCommand('csharp.reportIssue', async () =>
3637
reportIssue(
37-
context.extension.packageJSON.version,
38+
context,
3839
getDotnetInfo,
3940
/*shouldIncludeMonoInfo:*/ false,
41+
[outputChannel, csharpTraceChannel],
4042
hostExecutableResolver
4143
)
4244
)

src/omnisharp/features/commands.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export default function registerCommands(
3535
eventStream: EventStream,
3636
monoResolver: IHostExecutableResolver,
3737
dotnetResolver: IHostExecutableResolver,
38-
workspaceInformationProvider: IWorkspaceDebugInformationProvider
38+
workspaceInformationProvider: IWorkspaceDebugInformationProvider,
39+
outputChannel: vscode.OutputChannel
3940
): CompositeDisposable {
4041
const disposable = new CompositeDisposable();
4142
disposable.add(vscode.commands.registerCommand('o.restart', async () => restartOmniSharp(context, server)));
@@ -66,20 +67,31 @@ export default function registerCommands(
6667
);
6768

6869
disposable.add(
69-
vscode.commands.registerCommand('csharp.reportIssue', async () =>
70-
reportIssue(
71-
context.extension.packageJSON.version,
70+
vscode.commands.registerCommand('csharp.reportIssue', async () => {
71+
const logOutputChannel = isLogOutputChannel(outputChannel);
72+
return reportIssue(
73+
context,
7274
getDotnetInfo,
7375
platformInfo.isValidPlatformForMono(),
76+
logOutputChannel ? [logOutputChannel] : [],
7477
dotnetResolver,
7578
monoResolver
76-
)
77-
)
79+
);
80+
})
7881
);
7982

8083
return new CompositeDisposable(disposable);
8184
}
8285

86+
function isLogOutputChannel(channel: vscode.OutputChannel): vscode.LogOutputChannel | undefined {
87+
const anyChannel = channel as any;
88+
if (anyChannel.logLevel) {
89+
return channel as vscode.LogOutputChannel;
90+
} else {
91+
return undefined;
92+
}
93+
}
94+
8395
async function restartOmniSharp(context: vscode.ExtensionContext, server: OmniSharpServer) {
8496
if (server.isRunning()) {
8597
await server.restart();

src/omnisharp/omnisharpLanguageServer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,8 @@ async function activate(
240240
eventStream,
241241
omnisharpMonoResolver,
242242
omnisharpDotnetResolver,
243-
workspaceInformationProvider
243+
workspaceInformationProvider,
244+
outputChannel
244245
)
245246
);
246247

src/shared/reportIssue.ts

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import { CSharpExtensionId } from '../constants/csharpExtensionId';
1111
import { commonOptions, LanguageServerOptions, languageServerOptions } from './options';
1212

1313
export default async function reportIssue(
14-
csharpExtVersion: string,
14+
context: vscode.ExtensionContext,
1515
getDotnetInfo: (dotNetCliPaths: string[]) => Promise<DotnetInfo>,
1616
shouldIncludeMonoInfo: boolean,
17+
logChannels: vscode.LogOutputChannel[],
1718
dotnetResolver: IHostExecutableResolver,
1819
monoResolver?: IHostExecutableResolver
1920
) {
2021
// Get info for the dotnet that the language server executable is run on, not the dotnet the language server will execute user code on.
21-
let fullDotnetInfo: string | undefined;
22+
let fullDotnetInfo = '';
2223
try {
2324
const info = await dotnetResolver.getHostExecutableInfo();
2425
const dotnetInfo = await getDotnetInfo([dirname(info.path)]);
@@ -33,10 +34,11 @@ export default async function reportIssue(
3334
monoInfo = await getMonoIfPlatformValid(monoResolver);
3435
}
3536

36-
const extensions = getInstalledExtensions();
37-
37+
const csharpExtVersion = context.extension.packageJSON.version;
3838
const useOmnisharp = commonOptions.useOmnisharpServer;
39-
const logInfo = getLogInfo(useOmnisharp);
39+
const extensionsTable = generateExtensionTable();
40+
const optionsTable = generateOptionsTable();
41+
const logInfo = await getLogInfo(logChannels, context);
4042

4143
const body = `## Issue Description ##
4244
## Steps to Reproduce ##
@@ -45,12 +47,13 @@ export default async function reportIssue(
4547
4648
## Actual Behavior ##
4749
48-
## Logs ##
50+
`;
4951

50-
${logInfo}
52+
const userData = `### Logs ###
5153
52-
## Environment information ##
54+
${logInfo}
5355
56+
## Environment Information ##
5457
**VSCode version**: ${vscode.version}
5558
**C# Extension**: ${csharpExtVersion}
5659
**Using OmniSharp**: ${useOmnisharp}
@@ -59,16 +62,16 @@ ${monoInfo}
5962
<details><summary>Dotnet Information</summary>
6063
${fullDotnetInfo}</details>
6164
<details><summary>Visual Studio Code Extensions</summary>
62-
${generateExtensionTable(extensions)}
65+
${extensionsTable}
6366
</details>
6467
<details><summary>C# Settings</summary>
65-
${generateOptionsTable()}
68+
${optionsTable}
6669
</details>
6770
`;
68-
6971
await vscode.commands.executeCommand('workbench.action.openIssueReporter', {
7072
extensionId: CSharpExtensionId,
7173
issueBody: body,
74+
data: userData,
7275
});
7376
}
7477

@@ -82,7 +85,8 @@ function sortExtensions(a: vscode.Extension<any>, b: vscode.Extension<any>): num
8285
return 0;
8386
}
8487

85-
function generateExtensionTable(extensions: vscode.Extension<any>[]) {
88+
function generateExtensionTable() {
89+
const extensions = getInstalledExtensions();
8690
if (extensions.length <= 0) {
8791
return 'none';
8892
}
@@ -142,25 +146,47 @@ async function getMonoIfPlatformValid(monoResolver: IHostExecutableResolver): Pr
142146
${monoVersion}</details>`;
143147
}
144148

145-
function getLogInfo(useOmnisharp: boolean): string {
146-
if (useOmnisharp) {
147-
return `### OmniSharp log ###
148-
<details>Post the output from Output-->OmniSharp log here</details>
149-
150-
### C# log ###
151-
<details>Post the output from Output-->C# here</details>`;
152-
} else {
153-
return `<!--
149+
async function getLogInfo(logChannels: vscode.LogOutputChannel[], context: vscode.ExtensionContext): Promise<string> {
150+
let logInfo = `<!--
154151
If you can, it would be the most helpful to zip up and attach the entire extensions log folder. The folder can be opened by running the \`workbench.action.openExtensionLogsFolder\` command.
155152
156-
Additionally, if you can reproduce the issue reliably, set the \`C#\` output window log level to 'Trace' re-run the scenario to get more detailed logs.
153+
Additionally, if you can reproduce the issue reliably, use trace logging, see https://github.com/dotnet/vscode-csharp/blob/main/SUPPORT.md#c-lsp-trace-logs and then report the issue.
157154
-->
158-
159-
### C# log ###
160-
<details>Post the output from Output-->C# here</details>
155+
`;
161156

162-
### C# LSP Trace Logs ###
163-
<details>Post the output from Output-->C# LSP Trace Logs here. Requires the \`C#\` output window log level to be set to \`Trace\`</details>`;
157+
for (const channel of logChannels) {
158+
const contents = await getLogOutputChannelContents(context, channel);
159+
logInfo += `### ${channel.name} log ###
160+
<details>
161+
162+
\`\`\`
163+
${contents}
164+
\`\`\`
165+
</details>
166+
167+
`;
168+
}
169+
170+
return logInfo;
171+
}
172+
173+
async function getLogOutputChannelContents(
174+
context: vscode.ExtensionContext,
175+
channel: vscode.LogOutputChannel
176+
): Promise<string> {
177+
const logFilePath = vscode.Uri.joinPath(context.logUri, channel.name + '.log');
178+
179+
try {
180+
const fileStat = await vscode.workspace.fs.stat(logFilePath);
181+
if (fileStat.type !== vscode.FileType.File) {
182+
return `Unable to find log file at ${logFilePath}, ${fileStat.type}`;
183+
}
184+
185+
const logContent = await vscode.workspace.fs.readFile(logFilePath);
186+
const contents = Buffer.from(logContent).toString('utf8');
187+
return contents;
188+
} catch (error) {
189+
return `Error reading log file: ${error instanceof Error ? error.message : `${error}`}`;
164190
}
165191
}
166192

test/omnisharp/omnisharpUnitTests/features/reportIssue.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ describe(`${reportIssue.name}`, () => {
3636
extensionPath: 'c:/extensions/xyz-x64',
3737
} as vscode.Extension<any>;
3838

39+
const context = {
40+
extension: {
41+
packageJSON: {
42+
version: csharpExtVersion,
43+
},
44+
},
45+
} as vscode.ExtensionContext;
46+
3947
const fakeDotnetInfo: DotnetInfo = {
4048
FullInfo: 'myDotnetInfo',
4149
Version: '1.0.x',
@@ -59,7 +67,7 @@ describe(`${reportIssue.name}`, () => {
5967
} as vscode.WorkspaceConfiguration);
6068

6169
jest.spyOn(vscode.commands, 'executeCommand').mockImplementation(async (command: string, ...rest: any[]) => {
62-
issueBody = rest[0].issueBody;
70+
issueBody = rest[0].issueBody + rest[0].data;
6371
return {} as any;
6472
});
6573
jest.replaceProperty(vscode.extensions, 'all', [extension1, extension2] as readonly vscode.Extension<any>[]);
@@ -70,38 +78,38 @@ describe(`${reportIssue.name}`, () => {
7078

7179
describe('The body is passed to the vscode clipboard and', () => {
7280
test('it contains the vscode version', async () => {
73-
await reportIssue(csharpExtVersion, getDotnetInfo, isValidForMono, fakeDotnetResolver, fakeMonoResolver);
81+
await reportIssue(context, getDotnetInfo, isValidForMono, [], fakeDotnetResolver, fakeMonoResolver);
7482
expect(issueBody).toContain(`**VSCode version**: ${vscodeVersion}`);
7583
});
7684

7785
test('it contains the csharp extension version', async () => {
78-
await reportIssue(csharpExtVersion, getDotnetInfo, isValidForMono, fakeDotnetResolver, fakeMonoResolver);
86+
await reportIssue(context, getDotnetInfo, isValidForMono, [], fakeDotnetResolver, fakeMonoResolver);
7987
expect(issueBody).toContain(`**C# Extension**: ${csharpExtVersion}`);
8088
});
8189

8290
test('it contains dotnet info', async () => {
83-
await reportIssue(csharpExtVersion, getDotnetInfo, isValidForMono, fakeDotnetResolver, fakeMonoResolver);
91+
await reportIssue(context, getDotnetInfo, isValidForMono, [], fakeDotnetResolver, fakeMonoResolver);
8492
expect(issueBody).toContain(fakeDotnetInfo.FullInfo);
8593
});
8694

8795
test('mono information is obtained when it is a valid mono platform', async () => {
88-
await reportIssue(csharpExtVersion, getDotnetInfo, isValidForMono, fakeDotnetResolver, fakeMonoResolver);
96+
await reportIssue(context, getDotnetInfo, isValidForMono, [], fakeDotnetResolver, fakeMonoResolver);
8997
expect(fakeMonoResolver.getMonoCalled).toEqual(true);
9098
});
9199

92100
test('mono version is put in the body when it is a valid mono platform', async () => {
93-
await reportIssue(csharpExtVersion, getDotnetInfo, isValidForMono, fakeDotnetResolver, fakeMonoResolver);
101+
await reportIssue(context, getDotnetInfo, isValidForMono, [], fakeDotnetResolver, fakeMonoResolver);
94102
expect(fakeMonoResolver.getMonoCalled).toEqual(true);
95103
expect(issueBody).toContain(fakeMonoInfo.version);
96104
});
97105

98106
test('mono information is not obtained when it is not a valid mono platform', async () => {
99-
await reportIssue(csharpExtVersion, getDotnetInfo, false, fakeDotnetResolver, fakeMonoResolver);
107+
await reportIssue(context, getDotnetInfo, false, [], fakeDotnetResolver, fakeMonoResolver);
100108
expect(fakeMonoResolver.getMonoCalled).toEqual(false);
101109
});
102110

103111
test('The url contains the name, publisher and version for all the extensions that are not builtin', async () => {
104-
await reportIssue(csharpExtVersion, getDotnetInfo, isValidForMono, fakeDotnetResolver, fakeMonoResolver);
112+
await reportIssue(context, getDotnetInfo, isValidForMono, [], fakeDotnetResolver, fakeMonoResolver);
105113
expect(issueBody).toContain(extension2.packageJSON.name);
106114
expect(issueBody).toContain(extension2.packageJSON.publisher);
107115
expect(issueBody).toContain(extension2.packageJSON.version);

0 commit comments

Comments
 (0)