diff --git a/resources/report_issue_template.md b/resources/report_issue_template.md index 232626d9bf4d..11cb49166ea5 100644 --- a/resources/report_issue_template.md +++ b/resources/report_issue_template.md @@ -40,7 +40,7 @@ You can attach such things **after** you create your issue on GitHub. ``` -XXX +{3} ```

diff --git a/src/client/common/application/commands/reportIssueCommand.ts b/src/client/common/application/commands/reportIssueCommand.ts index 8c39d0b504e3..b144aae1e145 100644 --- a/src/client/common/application/commands/reportIssueCommand.ts +++ b/src/client/common/application/commands/reportIssueCommand.ts @@ -11,6 +11,7 @@ import { ICommandManager, IWorkspaceService } from '../types'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { IInterpreterService, IInterpreterVersionService } from '../../../interpreter/contracts'; import { identifyEnvironment } from '../../../pythonEnvironments/common/environmentIdentifier'; +import { getPythonOutputChannelContent } from '../../../logging'; /** * Allows the user to report an issue related to the Python extension using our template. @@ -31,6 +32,7 @@ export class ReportIssueCommandHandler implements IExtensionSingleActivationServ private templatePath = path.join(EXTENSION_ROOT_DIR, 'resources', 'report_issue_template.md'); public async openReportIssue(): Promise { + const pythonLogs = await getPythonOutputChannelContent(); const template = await fs.readFile(this.templatePath, 'utf8'); const interpreterPath = (await this.interpreterService.getActiveInterpreter())?.path || 'not-selected'; const pythonVersion = await this.interpreterVersionService.getVersion(interpreterPath, ''); @@ -40,7 +42,7 @@ export class ReportIssueCommandHandler implements IExtensionSingleActivationServ this.commandManager.executeCommand('workbench.action.openIssueReporter', { extensionId: 'ms-python.python', - issueBody: template.format(pythonVersion, virtualEnv, languageServer), + issueBody: template.format(pythonVersion, virtualEnv, languageServer, pythonLogs), }); } } diff --git a/src/client/logging/_global.ts b/src/client/logging/_global.ts index 14eb237419f4..aae751590b2e 100644 --- a/src/client/logging/_global.ts +++ b/src/client/logging/_global.ts @@ -9,10 +9,11 @@ import { getFormatter } from './formatters'; import { LogLevel, resolveLevelName } from './levels'; import { configureLogger, createLogger, getPreDefinedConfiguration, logToAll } from './logger'; import { createTracingDecorator, LogInfo, TraceOptions, tracing as _tracing } from './trace'; -import { getPythonOutputChannelTransport } from './transports'; +import { getPythonOutputChannelTransport, IPythonOutputChannelContent } from './transports'; import { Arguments } from './util'; const globalLogger = createLogger(); +let _globalLoggerContent: IPythonOutputChannelContent; initialize(); /** @@ -61,9 +62,14 @@ export function setLoggingLevel(level: LogLevel | 'off') { export function addOutputChannelLogging(channel: IOutputChannel) { const formatter = getFormatter(); const transport = getPythonOutputChannelTransport(channel, formatter); + _globalLoggerContent = transport; globalLogger.add(transport); } +export function getPythonOutputChannelContent(): Promise { + return _globalLoggerContent?.getContent() ?? ''; +} + // Emit a log message derived from the args to all enabled transports. function log(logLevel: LogLevel, ...args: Arguments) { logToAll([globalLogger], logLevel, args); diff --git a/src/client/logging/index.ts b/src/client/logging/index.ts index 5df347fc680f..1e0209b13624 100644 --- a/src/client/logging/index.ts +++ b/src/client/logging/index.ts @@ -11,5 +11,6 @@ export { logInfo, logVerbose, logWarning, + getPythonOutputChannelContent, traceDecorators, } from './_global'; diff --git a/src/client/logging/transports.ts b/src/client/logging/transports.ts index a8c99af46605..7f0aecba346f 100644 --- a/src/client/logging/transports.ts +++ b/src/client/logging/transports.ts @@ -6,6 +6,7 @@ // delete everything in '../client' except for '../client/logging' before running smoke tests. import * as logform from 'logform'; +import { EOL } from 'os'; import * as path from 'path'; import { OutputChannel } from 'vscode'; import * as winston from 'winston'; @@ -70,18 +71,29 @@ export function getConsoleTransport(formatter: logform.Format): Transport { }); } -class PythonOutputChannelTransport extends Transport { +export interface IPythonOutputChannelContent { + getContent(): Promise; +} + +class PythonOutputChannelTransport extends Transport implements IPythonOutputChannelContent { + private content: string[] = []; constructor(private readonly channel: OutputChannel, options?: any) { super(options); } public log?(info: { message: string; [formattedMessage]: string }, next: () => void): any { setImmediate(() => this.emit('logged', info)); - this.channel.appendLine(info[formattedMessage] || info.message); + const message = info[formattedMessage] || info.message; + this.channel.appendLine(message); + this.content.push(message); if (next) { next(); } } + + public getContent(): Promise { + return Promise.resolve(this.content.join(EOL)); + } } // Create a Python output channel targeting transport that can be added to a winston logger. diff --git a/src/test/common/application/commands/issueTemplateVenv1.md b/src/test/common/application/commands/issueTemplateVenv1.md index a8e4c29ea8c4..15a529904827 100644 --- a/src/test/common/application/commands/issueTemplateVenv1.md +++ b/src/test/common/application/commands/issueTemplateVenv1.md @@ -40,7 +40,7 @@ You can attach such things **after** you create your issue on GitHub. ``` -XXX +Python Output ```

diff --git a/src/test/common/application/commands/reportIssueCommand.unit.test.ts b/src/test/common/application/commands/reportIssueCommand.unit.test.ts index f19d94473195..f58c1aaa766d 100644 --- a/src/test/common/application/commands/reportIssueCommand.unit.test.ts +++ b/src/test/common/application/commands/reportIssueCommand.unit.test.ts @@ -6,7 +6,7 @@ import * as sinon from 'sinon'; import * as fs from 'fs-extra'; import * as path from 'path'; -import { anyString, anything, capture, instance, mock, verify, when } from 'ts-mockito'; +import { anything, capture, instance, mock, verify, when } from 'ts-mockito'; import { expect } from 'chai'; import { LanguageServerType } from '../../../../client/activation/types'; import { CommandManager } from '../../../../client/common/application/commandManager'; @@ -20,6 +20,7 @@ import * as EnvIdentifier from '../../../../client/pythonEnvironments/common/env import { MockWorkspaceConfiguration } from '../../../startPage/mockWorkspaceConfig'; import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../../constants'; import { InterpreterService } from '../../../../client/interpreter/interpreterService'; +import * as Logging from '../../../../client/logging/_global'; suite('Report Issue Command', () => { let reportIssueCommandHandler: ReportIssueCommandHandler; @@ -28,6 +29,7 @@ suite('Report Issue Command', () => { let interpreterVersionService: IInterpreterVersionService; let interpreterService: IInterpreterService; let identifyEnvironmentStub: sinon.SinonStub; + let getPythonOutputContentStub: sinon.SinonStub; setup(async () => { interpreterVersionService = mock(InterpreterVersionService); @@ -35,6 +37,7 @@ suite('Report Issue Command', () => { cmdManager = mock(CommandManager); interpreterService = mock(InterpreterService); + when(cmdManager.executeCommand('workbench.action.openIssueReporter', anything())).thenResolve(); when(interpreterVersionService.getVersion(anything(), anything())).thenResolve('3.9.0'); when(workspaceService.getConfiguration('python')).thenReturn( new MockWorkspaceConfiguration({ @@ -46,19 +49,21 @@ suite('Report Issue Command', () => { identifyEnvironmentStub.resolves(PythonEnvKind.Venv); cmdManager = mock(CommandManager); + + getPythonOutputContentStub = sinon.stub(Logging, 'getPythonOutputChannelContent'); + getPythonOutputContentStub.resolves('Python Output'); reportIssueCommandHandler = new ReportIssueCommandHandler( instance(cmdManager), instance(workspaceService), instance(interpreterService), instance(interpreterVersionService), ); - - when(cmdManager.executeCommand(anyString(), anything())).thenResolve(); await reportIssueCommandHandler.activate(); }); teardown(() => { identifyEnvironmentStub.restore(); + getPythonOutputContentStub.restore(); }); test('Test if issue body is filled', async () => {