diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 9d6f717b64e..0ef429408f2 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -48,7 +48,7 @@ export interface NativeParsedArgs { 'trace-category-filter'?: string; 'trace-options'?: string; 'open-devtools'?: boolean; - log?: string; + log?: string[]; logExtensionHostCommunication?: boolean; 'extensions-dir'?: string; 'extensions-download-dir'?: string; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 8ecac574e85..3953e06a708 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -81,6 +81,7 @@ export interface IEnvironmentService { // --- logging logsPath: string; logLevel?: string; + extensionLogLevel?: [string, string][]; verbose: boolean; isBuilt: boolean; diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 8ff58181a62..934a8657683 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -14,6 +14,8 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ExtensionKind, IDebugParams, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IProductService } from 'vs/platform/product/common/productService'; +export const EXTENSION_IDENTIFIER_WITH_LOG_REGEX = /^([^.]+\..+):(.+)$/; + export interface INativeEnvironmentPaths { /** @@ -216,7 +218,20 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron get isBuilt(): boolean { return !env['VSCODE_DEV']; } get verbose(): boolean { return !!this.args.verbose; } - get logLevel(): string | undefined { return this.args.log; } + + @memoize + get logLevel(): string | undefined { return this.args.log?.find(entry => !EXTENSION_IDENTIFIER_WITH_LOG_REGEX.test(entry)); } + @memoize + get extensionLogLevel(): [string, string][] | undefined { + const result: [string, string][] = []; + for (const entry of this.args.log || []) { + const matches = EXTENSION_IDENTIFIER_WITH_LOG_REGEX.exec(entry); + if (matches && matches[1] && matches[2]) { + result.push([matches[1], matches[2]]); + } + } + return result.length ? result : undefined; + } @memoize get serviceMachineIdResource(): URI { return joinPath(URI.file(this.userDataPath), 'machineid'); } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 9f0f6e5bb06..9ea98d94eb4 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -93,7 +93,7 @@ export const OPTIONS: OptionDescriptions> = { 'version': { type: 'boolean', cat: 't', alias: 'v', description: localize('version', "Print version.") }, 'verbose': { type: 'boolean', cat: 't', global: true, description: localize('verbose', "Print verbose output (implies --wait).") }, - 'log': { type: 'string', cat: 't', args: 'level', global: true, description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.") }, + 'log': { type: 'string[]', cat: 't', args: 'level', global: true, description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'. You can also configure the log level of an extension by passing extension id and log level in the following format: '${publisher}.${name}:${logLevel}'. For example: 'vscode.csharp:trace'. Can receive one or more such entries.") }, 'status': { type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") }, 'prof-startup': { type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup.") }, 'prof-append-timers': { type: 'string' }, diff --git a/src/vs/platform/log/common/fileLog.ts b/src/vs/platform/log/common/fileLog.ts index 4e346d74d0f..e16363b8b6e 100644 --- a/src/vs/platform/log/common/fileLog.ts +++ b/src/vs/platform/log/common/fileLog.ts @@ -147,7 +147,7 @@ export class FileLoggerService extends AbstractLoggerService implements ILoggerS @ILogService logService: ILogService, @IFileService private readonly fileService: IFileService, ) { - super(logService.getLevel(), logService.onDidChangeLogLevel, []); + super(logService.getLevel(), logService.onDidChangeLogLevel); } protected doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger { diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 8a6d33c5c4a..47f09907617 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -112,7 +112,7 @@ export interface ILoggerService { /** * Creates a logger, or gets one if it already exists. */ - createLogger(resource: URI, options?: ILoggerOptions): ILogger; + createLogger(resource: URI, options?: ILoggerOptions, logLevel?: LogLevel): ILogger; /** * Gets an existing logger, if any. @@ -128,12 +128,6 @@ export interface ILoggerService { * Get log level for a logger. */ getLogLevel(resource: URI): LogLevel | undefined; - - /** - * Get default log level for a logger with given name. - * @param name logger name - */ - getDefaultLogLevel(name: string): LogLevel; } export abstract class AbstractLogger extends Disposable { @@ -535,7 +529,6 @@ export abstract class AbstractLoggerService extends Disposable implements ILogge constructor( private logLevel: LogLevel, onDidChangeLogLevel: Event, - private readonly defaultLogLevels: [string, LogLevel][] ) { super(); this._register(onDidChangeLogLevel(logLevel => this.setLevel(logLevel))); @@ -549,11 +542,11 @@ export abstract class AbstractLoggerService extends Disposable implements ILogge return this.loggerItems.get(resource)?.logger; } - createLogger(resource: URI, options?: ILoggerOptions): ILogger { + createLogger(resource: URI, options?: ILoggerOptions, logLevel?: LogLevel): ILogger { let logger = this.loggerItems.get(resource)?.logger; if (!logger) { - const logLevel = options?.always ? LogLevel.Trace : undefined; - logger = this.doCreateLogger(resource, logLevel ?? (options?.name ? this.getDefaultLogLevel(options?.name) : this.logLevel), options); + logLevel = options?.always ? LogLevel.Trace : logLevel; + logger = this.doCreateLogger(resource, logLevel ?? this.logLevel, options); this.loggerItems.set(resource, { logger, logLevel }); } return logger; @@ -587,10 +580,6 @@ export abstract class AbstractLoggerService extends Disposable implements ILogge return logger?.logLevel; } - getDefaultLogLevel(name: string): LogLevel { - return this.defaultLogLevels.find(([loggerName]) => loggerName === name)?.[1] ?? this.logLevel; - } - override dispose(): void { this.loggerItems.forEach(({ logger }) => logger.dispose()); this.loggerItems.clear(); diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index a5ef50d8493..84e6b347ddb 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -111,7 +111,7 @@ export class LoggerChannel implements IServerChannel { export class LoggerChannelClient extends AbstractLoggerService implements ILoggerService { constructor(logLevel: LogLevel, onDidChangeLogLevel: Event, private readonly channel: IChannel) { - super(logLevel, onDidChangeLogLevel, []); + super(logLevel, onDidChangeLogLevel); } createConsoleMainLogger(): ILogger { diff --git a/src/vs/platform/log/node/loggerService.ts b/src/vs/platform/log/node/loggerService.ts index a7cc23f9e86..9e1c2e72581 100644 --- a/src/vs/platform/log/node/loggerService.ts +++ b/src/vs/platform/log/node/loggerService.ts @@ -13,7 +13,7 @@ export class LoggerService extends AbstractLoggerService implements ILoggerServi constructor( @ILogService logService: ILogService ) { - super(logService.getLevel(), logService.onDidChangeLogLevel, []); + super(logService.getLevel(), logService.onDidChangeLogLevel); } protected doCreateLogger(resource: URI, logLevel: LogLevel, options?: ILoggerOptions): ILogger { diff --git a/src/vs/server/node/serverEnvironmentService.ts b/src/vs/server/node/serverEnvironmentService.ts index 646e3ae2af1..432344f146a 100644 --- a/src/vs/server/node/serverEnvironmentService.ts +++ b/src/vs/server/node/serverEnvironmentService.ts @@ -147,7 +147,7 @@ export interface ServerParsedArgs { 'disable-telemetry'?: boolean; 'file-watcher-polling'?: string; - 'log'?: string; + 'log'?: string[]; 'logsPath'?: string; 'force-disable-user-env'?: boolean; diff --git a/src/vs/workbench/api/common/extHostLoggerService.ts b/src/vs/workbench/api/common/extHostLoggerService.ts index b994b3cf890..47ec1627609 100644 --- a/src/vs/workbench/api/common/extHostLoggerService.ts +++ b/src/vs/workbench/api/common/extHostLoggerService.ts @@ -20,7 +20,7 @@ export class ExtHostLoggerService extends AbstractLoggerService implements ExtHo @IExtHostRpcService rpc: IExtHostRpcService, @IExtHostInitDataService initData: IExtHostInitDataService, ) { - super(initData.logLevel, Event.None, []); + super(initData.logLevel, Event.None); this._proxy = rpc.getProxy(MainContext.MainThreadLogger); } diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index ac01aeb7bce..3f1de6629f7 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -8,8 +8,8 @@ import type * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { AbstractMessageLogger, ILogger, ILoggerService, ILogService, log, LogLevel } from 'vs/platform/log/common/log'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { AbstractMessageLogger, ILogger, ILoggerService, ILogService, log, LogLevel, parseLogLevel } from 'vs/platform/log/common/log'; import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/output'; import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; @@ -151,12 +151,13 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { if (isString(languageId) && !languageId.trim()) { throw new Error('illegal argument `languageId`. must not be empty'); } - const extHostOutputChannel = log ? this.doCreateLogOutputChannel(name, extension) : this.doCreateOutputChannel(name, languageId, extension); + const logLevel = this.getDefaultLogLevel(extension); + const extHostOutputChannel = log ? this.doCreateLogOutputChannel(name, logLevel, extension) : this.doCreateOutputChannel(name, languageId, extension); extHostOutputChannel.then(channel => { this.channels.set(channel.id, channel); channel.visible = channel.id === this.visibleChannelId; }); - return log ? this.createExtHostLogOutputChannel(name, >extHostOutputChannel) : this.createExtHostOutputChannel(name, >extHostOutputChannel); + return log ? this.createExtHostLogOutputChannel(name, logLevel, >extHostOutputChannel) : this.createExtHostOutputChannel(name, >extHostOutputChannel); } private async doCreateOutputChannel(name: string, languageId: string | undefined, extension: IExtensionDescription): Promise { @@ -170,14 +171,23 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { return new ExtHostOutputChannel(id, name, logger, this.proxy, extension); } - private async doCreateLogOutputChannel(name: string, extension: IExtensionDescription): Promise { + private async doCreateLogOutputChannel(name: string, logLevel: LogLevel, extension: IExtensionDescription): Promise { const extensionLogDir = await this.createExtensionLogDirectory(extension); const file = this.extHostFileSystemInfo.extUri.joinPath(extensionLogDir, `${name.replace(/[\\/:\*\?"<>\|]/g, '')}.log`); - const logger = this.loggerService.createLogger(file, { name }); + const logger = this.loggerService.createLogger(file, { name }, logLevel); const id = await this.proxy.$register(name, file, true, undefined, extension.identifier.value); return new ExtHostLogOutputChannel(id, name, logger, this.proxy, extension); } + private getDefaultLogLevel(extension: IExtensionDescription): LogLevel { + let logLevel: LogLevel | undefined; + const logLevelValue = this.initData.environment.extensionLogLevel?.find(([identifier]) => ExtensionIdentifier.equals(extension.identifier, identifier))?.[1]; + if (logLevelValue) { + logLevel = parseLogLevel(logLevelValue); + } + return logLevel ?? this.logService.getLevel(); + } + private createExtensionLogDirectory(extension: IExtensionDescription): Thenable { let extensionLogDirectoryPromise = this.extensionLogDirectoryPromise.get(extension.identifier.value); if (!extensionLogDirectoryPromise) { @@ -236,14 +246,13 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { }; } - private createExtHostLogOutputChannel(name: string, channelPromise: Promise): vscode.LogOutputChannel { + private createExtHostLogOutputChannel(name: string, logLevel: LogLevel, channelPromise: Promise): vscode.LogOutputChannel { const disposables = new DisposableStore(); const validate = () => { if (disposables.isDisposed) { throw new Error('Channel has been closed'); } }; - let logLevel = this.logService.getLevel(); const onDidChangeLogLevel = disposables.add(new Emitter()); channelPromise.then(channel => { disposables.add(channel); diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 6b8360ae153..3b530fdbf83 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -697,6 +697,11 @@ export interface IDevelopmentOptions { */ readonly logLevel?: LogLevel; + /** + * Extension log level. + */ + readonly extensionLogLevel?: [string, LogLevel][]; + /** * Location of a module containing extension tests to run once the workbench is open. */ diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index 3d4ee6058e2..3e9559f30ac 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -5,17 +5,19 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { ILogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel, getLogLevel, parseLogLevel } from 'vs/platform/log/common/log'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IOutputService } from 'vs/workbench/services/output/common/output'; +import { IOutputChannelDescriptor, IOutputService } from 'vs/workbench/services/output/common/output'; import { isUndefined } from 'vs/base/common/types'; import { ILogLevelService } from 'vs/workbench/contrib/logs/common/logLevelService'; import { extensionTelemetryLogChannelId, telemetryLogChannelId } from 'vs/workbench/contrib/logs/common/logConstants'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; export class SetLogLevelAction extends Action { @@ -26,19 +28,20 @@ export class SetLogLevelAction extends Action { @IQuickInputService private readonly quickInputService: IQuickInputService, @ILogService private readonly logService: ILogService, @ILogLevelService private readonly logLevelService: ILogLevelService, - @IOutputService private readonly outputService: IOutputService + @IOutputService private readonly outputService: IOutputService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, ) { super(id, label); } override async run(): Promise { - const logger = await this.selectLogger(); - if (!isUndefined(logger)) { - await this.selectLogLevel(logger); + const logChannel = await this.selectLogChannel(); + if (!isUndefined(logChannel)) { + await this.selectLogLevel(logChannel); } } - private async selectLogger(): Promise { + private async selectLogChannel(): Promise { const extensionLogs = [], logs = []; for (const channel of this.outputService.getChannelDescriptors()) { if (!channel.log || channel.id === telemetryLogChannelId || channel.id === extensionTelemetryLogChannelId) { @@ -50,55 +53,65 @@ export class SetLogLevelAction extends Action { logs.push(channel); } } - const entries: ({ id?: string; label: string } | IQuickPickSeparator)[] = []; + const entries: ({ label: string; channel?: IOutputChannelDescriptor } | IQuickPickSeparator)[] = []; entries.push({ label: nls.localize('all', "All") }); entries.push({ type: 'separator', label: nls.localize('loggers', "Logs") }); - for (const { id, label } of logs.sort((a, b) => a.label.localeCompare(b.label))) { - entries.push({ id, label }); + for (const channel of logs.sort((a, b) => a.label.localeCompare(b.label))) { + entries.push({ label: channel.label, channel }); } if (extensionLogs.length && logs.length) { entries.push({ type: 'separator', label: nls.localize('extensionLogs', "Extension Logs") }); } - for (const { id, label } of extensionLogs.sort((a, b) => a.label.localeCompare(b.label))) { - entries.push({ id, label }); + for (const channel of extensionLogs.sort((a, b) => a.label.localeCompare(b.label))) { + entries.push({ label: channel.label, channel }); } const entry = await this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log") }); - return entry ? entry.id ?? null : undefined; + return entry ? entry.channel ?? null : undefined; } - private async selectLogLevel(logger: string | null): Promise { - const current = (logger ? this.logLevelService.getLogLevel(logger) : undefined) ?? this.logService.getLevel(); + private async selectLogLevel(logChannel: IOutputChannelDescriptor | null): Promise { + const defaultLogLevel = this.getDefaultLogLevel(logChannel); + const current = logChannel ? this.logLevelService.getLogLevel(logChannel.id) ?? defaultLogLevel : this.logService.getLevel(); const entries = [ - { label: nls.localize('trace', "Trace"), level: LogLevel.Trace, description: this.getDescription(LogLevel.Trace, current) }, - { label: nls.localize('debug', "Debug"), level: LogLevel.Debug, description: this.getDescription(LogLevel.Debug, current) }, - { label: nls.localize('info', "Info"), level: LogLevel.Info, description: this.getDescription(LogLevel.Info, current) }, - { label: nls.localize('warn', "Warning"), level: LogLevel.Warning, description: this.getDescription(LogLevel.Warning, current) }, - { label: nls.localize('err', "Error"), level: LogLevel.Error, description: this.getDescription(LogLevel.Error, current) }, - { label: nls.localize('critical', "Critical"), level: LogLevel.Critical, description: this.getDescription(LogLevel.Critical, current) }, - { label: nls.localize('off', "Off"), level: LogLevel.Off, description: this.getDescription(LogLevel.Off, current) }, + { label: this.getLabel(nls.localize('trace', "Trace"), LogLevel.Trace, current), level: LogLevel.Trace, description: this.getDescription(LogLevel.Trace, defaultLogLevel) }, + { label: this.getLabel(nls.localize('debug', "Debug"), LogLevel.Debug, current), level: LogLevel.Debug, description: this.getDescription(LogLevel.Debug, defaultLogLevel) }, + { label: this.getLabel(nls.localize('info', "Info"), LogLevel.Info, current), level: LogLevel.Info, description: this.getDescription(LogLevel.Info, defaultLogLevel) }, + { label: this.getLabel(nls.localize('warn', "Warning"), LogLevel.Warning, current), level: LogLevel.Warning, description: this.getDescription(LogLevel.Warning, defaultLogLevel) }, + { label: this.getLabel(nls.localize('err', "Error"), LogLevel.Error, current), level: LogLevel.Error, description: this.getDescription(LogLevel.Error, defaultLogLevel) }, + { label: this.getLabel(nls.localize('critical', "Critical"), LogLevel.Critical, current), level: LogLevel.Critical, description: this.getDescription(LogLevel.Critical, defaultLogLevel) }, + { label: this.getLabel(nls.localize('off', "Off"), LogLevel.Off, current), level: LogLevel.Off, description: this.getDescription(LogLevel.Off, defaultLogLevel) }, ]; - const entry = await this.quickInputService.pick(entries, { placeHolder: logger ? nls.localize('selectLogLevelFor', " {0}: Select log level", this.outputService.getChannelDescriptor(logger)?.label) : nls.localize('selectLogLevel', "Select log level"), activeItem: entries[this.logService.getLevel()] }); + const entry = await this.quickInputService.pick(entries, { placeHolder: logChannel ? nls.localize('selectLogLevelFor', " {0}: Select log level", logChannel?.label) : nls.localize('selectLogLevel', "Select log level"), activeItem: entries[this.logService.getLevel()] }); if (entry) { - if (logger) { - this.logLevelService.setLogLevel(logger, entry.level); + if (logChannel) { + this.logLevelService.setLogLevel(logChannel.id, entry.level); } else { this.logService.setLevel(entry.level); } } } - private getDescription(level: LogLevel, current: LogLevel): string | undefined { - if (DEFAULT_LOG_LEVEL === level && current === level) { - return nls.localize('default and current', "Default & Current"); + private getLabel(label: string, level: LogLevel, current: LogLevel): string { + if (level === current) { + return `$(check) ${label}`; } - if (DEFAULT_LOG_LEVEL === level) { - return nls.localize('default', "Default"); + return label; + } + + private getDescription(level: LogLevel, defaultLogLevel: LogLevel): string | undefined { + return defaultLogLevel === level ? nls.localize('default', "Default") : undefined; + } + + private getDefaultLogLevel(outputChannel: IOutputChannelDescriptor | null): LogLevel { + let logLevel: LogLevel | undefined; + if (outputChannel?.extensionId) { + const logLevelValue = this.environmentService.extensionLogLevel?.find(([id]) => areSameExtensions({ id }, { id: outputChannel.extensionId! }))?.[1]; + if (logLevelValue) { + logLevel = parseLogLevel(logLevelValue); + } } - if (current === level) { - return nls.localize('current', "Current"); - } - return undefined; + return logLevel ?? getLogLevel(this.environmentService); } } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index b4babc0fae6..2aff66a1e1a 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -18,6 +18,7 @@ import { LogLevelToString } from 'vs/platform/log/common/log'; import { isUndefined } from 'vs/base/common/types'; import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { EXTENSION_IDENTIFIER_WITH_LOG_REGEX } from 'vs/platform/environment/common/environmentService'; export const IBrowserWorkbenchEnvironmentService = refineServiceDecorator(IEnvironmentService); @@ -47,7 +48,28 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi get logsPath(): string { return this.logsHome.path; } @memoize - get logLevel(): string | undefined { return this.payload?.get('logLevel') || (this.options.developmentOptions?.logLevel !== undefined ? LogLevelToString(this.options.developmentOptions?.logLevel) : undefined); } + get logLevel(): string | undefined { + const logLevelFromPayload = this.payload?.get('logLevel'); + if (logLevelFromPayload) { + return logLevelFromPayload.split(',').find(entry => !EXTENSION_IDENTIFIER_WITH_LOG_REGEX.test(entry)); + } + return this.options.developmentOptions?.logLevel !== undefined ? LogLevelToString(this.options.developmentOptions?.logLevel) : undefined; + } + + get extensionLogLevel(): [string, string][] | undefined { + const logLevelFromPayload = this.payload?.get('logLevel'); + if (logLevelFromPayload) { + const result: [string, string][] = []; + for (const entry of logLevelFromPayload.split(',')) { + const matches = EXTENSION_IDENTIFIER_WITH_LOG_REGEX.exec(entry); + if (matches && matches[1] && matches[2]) { + result.push([matches[1], matches[2]]); + } + } + return result.length ? result : undefined; + } + return this.options.developmentOptions?.extensionLogLevel !== undefined ? this.options.developmentOptions?.extensionLogLevel.map(([extension, logLevel]) => ([extension, LogLevelToString(logLevel)])) : undefined; + } @memoize get logFile(): URI { return joinPath(this.logsHome, 'window.log'); } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index e0f225e9490..b78a21d763e 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -298,6 +298,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: this._userDataProfilesService.defaultProfile.globalStorageHome, workspaceStorageHome: this._environmentService.workspaceStorageHome, + extensionLogLevel: this._environmentService.extensionLogLevel }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { configuration: workspace.configuration || undefined, diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index 1e854a7f16a..eb9db688f2f 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -54,6 +54,7 @@ export interface IEnvironment { workspaceStorageHome: URI; useHostProxy?: boolean; skipWorkspaceStorageLock?: boolean; + extensionLogLevel?: [string, string][]; } export interface IStaticWorkspaceData { diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts index 5105a62c15e..1cf2e6448eb 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHost.ts @@ -226,7 +226,8 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost { extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: remoteInitData.globalStorageHome, - workspaceStorageHome: remoteInitData.workspaceStorageHome + workspaceStorageHome: remoteInitData.workspaceStorageHome, + extensionLogLevel: this._environmentService.extensionLogLevel }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : { configuration: workspace.configuration, diff --git a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts index c69beb282c9..08247f4396b 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost.ts @@ -450,6 +450,7 @@ export class SandboxLocalProcessExtensionHost implements IExtensionHost { extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: this._userDataProfilesService.defaultProfile.globalStorageHome, workspaceStorageHome: this._environmentService.workspaceStorageHome, + extensionLogLevel: this._environmentService.extensionLogLevel }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { configuration: withNullAsUndefined(workspace.configuration),