diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index 726bd185dcb..d39af82182d 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -10,10 +10,11 @@ import { ILogService, LogLevel, NullLogService } from 'vs/platform/log/common/lo import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { RotatingLogger, setAsyncMode } from 'spdlog'; -export function createLogService(processName: string, environmentService: IEnvironmentService): ILogService { +export function createLogService(processName: string, environmentService: IEnvironmentService, logsSubfolder?: string): ILogService { try { setAsyncMode(8192, 2000); - const logfilePath = path.join(environmentService.logsPath, `${processName}.log`); + const logsDirPath = logsSubfolder ? path.join(environmentService.logsPath, logsSubfolder) : environmentService.logsPath; + const logfilePath = path.join(logsDirPath, `${processName}.log`); const logger = new RotatingLogger(processName, logfilePath, 1024 * 1024 * 5, 6); logger.setLevel(0); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 3be7325dbd3..7fe3e931dd6 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -335,4 +335,40 @@ declare module 'vscode' { private constructor(enabled: boolean, condition: string, hitCondition: string, functionName: string); } + + /** + * The severity level of a log message + */ + export enum LogLevel { + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Critical = 6, + Off = 7 + } + + /** + * A logger for writing to an extension's log file, and accessing its dedicated log directory. + */ + export interface Logger { + readonly onDidChangeLogLevel: Event; + readonly currentLevel: LogLevel; + readonly logDirectory: Thenable; + + trace(message: string, ...args: any[]): void; + debug(message: string, ...args: any[]): void; + info(message: string, ...args: any[]): void; + warn(message: string, ...args: any[]): void; + error(message: string | Error, ...args: any[]): void; + critical(message: string | Error, ...args: any[]): void; + } + + export interface ExtensionContext { + /** + * This extension's logger + */ + logger: Logger; + } } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index e29581a8859..d64abff5ed6 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -569,6 +569,7 @@ export function createApiFactory( Hover: extHostTypes.Hover, IndentAction: languageConfiguration.IndentAction, Location: extHostTypes.Location, + LogLevel: extHostTypes.LogLevel, MarkdownString: extHostTypes.MarkdownString, OverviewRulerLane: EditorCommon.OverviewRulerLane, ParameterInformation: extHostTypes.ParameterInformation, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 7ef268ba9ac..6fd6f4c9b30 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -779,6 +779,7 @@ export const ExtHostContext = { ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), ExtHostQuickOpen: createExtId('ExtHostQuickOpen'), ExtHostExtensionService: createExtId('ExtHostExtensionService'), + // ExtHostLogService: createExtId('ExtHostLogService'), ExtHostTerminalService: createExtId('ExtHostTerminalService'), ExtHostSCM: createExtId('ExtHostSCM', ProxyType.CustomMarshaller), ExtHostTask: createExtId('ExtHostTask', ProxyType.CustomMarshaller), diff --git a/src/vs/workbench/api/node/extHostExtensionActivator.ts b/src/vs/workbench/api/node/extHostExtensionActivator.ts index 5038d467d3b..b0949051e4f 100644 --- a/src/vs/workbench/api/node/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/node/extHostExtensionActivator.ts @@ -10,6 +10,7 @@ import Severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtHostLogger } from 'vs/workbench/api/node/extHostLogService'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = TPromise.wrap(void 0); @@ -26,6 +27,7 @@ export interface IExtensionContext { extensionPath: string; storagePath: string; asAbsolutePath(relativePath: string): string; + logger: ExtHostLogger; } /** diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 11b0389a4cf..1f0e130df8c 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -20,6 +20,8 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { TernarySearchTree } from 'vs/base/common/map'; import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; class ExtensionMemento implements IExtensionMemento { @@ -117,6 +119,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private readonly _storagePath: ExtensionStoragePath; private readonly _proxy: MainThreadExtensionServiceShape; private readonly _logService: ILogService; + private readonly _extHostLogService: ExtHostLogService; private _activator: ExtensionsActivator; private _extensionPathIndex: TPromise>; /** @@ -126,7 +129,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { threadService: IExtHostContext, extHostWorkspace: ExtHostWorkspace, extHostConfiguration: ExtHostConfiguration, - logService: ILogService + logService: ILogService, + environmentService: IEnvironmentService ) { this._barrier = new Barrier(); this._registry = new ExtensionDescriptionRegistry(initData.extensions); @@ -137,6 +141,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment); this._proxy = this._threadService.getProxy(MainContext.MainThreadExtensionService); this._activator = null; + this._extHostLogService = new ExtHostLogService(environmentService); // initialize API first (i.e. do not release barrier until the API is initialized) const apiFactory = createApiFactory(initData, threadService, extHostWorkspace, extHostConfiguration, this, logService); @@ -331,13 +336,15 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { workspaceState.whenReady, this._storagePath.whenReady ]).then(() => { + const that = this; return Object.freeze({ globalState, workspaceState, subscriptions: [], get extensionPath() { return extensionDescription.extensionFolderPath; }, storagePath: this._storagePath.value(extensionDescription), - asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionFolderPath, relativePath); } + asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionFolderPath, relativePath); }, + get logger() { return that._extHostLogService.getExtLogger(extensionDescription.id); } }); }); } diff --git a/src/vs/workbench/api/node/extHostLogService.ts b/src/vs/workbench/api/node/extHostLogService.ts new file mode 100644 index 00000000000..150bde4af3d --- /dev/null +++ b/src/vs/workbench/api/node/extHostLogService.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * 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 { LogLevel } from 'vs/workbench/api/node/extHostTypes'; +import { ILogService } from 'vs/platform/log/common/log'; +import { createLogService } from 'vs/platform/log/node/spdlogService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { memoize } from 'vs/base/common/decorators'; + +export class ExtHostLogService { + private _loggers: Map = new Map(); + + constructor(private _environmentService: IEnvironmentService) { + } + + getExtLogger(extensionID: string): ExtHostLogger { + if (!this._loggers.has(extensionID)) { + const logService = createLogService(extensionID, this._environmentService, extensionID); + const logsDirPath = path.join(this._environmentService.logsPath, extensionID); + this._loggers.set(extensionID, new ExtHostLogger(logService, logsDirPath)); + } + + return this._loggers.get(extensionID); + } +} + +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 currentLevel(): LogLevel { return this._currentLevel; } + + @memoize + get logDirectory(): TPromise { + return dirExists(this._logDirectory).then(exists => { + if (exists) { + return TPromise.wrap(null); + } else { + return mkdirp(this._logDirectory); + } + }).then(() => { + return this._logDirectory; + }); + } + + trace(message: string, ...args: any[]): void { + return this._logService.trace(message, ...args); + } + + debug(message: string, ...args: any[]): void { + return this._logService.debug(message, ...args); + } + + info(message: string, ...args: any[]): void { + return this._logService.info(message, ...args); + } + + warn(message: string, ...args: any[]): void { + return this._logService.warn(message, ...args); + } + + error(message: string | Error, ...args: any[]): void { + return this._logService.error(message, ...args); + } + + critical(message: string | Error, ...args: any[]): void { + return this._logService.critical(message, ...args); + } +} diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index eb0028b0b74..e865dee2b1c 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1531,3 +1531,13 @@ export class FunctionBreakpoint extends Breakpoint { this.functionName = functionName; } } + +export enum LogLevel { + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Critical = 6, + Off = 7 +} diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index 8bcaf3ad23c..76041a685a1 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -99,7 +99,7 @@ export class ExtensionHostMain { this._logService.trace('initData', initData); this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace, initData.configuration); - this._extensionService = new ExtHostExtensionService(initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._logService); + this._extensionService = new ExtHostExtensionService(initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._logService, environmentService); // error forwarding and stack trace scanning const extensionErrors = new WeakMap();