Skip to content

Commit 9f79255

Browse files
committed
Support window logging in web:
- Implement File service based log service - Implement In memory log provider - Use in memory log provider for window logging
1 parent 38aa476 commit 9f79255

11 files changed

Lines changed: 306 additions & 91 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log';
7+
import { URI } from 'vs/base/common/uri';
8+
import { IFileService } from 'vs/platform/files/common/files';
9+
import { Queue } from 'vs/base/common/async';
10+
import { VSBuffer } from 'vs/base/common/buffer';
11+
12+
export class FileLogService extends AbstractLogService implements ILogService {
13+
14+
_serviceBrand: any;
15+
16+
private readonly queue: Queue<void>;
17+
18+
constructor(
19+
private readonly name: string,
20+
private readonly resource: URI,
21+
level: LogLevel,
22+
@IFileService private readonly fileService: IFileService
23+
) {
24+
super();
25+
this.setLevel(level);
26+
this.queue = this._register(new Queue<void>());
27+
}
28+
29+
trace(): void {
30+
if (this.getLevel() <= LogLevel.Trace) {
31+
this._log(LogLevel.Trace, this.format(arguments));
32+
}
33+
}
34+
35+
debug(): void {
36+
if (this.getLevel() <= LogLevel.Debug) {
37+
this._log(LogLevel.Debug, this.format(arguments));
38+
}
39+
}
40+
41+
info(): void {
42+
if (this.getLevel() <= LogLevel.Info) {
43+
this._log(LogLevel.Info, this.format(arguments));
44+
}
45+
}
46+
47+
warn(): void {
48+
if (this.getLevel() <= LogLevel.Warning) {
49+
this._log(LogLevel.Warning, this.format(arguments));
50+
}
51+
}
52+
53+
error(): void {
54+
if (this.getLevel() <= LogLevel.Error) {
55+
const arg = arguments[0];
56+
57+
if (arg instanceof Error) {
58+
const array = Array.prototype.slice.call(arguments) as any[];
59+
array[0] = arg.stack;
60+
this._log(LogLevel.Error, this.format(array));
61+
} else {
62+
this._log(LogLevel.Error, this.format(arguments));
63+
}
64+
}
65+
}
66+
67+
critical(): void {
68+
if (this.getLevel() <= LogLevel.Critical) {
69+
this._log(LogLevel.Critical, this.format(arguments));
70+
}
71+
}
72+
73+
flush(): Promise<void> {
74+
return this.queue.queue(() => Promise.resolve());
75+
}
76+
77+
private _log(level: LogLevel, message: string): void {
78+
this.queue.queue(async () => {
79+
let content = await this.loadContent();
80+
content += `[${this.getCurrentTimestamp()}] [${this.name}] [${this.stringifyLogLevel(level)}] ${message}\n`;
81+
await this.fileService.writeFile(this.resource, VSBuffer.fromString(content));
82+
});
83+
}
84+
85+
private getCurrentTimestamp(): string {
86+
const toTwoDigits = (v: number) => v < 10 ? `0${v}` : v;
87+
const toThreeDigits = (v: number) => v < 10 ? `00${v}` : v < 100 ? `0${v}` : v;
88+
const currentTime = new Date();
89+
return `${currentTime.getFullYear()}-${toTwoDigits(currentTime.getMonth() + 1)}-${toTwoDigits(currentTime.getDate())} ${toTwoDigits(currentTime.getHours())}:${toTwoDigits(currentTime.getMinutes())}:${toTwoDigits(currentTime.getSeconds())}.${toThreeDigits(currentTime.getMilliseconds())}`;
90+
}
91+
92+
private async loadContent(): Promise<string> {
93+
try {
94+
const content = await this.fileService.readFile(this.resource);
95+
return content.value.toString();
96+
} catch (e) {
97+
return '';
98+
}
99+
}
100+
101+
private stringifyLogLevel(level: LogLevel): string {
102+
switch (level) {
103+
case LogLevel.Critical: return 'critical';
104+
case LogLevel.Debug: return 'debug';
105+
case LogLevel.Error: return 'error';
106+
case LogLevel.Info: return 'info';
107+
case LogLevel.Trace: return 'trace';
108+
case LogLevel.Warning: return 'warning';
109+
}
110+
return '';
111+
}
112+
113+
private format(args: any): string {
114+
let result = '';
115+
116+
for (let i = 0; i < args.length; i++) {
117+
let a = args[i];
118+
119+
if (typeof a === 'object') {
120+
try {
121+
a = JSON.stringify(a);
122+
} catch (e) { }
123+
}
124+
125+
result += (i > 0 ? ' ' : '') + a;
126+
}
127+
128+
return result;
129+
}
130+
}

