From 5dbd0935814acdd334e4c8c2dca1997986121a33 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 4 Sep 2018 15:26:19 +0200 Subject: [PATCH] #57618: ExtHost: Implement writable output channels using spdlog --- src/vs/platform/output/node/outputAppender.ts | 24 +++++ .../mainThreadOutputService.ts | 43 +++++---- src/vs/workbench/api/node/extHost.api.impl.ts | 5 +- src/vs/workbench/api/node/extHost.protocol.ts | 9 +- .../api/node/extHostOutputService.ts | 87 +++++++++++++------ .../extensions.contribution.ts | 2 +- .../electron-browser/logs.contribution.ts | 8 +- .../parts/output/browser/outputActions.ts | 40 ++++----- .../workbench/parts/output/common/output.ts | 29 ++++--- .../output/electron-browser/outputServices.ts | 42 ++++----- .../quickopen/browser/viewPickerHandler.ts | 2 +- .../electron-browser/task.contribution.ts | 2 +- 12 files changed, 182 insertions(+), 111 deletions(-) create mode 100644 src/vs/platform/output/node/outputAppender.ts diff --git a/src/vs/platform/output/node/outputAppender.ts b/src/vs/platform/output/node/outputAppender.ts new file mode 100644 index 0000000000000..20e6f6b9710b9 --- /dev/null +++ b/src/vs/platform/output/node/outputAppender.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { RotatingLogger } from 'spdlog'; + +export class OutputAppender { + + private appender: RotatingLogger; + + constructor(name: string, file: string) { + this.appender = new RotatingLogger(name, file, 1024 * 1024 * 30, 1); + this.appender.clearFormatters(); + } + + append(content: string): void { + this.appender.critical(content); + } + + flush(): void { + this.appender.flush(); + } + +} \ No newline at end of file diff --git a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts index 905dc07490de8..df53cd447248f 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts @@ -11,6 +11,7 @@ import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { MainThreadOutputServiceShape, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { UriComponents, URI } from 'vs/base/common/uri'; @extHostNamedCustomer(MainContext.MainThreadOutputService) export class MainThreadOutputService implements MainThreadOutputServiceShape { @@ -34,28 +35,33 @@ export class MainThreadOutputService implements MainThreadOutputServiceShape { // Leave all the existing channels intact (e.g. might help with troubleshooting) } - public $append(channelId: string, label: string, value: string): TPromise { - this._getChannel(channelId, label).append(value); - return undefined; + public $register(id: string, label: string, file?: UriComponents): TPromise { + Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: file ? URI.revive(file) : null, log: false }); + return TPromise.as(null); } - public $clear(channelId: string, label: string): TPromise { - this._getChannel(channelId, label).clear(); + public $append(channelId: string, value: string): TPromise { + const channel = this._getChannel(channelId); + if (channel) { + channel.append(value); + } return undefined; } - public $reveal(channelId: string, label: string, preserveFocus: boolean): TPromise { - const channel = this._getChannel(channelId, label); - this._outputService.showChannel(channel.id, preserveFocus); + public $clear(channelId: string): TPromise { + const channel = this._getChannel(channelId); + if (channel) { + channel.clear(); + } return undefined; } - private _getChannel(channelId: string, label: string): IOutputChannel { - if (!Registry.as(Extensions.OutputChannels).getChannel(channelId)) { - Registry.as(Extensions.OutputChannels).registerChannel(channelId, label); + public $reveal(channelId: string, preserveFocus: boolean): TPromise { + const channel = this._getChannel(channelId); + if (channel) { + this._outputService.showChannel(channel.id, preserveFocus); } - - return this._outputService.getChannel(channelId); + return undefined; } public $close(channelId: string): TPromise { @@ -67,8 +73,15 @@ export class MainThreadOutputService implements MainThreadOutputServiceShape { return undefined; } - public $dispose(channelId: string, label: string): TPromise { - this._getChannel(channelId, label).dispose(); + public $dispose(channelId: string): TPromise { + const channel = this._getChannel(channelId); + if (channel) { + channel.dispose(); + } return undefined; } + + private _getChannel(channelId: string): IOutputChannel { + return this._outputService.getChannel(channelId); + } } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 5537b87fc61dd..97df467eac952 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -61,6 +61,8 @@ import { ExtHostWebviews } from 'vs/workbench/api/node/extHostWebview'; import { ExtHostComments } from './extHostComments'; import { ExtHostSearch } from './extHostSearch'; import { ExtHostUrls } from './extHostUrls'; +import { toLocalISOString } from 'vs/base/common/date'; +import { posix } from 'path'; export interface IExtensionApiFactory { (extension: IExtensionDescription): typeof vscode; @@ -136,7 +138,8 @@ export function createApiFactory( const extHostMessageService = new ExtHostMessageService(rpcProtocol); const extHostDialogs = new ExtHostDialogs(rpcProtocol); const extHostStatusBar = new ExtHostStatusBar(rpcProtocol); - const extHostOutputService = new ExtHostOutputService(rpcProtocol); + const outputDir = posix.join(initData.logsPath, `output_logging_${initData.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + const extHostOutputService = new ExtHostOutputService(outputDir, rpcProtocol); const extHostLanguages = new ExtHostLanguages(rpcProtocol); // Register API-ish commands diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 84870e5e93a4e..49421a3b34131 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -302,11 +302,12 @@ export interface MainThreadMessageServiceShape extends IDisposable { } export interface MainThreadOutputServiceShape extends IDisposable { - $append(channelId: string, label: string, value: string): TPromise; - $clear(channelId: string, label: string): TPromise; - $dispose(channelId: string, label: string): TPromise; - $reveal(channelId: string, label: string, preserveFocus: boolean): TPromise; + $register(channelId: string, label: string, file?: UriComponents): TPromise; + $append(channelId: string, value: string): TPromise; + $clear(channelId: string): TPromise; + $reveal(channelId: string, preserveFocus: boolean): TPromise; $close(channelId: string): TPromise; + $dispose(channelId: string): TPromise; } export interface MainThreadProgressShape extends IDisposable { diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index b36f0bfb25ec3..d89c64bac4293 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -6,19 +6,24 @@ import { MainContext, MainThreadOutputServiceShape, IMainContext } from './extHost.protocol'; import * as vscode from 'vscode'; +import { URI } from 'vs/base/common/uri'; +import { posix } from 'path'; +import { OutputAppender } from 'vs/platform/output/node/outputAppender'; +import { TPromise } from 'vs/base/common/winjs.base'; -export class ExtHostOutputChannel implements vscode.OutputChannel { +export abstract class AbstractExtHostOutputChannel implements vscode.OutputChannel { private static _idPool = 1; - private _proxy: MainThreadOutputServiceShape; - private _name: string; - private _id: string; + protected readonly _id: string; + private readonly _name: string; + protected readonly _proxy: MainThreadOutputServiceShape; + protected _registerationPromise: TPromise = TPromise.as(null); private _disposed: boolean; constructor(name: string, proxy: MainThreadOutputServiceShape) { + this._id = 'extension-output-#' + (AbstractExtHostOutputChannel._idPool++); this._name = name; - this._id = 'extension-output-#' + (ExtHostOutputChannel._idPool++); this._proxy = proxy; } @@ -26,18 +31,7 @@ export class ExtHostOutputChannel implements vscode.OutputChannel { return this._name; } - dispose(): void { - if (!this._disposed) { - this._proxy.$dispose(this._id, this._name).then(() => { - this._disposed = true; - }); - } - } - - append(value: string): void { - this.validate(); - this._proxy.$append(this._id, this._name, value); - } + abstract append(value: string): void; appendLine(value: string): void { this.validate(); @@ -46,44 +40,81 @@ export class ExtHostOutputChannel implements vscode.OutputChannel { clear(): void { this.validate(); - this._proxy.$clear(this._id, this._name); + this._registerationPromise.then(() => this._proxy.$clear(this._id)); } show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { this.validate(); - if (typeof columnOrPreserveFocus === 'boolean') { - preserveFocus = columnOrPreserveFocus; - } - - this._proxy.$reveal(this._id, this._name, preserveFocus); + this._registerationPromise.then(() => this._proxy.$reveal(this._id, typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus)); } hide(): void { this.validate(); - this._proxy.$close(this._id); + this._registerationPromise.then(() => this._proxy.$close(this._id)); } - private validate(): void { + protected validate(): void { if (this._disposed) { throw new Error('Channel has been closed'); } } + + dispose(): void { + if (!this._disposed) { + this._registerationPromise + .then(() => this._proxy.$dispose(this._id)) + .then(() => this._disposed = true); + } + } +} + +export class ExtHostOutputChannel extends AbstractExtHostOutputChannel { + + constructor(name: string, proxy: MainThreadOutputServiceShape) { + super(name, proxy); + this._registerationPromise = proxy.$register(this._id, name); + } + + append(value: string): void { + this.validate(); + this._registerationPromise.then(() => this._proxy.$append(this._id, value)); + } +} + +export class ExtHostLoggingOutputChannel extends AbstractExtHostOutputChannel { + + private _appender: OutputAppender; + + constructor(name: string, outputDir: string, proxy: MainThreadOutputServiceShape) { + super(name, proxy); + const file = URI.file(posix.join(outputDir, `${this._id}.log`)); + this._appender = new OutputAppender(this._id, file.fsPath); + this._registerationPromise = proxy.$register(this._id, this.name, file); + + } + + append(value: string): void { + this.validate(); + this._appender.append(value); + } } export class ExtHostOutputService { private _proxy: MainThreadOutputServiceShape; + private _outputDir: string; - constructor(mainContext: IMainContext) { + constructor(outputDir: string, mainContext: IMainContext) { + this._outputDir = outputDir; this._proxy = mainContext.getProxy(MainContext.MainThreadOutputService); } - createOutputChannel(name: string): vscode.OutputChannel { + createOutputChannel(name: string, logging?: boolean): vscode.OutputChannel { name = name.trim(); if (!name) { throw new Error('illegal argument `name`. must not be falsy'); } else { - return new ExtHostOutputChannel(name, this._proxy); + return logging ? new ExtHostLoggingOutputChannel(name, this._outputDir, this._proxy) : new ExtHostOutputChannel(name, this._proxy); } } } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index f483d572216c2..a942e6f13bb61 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -55,7 +55,7 @@ workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting); Registry.as(OutputExtensions.OutputChannels) - .registerChannel(ExtensionsChannelId, ExtensionsLabel); + .registerChannel({ id: ExtensionsChannelId, label: ExtensionsLabel, log: false }); // Quickopen Registry.as(Extensions.Quickopen).registerQuickOpenHandler( diff --git a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts index cfc34a2d7c0ae..9d04687681e7b 100644 --- a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts +++ b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts @@ -29,10 +29,10 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { ) { super(); let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - outputChannelRegistry.registerChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Log (Main)"), URI.file(join(this.environmentService.logsPath, `main.log`))); - outputChannelRegistry.registerChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Log (Shared)"), URI.file(join(this.environmentService.logsPath, `sharedprocess.log`))); - outputChannelRegistry.registerChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Log (Window)"), URI.file(join(this.environmentService.logsPath, `renderer${this.windowService.getCurrentWindowId()}.log`))); - outputChannelRegistry.registerChannel(Constants.extHostLogChannelId, nls.localize('extensionsLog', "Log (Extension Host)"), URI.file(join(this.environmentService.logsPath, `exthost${this.windowService.getCurrentWindowId()}.log`))); + outputChannelRegistry.registerChannel({ id: Constants.mainLogChannelId, label: nls.localize('mainLog', "Log (Main)"), file: URI.file(join(this.environmentService.logsPath, `main.log`)), log: true }); + outputChannelRegistry.registerChannel({ id: Constants.sharedLogChannelId, label: nls.localize('sharedLog', "Log (Shared)"), file: URI.file(join(this.environmentService.logsPath, `sharedprocess.log`)), log: true }); + outputChannelRegistry.registerChannel({ id: Constants.rendererLogChannelId, label: nls.localize('rendererLog', "Log (Window)"), file: URI.file(join(this.environmentService.logsPath, `renderer${this.windowService.getCurrentWindowId()}.log`)), log: true }); + outputChannelRegistry.registerChannel({ id: Constants.extHostLogChannelId, label: nls.localize('extensionsLog', "Log (Extension Host)"), file: URI.file(join(this.environmentService.logsPath, `exthost${this.windowService.getCurrentWindowId()}.log`)), log: true }); const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); diff --git a/src/vs/workbench/parts/output/browser/outputActions.ts b/src/vs/workbench/parts/output/browser/outputActions.ts index 80a94dcfe4a5b..8190d5131a047 100644 --- a/src/vs/workbench/parts/output/browser/outputActions.ts +++ b/src/vs/workbench/parts/output/browser/outputActions.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IAction, Action } from 'vs/base/common/actions'; -import { IOutputService, OUTPUT_PANEL_ID, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannelIdentifier, COMMAND_OPEN_LOG_VIEWER } from 'vs/workbench/parts/output/common/output'; +import { IOutputService, OUTPUT_PANEL_ID, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannelDescriptor, COMMAND_OPEN_LOG_VIEWER } from 'vs/workbench/parts/output/common/output'; import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -20,7 +20,6 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { Registry } from 'vs/platform/registry/common/platform'; import { groupBy } from 'vs/base/common/arrays'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { URI } from 'vs/base/common/uri'; export class ToggleOutputAction extends TogglePanelAction { @@ -112,8 +111,8 @@ export class SwitchOutputActionItem extends SelectActionItem { private static readonly SEPARATOR = '─────────'; - private normalChannels: IOutputChannelIdentifier[]; - private fileChannels: IOutputChannelIdentifier[]; + private outputChannels: IOutputChannelDescriptor[]; + private logChannels: IOutputChannelDescriptor[]; constructor( action: IAction, @@ -133,30 +132,30 @@ export class SwitchOutputActionItem extends SelectActionItem { } protected getActionContext(option: string, index: number): string { - const channel = index < this.normalChannels.length ? this.normalChannels[index] : this.fileChannels[index - this.normalChannels.length - 1]; + const channel = index < this.outputChannels.length ? this.outputChannels[index] : this.logChannels[index - this.outputChannels.length - 1]; return channel ? channel.id : option; } private updateOtions(selectedChannel: string): void { - const groups = groupBy(this.outputService.getChannels(), (c1: IOutputChannelIdentifier, c2: IOutputChannelIdentifier) => { - if (!c1.file && c2.file) { + const groups = groupBy(this.outputService.getChannelDescriptors(), (c1: IOutputChannelDescriptor, c2: IOutputChannelDescriptor) => { + if (!c1.log && c2.log) { return -1; } - if (c1.file && !c2.file) { + if (c1.log && !c2.log) { return 1; } return 0; }); - this.normalChannels = groups[0] || []; - this.fileChannels = groups[1] || []; - const showSeparator = this.normalChannels.length && this.fileChannels.length; - const separatorIndex = showSeparator ? this.normalChannels.length : -1; - const options: string[] = [...this.normalChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionItem.SEPARATOR] : []), ...this.fileChannels.map(c => c.label)]; + this.outputChannels = groups[0] || []; + this.logChannels = groups[1] || []; + const showSeparator = this.outputChannels.length && this.logChannels.length; + const separatorIndex = showSeparator ? this.outputChannels.length : -1; + const options: string[] = [...this.outputChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionItem.SEPARATOR] : []), ...this.logChannels.map(c => c.label)]; let selected = 0; if (selectedChannel) { - selected = this.normalChannels.map(c => c.id).indexOf(selectedChannel); + selected = this.outputChannels.map(c => c.id).indexOf(selectedChannel); if (selected === -1) { - selected = separatorIndex + 1 + this.fileChannels.map(c => c.id).indexOf(selectedChannel); + selected = separatorIndex + 1 + this.logChannels.map(c => c.id).indexOf(selectedChannel); } } this.setOptions(options, Math.max(0, selected), separatorIndex !== -1 ? separatorIndex : void 0); @@ -180,17 +179,16 @@ export class OpenLogOutputFile extends Action { } private update(): void { - const logFile = this.getActiveLogChannelFile(); - this.enabled = !!logFile; + this.enabled = this.isLogChannel(); } public run(): TPromise { - return this.commandService.executeCommand(COMMAND_OPEN_LOG_VIEWER, this.getActiveLogChannelFile()); + return this.commandService.executeCommand(COMMAND_OPEN_LOG_VIEWER, this.isLogChannel()); } - private getActiveLogChannelFile(): URI { + private isLogChannel(): boolean { const channel = this.outputService.getActiveChannel(); - const identifier = channel ? this.outputService.getChannels().filter(c => c.id === channel.id)[0] : null; - return identifier ? identifier.file : null; + const descriptor = channel ? this.outputService.getChannelDescriptors().filter(c => c.id === channel.id)[0] : null; + return descriptor && descriptor.log; } } diff --git a/src/vs/workbench/parts/output/common/output.ts b/src/vs/workbench/parts/output/common/output.ts index 795ac5acf9e9b..5579a94589886 100644 --- a/src/vs/workbench/parts/output/common/output.ts +++ b/src/vs/workbench/parts/output/common/output.ts @@ -78,9 +78,9 @@ export interface IOutputService { getChannel(id: string): IOutputChannel; /** - * Returns an array of all known output channels as identifiers. + * Returns an array of all known output channels descriptors. */ - getChannels(): IOutputChannelIdentifier[]; + getChannelDescriptors(): IOutputChannelDescriptor[]; /** * Returns the currently active channel. @@ -132,9 +132,10 @@ export interface IOutputChannel { dispose(): void; } -export interface IOutputChannelIdentifier { +export interface IOutputChannelDescriptor { id: string; label: string; + log: boolean; file?: URI; } @@ -146,17 +147,17 @@ export interface IOutputChannelRegistry { /** * Make an output channel known to the output world. */ - registerChannel(id: string, name: string, file?: URI): void; + registerChannel(descriptor: IOutputChannelDescriptor): void; /** * Returns the list of channels known to the output world. */ - getChannels(): IOutputChannelIdentifier[]; + getChannels(): IOutputChannelDescriptor[]; /** * Returns the channel with the passed id. */ - getChannel(id: string): IOutputChannelIdentifier; + getChannel(id: string): IOutputChannelDescriptor; /** * Remove the output channel with the passed id. @@ -165,7 +166,7 @@ export interface IOutputChannelRegistry { } class OutputChannelRegistry implements IOutputChannelRegistry { - private channels = new Map(); + private channels = new Map(); private readonly _onDidRegisterChannel: Emitter = new Emitter(); readonly onDidRegisterChannel: Event = this._onDidRegisterChannel.event; @@ -173,20 +174,20 @@ class OutputChannelRegistry implements IOutputChannelRegistry { private readonly _onDidRemoveChannel: Emitter = new Emitter(); readonly onDidRemoveChannel: Event = this._onDidRemoveChannel.event; - public registerChannel(id: string, label: string, file?: URI): void { - if (!this.channels.has(id)) { - this.channels.set(id, { id, label, file }); - this._onDidRegisterChannel.fire(id); + public registerChannel(descriptor: IOutputChannelDescriptor): void { + if (!this.channels.has(descriptor.id)) { + this.channels.set(descriptor.id, descriptor); + this._onDidRegisterChannel.fire(descriptor.id); } } - public getChannels(): IOutputChannelIdentifier[] { - const result: IOutputChannelIdentifier[] = []; + public getChannels(): IOutputChannelDescriptor[] { + const result: IOutputChannelDescriptor[] = []; this.channels.forEach(value => result.push(value)); return result; } - public getChannel(id: string): IOutputChannelIdentifier { + public getChannel(id: string): IOutputChannelDescriptor { return this.channels.get(id); } diff --git a/src/vs/workbench/parts/output/electron-browser/outputServices.ts b/src/vs/workbench/parts/output/electron-browser/outputServices.ts index 64f6b8c76d337..102597ed54659 100644 --- a/src/vs/workbench/parts/output/electron-browser/outputServices.ts +++ b/src/vs/workbench/parts/output/electron-browser/outputServices.ts @@ -15,7 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorOptions } from 'vs/workbench/common/editor'; -import { IOutputChannelIdentifier, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, LOG_SCHEME, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/parts/output/common/output'; +import { IOutputChannelDescriptor, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, LOG_SCHEME, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/parts/output/common/output'; import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -31,7 +31,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IPanel } from 'vs/workbench/common/panel'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { RotatingLogger } from 'spdlog'; import { toLocalISOString } from 'vs/base/common/date'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { ILogService } from 'vs/platform/log/common/log'; @@ -39,6 +38,7 @@ import { Schemas } from 'vs/base/common/network'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { OutputAppender } from 'vs/platform/output/node/outputAppender'; const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; @@ -83,6 +83,7 @@ abstract class AbstractFileOutputChannel extends Disposable { protected _onDispose: Emitter = new Emitter(); readonly onDispose: Event = this._onDispose.event; + private readonly mimeType: string; protected modelUpdater: RunOnceScheduler; protected model: ITextModel; readonly file: URI; @@ -91,25 +92,25 @@ abstract class AbstractFileOutputChannel extends Disposable { protected endOffset: number = 0; constructor( - protected readonly outputChannelIdentifier: IOutputChannelIdentifier, + protected readonly outputChannelDescriptor: IOutputChannelDescriptor, private readonly modelUri: URI, - private mimeType: string, protected fileService: IFileService, protected modelService: IModelService, protected modeService: IModeService, ) { super(); - this.file = this.outputChannelIdentifier.file; + this.mimeType = outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME; + this.file = this.outputChannelDescriptor.file; this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); this._register(toDisposable(() => this.modelUpdater.cancel())); } get id(): string { - return this.outputChannelIdentifier.id; + return this.outputChannelDescriptor.id; } get label(): string { - return this.outputChannelIdentifier.label; + return this.outputChannelDescriptor.label; } clear(): void { @@ -162,14 +163,14 @@ abstract class AbstractFileOutputChannel extends Disposable { */ class OutputChannelBackedByFile extends AbstractFileOutputChannel implements OutputChannel { - private outputWriter: RotatingLogger; + private appender: OutputAppender; private appendedMessage = ''; private loadingFromFileInProgress: boolean = false; private resettingDelayer: ThrottledDelayer; private readonly rotatingFilePath: string; constructor( - outputChannelIdentifier: IOutputChannelIdentifier, + outputChannelDescriptor: IOutputChannelDescriptor, outputDir: string, modelUri: URI, @IFileService fileService: IFileService, @@ -177,12 +178,11 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannel implements Out @IModeService modeService: IModeService, @ILogService logService: ILogService ) { - super({ ...outputChannelIdentifier, file: URI.file(paths.join(outputDir, `${outputChannelIdentifier.id}.log`)) }, modelUri, OUTPUT_MIME, fileService, modelService, modeService); + super({ ...outputChannelDescriptor, file: URI.file(paths.join(outputDir, `${outputChannelDescriptor.id}.log`)) }, modelUri, fileService, modelService, modeService); // Use one rotating file to check for main file reset - this.outputWriter = new RotatingLogger(this.id, this.file.fsPath, 1024 * 1024 * 30, 1); - this.outputWriter.clearFormatters(); - this.rotatingFilePath = `${outputChannelIdentifier.id}.1.log`; + this.appender = new OutputAppender(this.id, this.file.fsPath); + this.rotatingFilePath = `${outputChannelDescriptor.id}.1.log`; this._register(watchOutputDirectory(paths.dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file))); this.resettingDelayer = new ThrottledDelayer(50); @@ -264,11 +264,11 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannel implements Out } private write(content: string): void { - this.outputWriter.critical(content); + this.appender.append(content); } private flush(): void { - this.outputWriter.flush(); + this.appender.flush(); } } @@ -323,14 +323,14 @@ class FileOutputChannel extends AbstractFileOutputChannel implements OutputChann private updateInProgress: boolean = false; constructor( - outputChannelIdentifier: IOutputChannelIdentifier, + outputChannelDescriptor: IOutputChannelDescriptor, modelUri: URI, @IFileService fileService: IFileService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, @ILogService logService: ILogService, ) { - super(outputChannelIdentifier, modelUri, LOG_MIME, fileService, modelService, modeService); + super(outputChannelDescriptor, modelUri, fileService, modelService, modeService); this.fileHandler = this._register(new OutputFileListener(this.file, this.fileService)); this._register(this.fileHandler.onDidContentChange(() => this.onDidContentChange())); @@ -426,7 +426,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo // Set active channel to first channel if not set if (!this.activeChannel) { - const channels = this.getChannels(); + const channels = this.getChannelDescriptors(); this.activeChannel = channels && channels.length > 0 ? this.getChannel(channels[0].id) : null; } @@ -461,7 +461,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo return this.channels.get(id); } - getChannels(): IOutputChannelIdentifier[] { + getChannelDescriptors(): IOutputChannelDescriptor[] { return Registry.as(Extensions.OutputChannels).getChannels(); } @@ -521,7 +521,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo channel.onDispose(() => { Registry.as(Extensions.OutputChannels).removeChannel(id); if (this.activeChannel === channel) { - const channels = this.getChannels(); + const channels = this.getChannelDescriptors(); if (this.isPanelShown() && channels.length) { this.doShowChannel(this.getChannel(channels[0].id), true); this._onActiveOutputChannel.fire(channels[0].id); @@ -610,7 +610,7 @@ export class LogContentProvider { let channel = this.channels.get(id); if (!channel) { const channelDisposables: IDisposable[] = []; - channel = this.instantiationService.createInstance(FileOutputChannel, { id, label: '', file: resource.with({ scheme: Schemas.file }) }, resource); + channel = this.instantiationService.createInstance(FileOutputChannel, { id, label: '', file: resource.with({ scheme: Schemas.file }), log: true }, resource); channel.onDispose(() => dispose(channelDisposables), channelDisposables); this.channels.set(id, channel); } diff --git a/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts index 836f7c16304ff..afa60393c8fc6 100644 --- a/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts @@ -167,7 +167,7 @@ export class ViewPickerHandler extends QuickOpenHandler { }); // Output Channels - const channels = this.outputService.getChannels(); + const channels = this.outputService.getChannelDescriptors(); channels.forEach((channel, index) => { const outputCategory = nls.localize('channels', "Output"); const entry = new ViewEntry(channel.label, outputCategory, () => this.outputService.showChannel(channel.id)); diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 03c83e546f430..0df9e4e274d18 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -2503,7 +2503,7 @@ MenuRegistry.addCommand({ id: 'workbench.action.tasks.configureDefaultTestTask', // Tasks Output channel. Register it before using it in Task Service. let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); -outputChannelRegistry.registerChannel(TaskService.OutputChannelId, TaskService.OutputChannelLabel); +outputChannelRegistry.registerChannel({ id: TaskService.OutputChannelId, label: TaskService.OutputChannelLabel, log: false }); // Task Service registerSingleton(ITaskService, TaskService);