diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 2f0521cf00e..17fdc47c3e7 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -38,7 +38,8 @@ import { ipcRenderer } from 'electron'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createSharedProcessContributions } from 'vs/code/electron-browser/sharedProcess/contrib/contributions'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; -import { ILogService } from 'vs/platform/log/common/log'; +import { ILogService, FollowerLogService } from 'vs/platform/log/common/log'; +import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -81,7 +82,8 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I const services = new ServiceCollection(); const environmentService = new EnvironmentService(initData.args, process.execPath); - const logService = createSpdLogService('sharedprocess', environmentService); + const logLevelClient = new LogLevelChannelClient(server.getChannel('loglevel', { route: () => 'main' })); + const logService = new FollowerLogService(logLevelClient, createSpdLogService('sharedprocess', environmentService)); process.once('exit', () => logService.dispose()); logService.info('main', JSON.stringify(configuration)); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 24fb6d6ca51..eec6c2bae9b 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -57,6 +57,7 @@ import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateServ import { IIssueService } from 'vs/platform/issue/common/issue'; import { IssueChannel } from 'vs/platform/issue/common/issueIpc'; import { IssueService } from 'vs/platform/issue/electron-main/issueService'; +import { LogLevelChannel } from 'vs/platform/log/common/logIpc'; export class CodeApplication { @@ -378,6 +379,11 @@ export class CodeApplication { this.electronIpcServer.registerChannel('windows', windowsChannel); this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel)); + // Log level management + const logLevelChannel = new LogLevelChannel(accessor.get(ILogService)); + this.electronIpcServer.registerChannel('loglevel', logLevelChannel); + this.sharedProcessClient.done(client => client.registerChannel('loglevel', logLevelChannel)); + // Lifecycle this.lifecycleService.ready(); diff --git a/src/vs/platform/log/common/bufferLog.ts b/src/vs/platform/log/common/bufferLog.ts index caa628e51da..65fa32adb0e 100644 --- a/src/vs/platform/log/common/bufferLog.ts +++ b/src/vs/platform/log/common/bufferLog.ts @@ -5,7 +5,7 @@ 'use strict'; -import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log'; interface ILog { level: LogLevel; @@ -24,17 +24,12 @@ function getLogFunction(logger: ILogService, level: LogLevel): Function { } } -export class BufferLogService implements ILogService { +export class BufferLogService extends AbstractLogService implements ILogService { _serviceBrand: any; private buffer: ILog[] = []; private _logger: ILogService | undefined = undefined; - constructor( - private level: LogLevel = LogLevel.Error - ) { - } - set logger(logger: ILogService) { this._logger = logger; @@ -46,19 +41,11 @@ export class BufferLogService implements ILogService { this.buffer = []; } - setLevel(logLevel: LogLevel): void { - this.level = logLevel; - } - - getLevel(): LogLevel { - return this.level; - } - private _log(level: LogLevel, args: IArguments): void { if (this._logger) { const fn = getLogFunction(this._logger, level); fn.apply(this._logger, args); - } else if (this.level <= level) { + } else if (this.getLevel() <= level) { this.buffer.push({ level, args }); } } diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts new file mode 100644 index 00000000000..9eb0764c54e --- /dev/null +++ b/src/vs/platform/log/common/logIpc.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { LogLevel, ILogService } from 'vs/platform/log/common/log'; +import Event, { buffer } from 'vs/base/common/event'; + +export interface ILogLevelManagementChannel extends IChannel { + call(command: 'event:onDidChangeLogLevel'): TPromise; + call(command: 'setLogLevel', logLevel: LogLevel): TPromise; +} + +export class LogLevelChannel implements ILogLevelManagementChannel { + + onDidChangeLogLevel: Event; + + constructor(private service: ILogService) { + this.onDidChangeLogLevel = buffer(service.onDidChangeLogLevel, true); + } + + call(command: string, arg?: any): TPromise { + switch (command) { + case 'event:onDidChangeLogLevel': return eventToCall(this.onDidChangeLogLevel); + case 'setLogLevel': this.service.setLevel(arg); return TPromise.as(null); + } + return undefined; + } +} + +export class LogLevelChannelClient { + + constructor(private channel: ILogLevelManagementChannel) { } + + private _onDidChangeLogLevel = eventFromCall(this.channel, 'event:onDidChangeLogLevel'); + get onDidChangeLogLevel(): Event { return this._onDidChangeLogLevel; } + + setLogLevel(level: LogLevel): TPromise { + return this.channel.call('setLogLevel', level); + } +} \ No newline at end of file diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index 6493d6e1c8d..9df00ce99f6 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -6,7 +6,7 @@ 'use strict'; import * as path from 'path'; -import { ILogService, LogLevel, NullLogService } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel, NullLogService, AbstractLogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { RotatingLogger, setAsyncMode } from 'spdlog'; @@ -25,50 +25,44 @@ export function createSpdLogService(processName: string, environmentService: IEn return new NullLogService(); } -class SpdLogService implements ILogService { +class SpdLogService extends AbstractLogService implements ILogService { _serviceBrand: any; constructor( private readonly logger: RotatingLogger, - private level: LogLevel = LogLevel.Error + level: LogLevel ) { - } - - setLevel(logLevel: LogLevel): void { - this.level = logLevel; - } - - getLevel(): LogLevel { - return this.level; + super(); + this.setLevel(level); } trace(): void { - if (this.level <= LogLevel.Trace) { + if (this.getLevel() <= LogLevel.Trace) { this.logger.trace(this.format(arguments)); } } debug(): void { - if (this.level <= LogLevel.Debug) { + if (this.getLevel() <= LogLevel.Debug) { this.logger.debug(this.format(arguments)); } } info(): void { - if (this.level <= LogLevel.Info) { + if (this.getLevel() <= LogLevel.Info) { this.logger.info(this.format(arguments)); } } warn(): void { - if (this.level <= LogLevel.Warning) { + if (this.getLevel() <= LogLevel.Warning) { this.logger.warn(this.format(arguments)); } } error(): void { - if (this.level <= LogLevel.Error) { + if (this.getLevel() <= LogLevel.Error) { const arg = arguments[0]; if (arg instanceof Error) { @@ -82,7 +76,7 @@ class SpdLogService implements ILogService { } critical(): void { - if (this.level <= LogLevel.Critical) { + if (this.getLevel() <= LogLevel.Critical) { this.logger.critical(this.format(arguments)); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadLogService.ts b/src/vs/workbench/api/electron-browser/mainThreadLogService.ts new file mode 100644 index 00000000000..c24b24c619a --- /dev/null +++ b/src/vs/workbench/api/electron-browser/mainThreadLogService.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. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { ExtHostContext, IExtHostContext } from '../node/extHost.protocol'; +import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Disposable } from 'vs/base/common/lifecycle'; + +@extHostCustomer +export class MainThreadLogLevelManagementChannel extends Disposable { + + constructor( + extHostContext: IExtHostContext, + @ILogService logService: ILogService, + ) { + super(); + this._register(logService.onDidChangeLogLevel(level => extHostContext.getProxy(ExtHostContext.ExtHostLogService).$setLogLevel(level))); + } + +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index d4dcd03d363..955e29ee3fc 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -54,6 +54,7 @@ import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { CommentRule, CharacterPair, EnterAction } from 'vs/editor/common/modes/languageConfiguration'; import { EndOfLineSequence, ISingleEditOperation } from 'vs/editor/common/model'; import { ILineMatch, IPatternInfo } from 'vs/platform/search/common/search'; +import { LogLevel } from 'vs/platform/log/common/log'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -744,6 +745,10 @@ export interface ExtHostWindowShape { $onDidChangeWindowFocus(value: boolean): void; } +export interface ExtHostLogServiceShape { + $setLogLevel(level: LogLevel); +} + // --- proxy identifiers export const MainContext = { @@ -794,7 +799,7 @@ export const ExtHostContext = { ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), ExtHostQuickOpen: createExtId('ExtHostQuickOpen'), ExtHostExtensionService: createExtId('ExtHostExtensionService'), - // ExtHostLogService: createExtId('ExtHostLogService'), + ExtHostLogService: createExtId('ExtHostLogService'), ExtHostTerminalService: createExtId('ExtHostTerminalService'), ExtHostSCM: createExtId('ExtHostSCM'), ExtHostTask: createExtId('ExtHostTask', ProxyType.CustomMarshaller), diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index d39a58a432e..fb50ec716d8 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -150,7 +150,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment); this._proxy = extHostContext.getProxy(MainContext.MainThreadExtensionService); this._activator = null; - this._extHostLogService = new ExtHostLogService(environmentService); + this._extHostLogService = new ExtHostLogService(environmentService, this._logService); // initialize API first (i.e. do not release barrier until the API is initialized) const apiFactory = createApiFactory(initData, extHostContext, extHostWorkspace, extHostConfiguration, this, logService); diff --git a/src/vs/workbench/api/node/extHostLogService.ts b/src/vs/workbench/api/node/extHostLogService.ts index fff0755018d..4689e038ae8 100644 --- a/src/vs/workbench/api/node/extHostLogService.ts +++ b/src/vs/workbench/api/node/extHostLogService.ts @@ -8,47 +8,59 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { TPromise } from 'vs/base/common/winjs.base'; import { mkdirp, dirExists } from 'vs/base/node/pfs'; -import Event, { Emitter } from 'vs/base/common/event'; +import Event from 'vs/base/common/event'; import { LogLevel } from 'vs/workbench/api/node/extHostTypes'; import { ILogService } from 'vs/platform/log/common/log'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { memoize } from 'vs/base/common/decorators'; +import { ExtHostLogServiceShape } from 'vs/workbench/api/node/extHost.protocol'; +import { Disposable } from 'vs/base/common/lifecycle'; -export class ExtHostLogService { +export class ExtHostLogService extends Disposable implements ExtHostLogServiceShape { private _loggers: Map = new Map(); - constructor(private _environmentService: IEnvironmentService) { + constructor( + private _environmentService: IEnvironmentService, + private _logService: ILogService + ) { + super(); + } + + $setLogLevel(level: LogLevel) { + this._logService.setLevel(level); } getExtLogger(extensionID: string): ExtHostLogger { - if (!this._loggers.has(extensionID)) { - const logService = createSpdLogService(extensionID, this._environmentService, extensionID); - const logsDirPath = path.join(this._environmentService.logsPath, extensionID); - this._loggers.set(extensionID, new ExtHostLogger(logService, logsDirPath)); + let logger = this._loggers.get(extensionID); + if (!logger) { + logger = this.createLogger(extensionID); + this._loggers.set(extensionID, logger); } + return logger; + } - return this._loggers.get(extensionID); + private createLogger(extensionID: string): ExtHostLogger { + const logService = createSpdLogService(extensionID, this._environmentService, extensionID); + const logsDirPath = path.join(this._environmentService.logsPath, extensionID); + this._register(this._logService.onDidChangeLogLevel(level => logService.setLevel(level))); + return new ExtHostLogger(logService, logsDirPath); } } export class ExtHostLogger implements vscode.Logger { - private _currentLevel: LogLevel; - private _onDidChangeLogLevel: Emitter; constructor( private readonly _logService: ILogService, private readonly _logDirectory: string ) { - this._currentLevel = this._logService.getLevel(); - this._onDidChangeLogLevel = new Emitter(); - this.onDidChangeLogLevel = this._onDidChangeLogLevel.event; } - // TODO - readonly onDidChangeLogLevel: Event; + get onDidChangeLogLevel(): Event { + return this._logService.onDidChangeLogLevel; + } - get currentLevel(): LogLevel { return this._currentLevel; } + get currentLevel(): LogLevel { return this._logService.getLevel(); } @memoize get logDirectory(): TPromise { diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 9f6e912a251..f40edbae3fc 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -43,9 +43,10 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; import fs = require('fs'); -import { ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log'; +import { ConsoleLogService, MultiplexLogService, ILogService, FollowerLogService } from 'vs/platform/log/common/log'; import { IssueChannelClient } from 'vs/platform/issue/common/issueIpc'; import { IIssueService } from 'vs/platform/issue/common/issue'; +import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; gracefulFs.gracefulify(fs); // enable gracefulFs export function startup(configuration: IWindowConfiguration): TPromise { @@ -75,10 +76,7 @@ function openWorkbench(configuration: IWindowConfiguration): TPromise { const mainServices = createMainProcessServices(mainProcessClient, configuration); const environmentService = new EnvironmentService(configuration, configuration.execPath); - const spdlogService = createSpdLogService(`renderer${configuration.windowId}`, environmentService); - const consoleLogService = new ConsoleLogService(environmentService); - const logService = new MultiplexLogService([consoleLogService, spdlogService]); - + const logService = createLogService(mainProcessClient, configuration, environmentService); logService.trace('openWorkbench configuration', JSON.stringify(configuration)); // Since the configuration service is one of the core services that is used in so many places, we initialize it @@ -200,6 +198,14 @@ function createStorageService(workspaceService: IWorkspaceContextService, enviro return new StorageService(storage, storage, workspaceId, secondaryWorkspaceId); } +function createLogService(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration, environmentService: IEnvironmentService): ILogService { + const spdlogService = createSpdLogService(`renderer${configuration.windowId}`, environmentService); + const consoleLogService = new ConsoleLogService(environmentService); + const logService = new MultiplexLogService([consoleLogService, spdlogService]); + const logLevelClient = new LogLevelChannelClient(mainProcessClient.getChannel('loglevel')); + return new FollowerLogService(logLevelClient, logService); +} + function createMainProcessServices(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration): ServiceCollection { const serviceCollection = new ServiceCollection();