src/vs/workbench/browser/web.main.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { domContentLoaded, addDisposableListener, EventType, addClass } from 'vs
88
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
99
import { ILogService } from 'vs/platform/log/common/log';
1010
import { Disposable } from 'vs/base/common/lifecycle';
11-
import { SimpleLogService } from 'vs/workbench/browser/web.simpleservices';
1211
import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
1312
import { Workbench } from 'vs/workbench/browser/workbench';
1413
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
@@ -42,6 +41,9 @@ import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/th
4241
import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider';
4342
import { registerWindowDriver } from 'vs/platform/driver/browser/driver';
4443
import { StaticExtensionsService, IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions';
44+
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
45+
import { INMEMORY_LOG_SCHEME, InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider';
46+
import { FileLogService } from 'vs/platform/log/common/fileLogService';
4547

4648
class CodeRendererMain extends Disposable {
4749

@@ -117,13 +119,14 @@ class CodeRendererMain extends Disposable {
117119
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
118120

119121
// Log
120-
const logService = new SimpleLogService();
122+
const logFile = URI.file(`window.log`).with({ scheme: INMEMORY_LOG_SCHEME });
123+
const logService = new BufferLogService();
121124
serviceCollection.set(ILogService, logService);
122125

123126
const payload = await this.resolveWorkspaceInitializationPayload();
124127

125128
// Environment
126-
const environmentService = new BrowserWorkbenchEnvironmentService(payload.id, this.configuration);
129+
const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: payload.id, logFile, ...this.configuration });
127130
serviceCollection.set(IWorkbenchEnvironmentService, environmentService);
128131

129132
// Product
@@ -146,6 +149,10 @@ class CodeRendererMain extends Disposable {
146149
const fileService = this._register(new FileService(logService));
147150
serviceCollection.set(IFileService, fileService);
148151

152+
// InMemory Log
153+
fileService.registerProvider(INMEMORY_LOG_SCHEME, new InMemoryLogProvider());
154+
logService.logger = new FileLogService('window', logFile, logService.getLevel(), fileService);
155+
149156
// Static Extensions
150157
const staticExtensions = new StaticExtensionsService(this.configuration.staticExtensions || []);
151158
serviceCollection.set(IStaticExtensionsService, staticExtensions);

src/vs/workbench/browser/web.simpleservices.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
1111
import { IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
1212
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
1313
import { IURLHandler, IURLService } from 'vs/platform/url/common/url';
14-
import { ConsoleLogService, ILogService } from 'vs/platform/log/common/log';
14+
import { ILogService } from 'vs/platform/log/common/log';
1515
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
1616
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
1717
import { IUpdateService, State } from 'vs/platform/update/common/update';
@@ -102,12 +102,6 @@ registerSingleton(IExtensionUrlHandler, SimpleExtensionURLHandler, true);
102102

103103
//#endregion
104104

105-
//#region Log
106-
107-
export class SimpleLogService extends ConsoleLogService { }
108-
109-
//#endregion
110-
111105
//#region Update
112106

113107
export class SimpleUpdateService implements IUpdateService {

src/vs/workbench/contrib/logs/common/logs.contribution.ts

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,82 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as nls from 'vs/nls';
7+
import { join } from 'vs/base/common/path';
78
import { Registry } from 'vs/platform/registry/common/platform';
89
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions';
910
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
10-
import { SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions';
11+
import { SetLogLevelAction, OpenLogsFolderAction } from 'vs/workbench/contrib/logs/common/logsActions';
12+
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
13+
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
14+
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
15+
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
16+
import { URI } from 'vs/base/common/uri';
17+
import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/contrib/output/common/output';
18+
import { Disposable } from 'vs/base/common/lifecycle';
19+
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
20+
import { dirname } from 'vs/base/common/resources';
21+
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
22+
import { isWeb } from 'vs/base/common/platform';
1123

1224
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
1325
const devCategory = nls.localize('developer', "Developer");
14-
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory);
26+
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level...', devCategory);
27+
28+
class LogOutputChannels extends Disposable implements IWorkbenchContribution {
29+
30+
constructor(
31+
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
32+
@ILogService private readonly logService: ILogService,
33+
@IFileService private readonly fileService: IFileService
34+
) {
35+
super();
36+
if (isWeb) {
37+
this.registerWebContributions();
38+
} else {
39+
this.registerNativeContributions();
40+
}
41+
}
42+
43+
private registerWebContributions(): void {
44+
Registry.as<IOutputChannelRegistry>(OutputExt.OutputChannels).registerChannel({ id: Constants.rendererLogChannelId, label: nls.localize('rendererLog', "Window"), file: this.environmentService.logFile, log: true });
45+
}
46+
47+
private registerNativeContributions(): void {
48+
this.registerLogChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Main"), URI.file(join(this.environmentService.logsPath, `main.log`)));
49+
this.registerLogChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Shared"), URI.file(join(this.environmentService.logsPath, `sharedprocess.log`)));
50+
this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile);
51+
52+
const registerTelemetryChannel = (level: LogLevel) => {
53+
if (level === LogLevel.Trace && !Registry.as<IOutputChannelRegistry>(OutputExt.OutputChannels).getChannel(Constants.telemetryLogChannelId)) {
54+
this.registerLogChannel(Constants.telemetryLogChannelId, nls.localize('telemetryLog', "Telemetry"), URI.file(join(this.environmentService.logsPath, `telemetry.log`)));
55+
}
56+
};
57+
registerTelemetryChannel(this.logService.getLevel());
58+
this.logService.onDidChangeLogLevel(registerTelemetryChannel);
59+
60+
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
61+
const devCategory = nls.localize('developer', "Developer");
62+
workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory);
63+
}
64+
65+
private async registerLogChannel(id: string, label: string, file: URI): Promise<void> {
66+
const outputChannelRegistry = Registry.as<IOutputChannelRegistry>(OutputExt.OutputChannels);
67+
const exists = await this.fileService.exists(file);
68+
if (exists) {
69+
outputChannelRegistry.registerChannel({ id, label, file, log: true });
70+
return;
71+
}
72+
73+
const watcher = this.fileService.watch(dirname(file));
74+
const disposable = this.fileService.onFileChanges(e => {
75+
if (e.contains(file, FileChangeType.ADDED)) {
76+
watcher.dispose();
77+
disposable.dispose();
78+
outputChannelRegistry.registerChannel({ id, label, file, log: true });
79+
}
80+
});
81+
}
82+
83+
}
84+
85+
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Restored);

src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts

Lines changed: 0 additions & 68 deletions
This file was deleted.

src/vs/workbench/services/environment/browser/environmentService.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,9 @@ export class BrowserWindowConfiguration implements IWindowConfiguration {
6262
termProgram?: string;
6363
}
6464

65-
export interface IBrowserWindowConfiguration {
65+
interface IBrowserWorkbenchEnvironemntConstructionOptions extends IWorkbenchConstructionOptions {
6666
workspaceId: string;
67-
remoteAuthority?: string;
68-
webviewEndpoint?: string;
69-
connectionToken?: string;
67+
logFile: URI;
7068
}
7169

7270
export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironmentService {
@@ -75,8 +73,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
7573

7674
readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration();
7775

78-
constructor(workspaceId: string, public readonly options: IWorkbenchConstructionOptions) {
76+
constructor(readonly options: IBrowserWorkbenchEnvironemntConstructionOptions) {
7977
this.args = { _: [] };
78+
this.logFile = options.logFile;
8079
this.appRoot = '/web/';
8180
this.appNameLong = 'Visual Studio Code - Web';
8281

@@ -88,7 +87,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
8887
this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json');
8988
this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json');
9089
this.backupHome = joinPath(this.userRoamingDataHome, BACKUPS);
91-
this.configuration.backupWorkspaceResource = joinPath(this.backupHome, workspaceId);
90+
this.configuration.backupWorkspaceResource = joinPath(this.backupHome, options.workspaceId);
9291
this.configuration.connectionToken = options.connectionToken || this.getConnectionTokenFromLocation();
9392

9493
this.logsPath = '/web/logs';
@@ -183,6 +182,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
183182
driverVerbose: boolean;
184183
webviewEndpoint?: string;
185184
galleryMachineIdResource?: URI;
185+
readonly logFile: URI;
186186

187187
get webviewResourceRoot(): string {
188188
return this.webviewEndpoint ? this.webviewEndpoint + '/vscode-resource{{resource}}' : 'vscode-resource:{{resource}}';

0 commit comments

Comments
 (0)