Skip to content

Commit 1fc282f

Browse files
committed
microsoft#70998 Create output directoru before creating logger
1 parent 1e96a51 commit 1fc282f

4 files changed

Lines changed: 131 additions & 48 deletions

File tree

src/vs/workbench/api/node/extHostOutputService.ts

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender
1111
import { toLocalISOString } from 'vs/base/common/date';
1212
import { Event, Emitter } from 'vs/base/common/event';
1313
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
14+
import { dirExists, mkdirp } from 'vs/base/node/pfs';
1415

15-
export abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel {
16+
abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel {
1617

1718
readonly _id: Promise<string>;
1819
private readonly _name: string;
@@ -83,7 +84,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements
8384
}
8485
}
8586

86-
export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel {
87+
class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel {
8788

8889
constructor(name: string, proxy: MainThreadOutputServiceShape) {
8990
super(name, false, undefined, proxy);
@@ -96,17 +97,13 @@ export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel {
9697
}
9798
}
9899

99-
export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel {
100+
class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel {
100101

101-
private static _namePool = 1;
102102
private _appender: OutputAppender;
103103

104-
constructor(name: string, outputDir: string, proxy: MainThreadOutputServiceShape) {
105-
const fileName = `${ExtHostOutputChannelBackedByFile._namePool++}-${name}`;
106-
const file = URI.file(join(outputDir, `${fileName}.log`));
107-
108-
super(name, false, file, proxy);
109-
this._appender = new OutputAppender(fileName, file.fsPath);
104+
constructor(name: string, appender: OutputAppender, proxy: MainThreadOutputServiceShape) {
105+
super(name, false, URI.file(appender.file), proxy);
106+
this._appender = appender;
110107
}
111108

112109
append(value: string): void {
@@ -131,7 +128,7 @@ export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChann
131128
}
132129
}
133130

134-
export class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel {
131+
class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel {
135132

136133
constructor(name: string, file: URI, proxy: MainThreadOutputServiceShape) {
137134
super(name, true, file, proxy);
@@ -142,15 +139,31 @@ export class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel {
142139
}
143140
}
144141

142+
let namePool = 1;
143+
async function createExtHostOutputChannel(name: string, outputDirPromise: Promise<string>, proxy: MainThreadOutputServiceShape): Promise<AbstractExtHostOutputChannel> {
144+
try {
145+
const outputDir = await outputDirPromise;
146+
const fileName = `${namePool++}-${name}`;
147+
const file = URI.file(join(outputDir, `${fileName}.log`));
148+
const appender = new OutputAppender(fileName, file.fsPath);
149+
return new ExtHostOutputChannelBackedByFile(name, appender, proxy);
150+
} catch (error) {
151+
// Do not crash if logger cannot be created
152+
console.log(error);
153+
return new ExtHostPushOutputChannel(name, proxy);
154+
}
155+
}
156+
145157
export class ExtHostOutputService implements ExtHostOutputServiceShape {
146158

159+
private readonly _outputDir: Promise<string>;
147160
private _proxy: MainThreadOutputServiceShape;
148-
private _outputDir: string;
149161
private _channels: Map<string, AbstractExtHostOutputChannel> = new Map<string, AbstractExtHostOutputChannel>();
150162
private _visibleChannelDisposable: IDisposable;
151163

152164
constructor(logsLocation: URI, mainContext: IMainContext) {
153-
this._outputDir = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`);
165+
const outputDirPath = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`);
166+
this._outputDir = dirExists(outputDirPath).then(exists => exists ? exists : mkdirp(outputDirPath)).then(() => outputDirPath);
154167
this._proxy = mainContext.getProxy(MainContext.MainThreadOutputService);
155168
}
156169

@@ -167,23 +180,32 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape {
167180
}
168181

169182
createOutputChannel(name: string): vscode.OutputChannel {
170-
const channel = this._createOutputChannel(name);
171-
channel._id.then(id => this._channels.set(id, channel));
172-
return channel;
173-
}
174-
175-
private _createOutputChannel(name: string): AbstractExtHostOutputChannel {
176183
name = name.trim();
177184
if (!name) {
178185
throw new Error('illegal argument `name`. must not be falsy');
179186
} else {
180-
// Do not crash if logger cannot be created
181-
try {
182-
return new ExtHostOutputChannelBackedByFile(name, this._outputDir, this._proxy);
183-
} catch (error) {
184-
console.log(error);
185-
return new ExtHostPushOutputChannel(name, this._proxy);
186-
}
187+
const extHostOutputChannel = createExtHostOutputChannel(name, this._outputDir, this._proxy);
188+
extHostOutputChannel.then(channel => channel._id.then(id => this._channels.set(id, channel)));
189+
return <vscode.OutputChannel>{
190+
append(value: string): void {
191+
extHostOutputChannel.then(channel => channel.append(value));
192+
},
193+
appendLine(value: string): void {
194+
extHostOutputChannel.then(channel => channel.appendLine(value));
195+
},
196+
clear(): void {
197+
extHostOutputChannel.then(channel => channel.clear());
198+
},
199+
show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void {
200+
extHostOutputChannel.then(channel => channel.show(columnOrPreserveFocus, preserveFocus));
201+
},
202+
hide(): void {
203+
extHostOutputChannel.then(channel => channel.hide());
204+
},
205+
dispose(): void {
206+
extHostOutputChannel.then(channel => channel.dispose());
207+
}
208+
};
187209
}
188210
}
189211

src/vs/workbench/services/output/common/outputChannelModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ class FileOutputChannelModel extends AbstractFileOutputChannelModel implements I
279279
}
280280
}
281281

282-
class BufferredOutputChannel extends Disposable implements IOutputChannelModel {
282+
export class BufferredOutputChannel extends Disposable implements IOutputChannelModel {
283283

284284
readonly file: URI | null = null;
285285
scrollLock: boolean = false;

src/vs/workbench/services/output/node/outputAppender.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class OutputAppender {
99

1010
private appender: RotatingLogger;
1111

12-
constructor(name: string, file: string) {
12+
constructor(name: string, readonly file: string) {
1313
this.appender = createRotatingLogger(name, file, 1024 * 1024 * 30, 1);
1414
this.appender.clearFormatters();
1515
}

src/vs/workbench/services/output/node/outputChannelModelService.ts

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,23 @@
66
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
77
import * as extfs from 'vs/base/node/extfs';
88
import { dirname, join } from 'vs/base/common/path';
9+
import * as resources from 'vs/base/common/resources';
910
import { ITextModel } from 'vs/editor/common/model';
1011
import { URI } from 'vs/base/common/uri';
1112
import { ThrottledDelayer } from 'vs/base/common/async';
1213
import { IFileService } from 'vs/platform/files/common/files';
1314
import { IModelService } from 'vs/editor/common/services/modelService';
1415
import { IModeService } from 'vs/editor/common/services/modeService';
15-
import { toDisposable, IDisposable } from 'vs/base/common/lifecycle';
16+
import { toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle';
1617
import { ILogService } from 'vs/platform/log/common/log';
17-
import { IOutputChannelModel, AbstractFileOutputChannelModel, IOutputChannelModelService, AsbtractOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel';
18+
import { IOutputChannelModel, AbstractFileOutputChannelModel, IOutputChannelModelService, AsbtractOutputChannelModelService, BufferredOutputChannel } from 'vs/workbench/services/output/common/outputChannelModel';
1819
import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender';
1920
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
2021
import { IWindowService } from 'vs/platform/windows/common/windows';
2122
import { toLocalISOString } from 'vs/base/common/date';
2223
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
2324
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
25+
import { Emitter, Event } from 'vs/base/common/event';
2426

2527
let watchingOutputDir = false;
2628
let callbacks: ((eventType: string, fileName?: string) => void)[] = [];
@@ -55,15 +57,13 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement
5557
id: string,
5658
modelUri: URI,
5759
mimeType: string,
58-
@IWindowService windowService: IWindowService,
59-
@IEnvironmentService environmentService: IEnvironmentService,
60+
file: URI,
6061
@IFileService fileService: IFileService,
6162
@IModelService modelService: IModelService,
6263
@IModeService modeService: IModeService,
6364
@ILogService logService: ILogService
6465
) {
65-
const outputDir = join(environmentService.logsPath, `output_${windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`);
66-
super(modelUri, mimeType, URI.file(join(outputDir, `${id}.log`)), fileService, modelService, modeService);
66+
super(modelUri, mimeType, file, fileService, modelService, modeService);
6767
this.appendedMessage = '';
6868
this.loadingFromFileInProgress = false;
6969

@@ -159,32 +159,93 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement
159159
}
160160
}
161161

162+
class DelegatedOutputChannelModel extends Disposable implements IOutputChannelModel {
163+
164+
private readonly _onDidAppendedContent: Emitter<void> = this._register(new Emitter<void>());
165+
readonly onDidAppendedContent: Event<void> = this._onDidAppendedContent.event;
166+
167+
private readonly _onDispose: Emitter<void> = this._register(new Emitter<void>());
168+
readonly onDispose: Event<void> = this._onDispose.event;
169+
170+
private readonly outputChannelModel: Promise<IOutputChannelModel>;
171+
172+
constructor(
173+
id: string,
174+
modelUri: URI,
175+
mimeType: string,
176+
outputDir: Promise<URI>,
177+
@IInstantiationService private readonly instantiationService: IInstantiationService,
178+
@ILogService private readonly logService: ILogService,
179+
@ITelemetryService private readonly telemetryService: ITelemetryService,
180+
) {
181+
super();
182+
this.outputChannelModel = this.createOutputChannelModel(id, modelUri, mimeType, outputDir);
183+
}
184+
185+
private async createOutputChannelModel(id: string, modelUri: URI, mimeType: string, outputDirPromise: Promise<URI>): Promise<IOutputChannelModel> {
186+
let outputChannelModel: IOutputChannelModel;
187+
try {
188+
const outputDir = await outputDirPromise;
189+
const file = resources.joinPath(outputDir, `${id}.log`);
190+
outputChannelModel = this.instantiationService.createInstance(OutputChannelBackedByFile, id, modelUri, mimeType, file);
191+
} catch (e) {
192+
// Do not crash if spdlog rotating logger cannot be loaded (workaround for https://github.com/Microsoft/vscode/issues/47883)
193+
this.logService.error(e);
194+
/* __GDPR__
195+
"output.channel.creation.error" : {}
196+
*/
197+
this.telemetryService.publicLog('output.channel.creation.error');
198+
outputChannelModel = this.instantiationService.createInstance(BufferredOutputChannel, modelUri, mimeType);
199+
}
200+
this._register(outputChannelModel);
201+
outputChannelModel.onDidAppendedContent(() => this._onDidAppendedContent.fire());
202+
outputChannelModel.onDispose(() => this._onDispose.fire());
203+
return outputChannelModel;
204+
}
205+
206+
append(output: string): void {
207+
this.outputChannelModel.then(outputChannelModel => outputChannelModel.append(output));
208+
}
209+
210+
update(): void {
211+
this.outputChannelModel.then(outputChannelModel => outputChannelModel.update());
212+
}
213+
214+
loadModel(): Promise<ITextModel> {
215+
return this.outputChannelModel.then(outputChannelModel => outputChannelModel.loadModel());
216+
}
217+
218+
clear(till?: number): void {
219+
this.outputChannelModel.then(outputChannelModel => outputChannelModel.clear(till));
220+
}
221+
222+
}
223+
162224
export class OutputChannelModelService extends AsbtractOutputChannelModelService implements IOutputChannelModelService {
163225

164226
_serviceBrand: any;
165227

166228
constructor(
167229
@IInstantiationService instantiationService: IInstantiationService,
168-
@ILogService private readonly logService: ILogService,
169-
@ITelemetryService private readonly telemetryService: ITelemetryService
230+
@IEnvironmentService private readonly environmentService: IEnvironmentService,
231+
@IWindowService private readonly windowService: IWindowService,
232+
@IFileService private readonly fileService: IFileService
170233
) {
171234
super(instantiationService);
172235
}
173236

174237
createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel {
175-
if (!file) {
176-
try {
177-
return this.instantiationService.createInstance(OutputChannelBackedByFile, id, modelUri, mimeType);
178-
} catch (e) {
179-
// Do not crash if spdlog rotating logger cannot be loaded (workaround for https://github.com/Microsoft/vscode/issues/47883)
180-
this.logService.error(e);
181-
/* __GDPR__
182-
"output.channel.creation.error" : {}
183-
*/
184-
this.telemetryService.publicLog('output.channel.creation.error');
185-
}
238+
return file ? super.createOutputChannelModel(id, modelUri, mimeType, file) :
239+
this.instantiationService.createInstance(DelegatedOutputChannelModel, id, modelUri, mimeType, this.outputDir);
240+
}
241+
242+
private _outputDir: Promise<URI> | null;
243+
private get outputDir(): Promise<URI> {
244+
if (!this._outputDir) {
245+
const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`));
246+
this._outputDir = this.fileService.createFolder(outputDir).then(() => outputDir);
186247
}
187-
return super.createOutputChannelModel(id, modelUri, mimeType, file);
248+
return this._outputDir;
188249
}
189250

190251
}

0 commit comments

Comments
 (0)