Skip to content

Commit fc8b13b

Browse files
author
Dave Bartolomeo
committed
Use TeeLogger instead of additionalLogLocation: option
1 parent 0d8df9a commit fc8b13b

14 files changed

Lines changed: 103 additions & 170 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./logger";
2+
export * from "./tee-logger";
23
export * from "./vscode/loggers";
34
export * from "./vscode/output-channel-logger";

extensions/ql-vscode/src/common/logging/logger.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
export interface LogOptions {
22
// If false, don't output a trailing newline for the log entry. Default true.
33
trailingNewline?: boolean;
4-
5-
// If specified, add this log entry to the log file at the specified location.
6-
additionalLogLocation?: string;
74
}
85

96
export interface Logger {
@@ -25,11 +22,4 @@ export interface Logger {
2522
* @param preserveFocus When `true` the channel will not take focus.
2623
*/
2724
show(preserveFocus?: boolean): void;
28-
29-
/**
30-
* Remove the log at the specified location.
31-
*
32-
* @param location log to remove
33-
*/
34-
removeAdditionalLogLocation(location: string | undefined): void;
3525
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { appendFile, ensureFile } from "fs-extra";
2+
import { isAbsolute } from "path";
3+
import { Logger, LogOptions } from "./logger";
4+
5+
/**
6+
* An implementation of {@link Logger} that sends the output both to another {@link Logger}
7+
* and to a file.
8+
*
9+
* The first time a message is written, an additional banner is written to the underlying logger
10+
* pointing the user to the "side log" file.
11+
*/
12+
export class TeeLogger implements Logger {
13+
private emittedRedirectMessage = false;
14+
15+
public constructor(
16+
private readonly logger: Logger,
17+
private readonly location: string,
18+
) {
19+
if (!isAbsolute(location)) {
20+
throw new Error(
21+
`Additional Log Location must be an absolute path: ${location}`,
22+
);
23+
}
24+
}
25+
26+
async log(message: string, options = {} as LogOptions): Promise<void> {
27+
if (!this.emittedRedirectMessage) {
28+
this.emittedRedirectMessage = true;
29+
const msg = `| Log being saved to ${this.location} |`;
30+
const separator = new Array(msg.length).fill("-").join("");
31+
await this.logger.log(separator);
32+
await this.logger.log(msg);
33+
await this.logger.log(separator);
34+
}
35+
36+
const trailingNewline = options.trailingNewline ?? true;
37+
await ensureFile(this.location);
38+
39+
await appendFile(this.location, message + (trailingNewline ? "\n" : ""), {
40+
encoding: "utf8",
41+
});
42+
43+
await this.logger.log(message, options);
44+
}
45+
46+
show(preserveFocus?: boolean): void {
47+
this.logger.show(preserveFocus);
48+
}
49+
}
Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import { window as Window, OutputChannel, Progress } from "vscode";
2-
import { ensureFile, appendFile } from "fs-extra";
3-
import { isAbsolute } from "path";
42
import { Logger, LogOptions } from "../logger";
53
import { DisposableObject } from "../../../pure/disposable-object";
64

@@ -9,10 +7,6 @@ import { DisposableObject } from "../../../pure/disposable-object";
97
*/
108
export class OutputChannelLogger extends DisposableObject implements Logger {
119
public readonly outputChannel: OutputChannel;
12-
private readonly additionalLocations = new Map<
13-
string,
14-
AdditionalLogLocation
15-
>();
1610
isCustomLogDirectory: boolean;
1711

1812
constructor(title: string) {
@@ -32,27 +26,6 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
3226
} else {
3327
this.outputChannel.append(message);
3428
}
35-
36-
if (options.additionalLogLocation) {
37-
if (!isAbsolute(options.additionalLogLocation)) {
38-
throw new Error(
39-
`Additional Log Location must be an absolute path: ${options.additionalLogLocation}`,
40-
);
41-
}
42-
const logPath = options.additionalLogLocation;
43-
let additional = this.additionalLocations.get(logPath);
44-
if (!additional) {
45-
const msg = `| Log being saved to ${logPath} |`;
46-
const separator = new Array(msg.length).fill("-").join("");
47-
this.outputChannel.appendLine(separator);
48-
this.outputChannel.appendLine(msg);
49-
this.outputChannel.appendLine(separator);
50-
additional = new AdditionalLogLocation(logPath);
51-
this.additionalLocations.set(logPath, additional);
52-
}
53-
54-
await additional.log(message, options);
55-
}
5629
} catch (e) {
5730
if (e instanceof Error && e.message === "Channel has been closed") {
5831
// Output channel is closed logging to console instead
@@ -69,31 +42,6 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
6942
show(preserveFocus?: boolean): void {
7043
this.outputChannel.show(preserveFocus);
7144
}
72-
73-
removeAdditionalLogLocation(location: string | undefined): void {
74-
if (location) {
75-
this.additionalLocations.delete(location);
76-
}
77-
}
78-
}
79-
80-
class AdditionalLogLocation {
81-
constructor(private location: string) {}
82-
83-
async log(message: string, options = {} as LogOptions): Promise<void> {
84-
if (options.trailingNewline === undefined) {
85-
options.trailingNewline = true;
86-
}
87-
await ensureFile(this.location);
88-
89-
await appendFile(
90-
this.location,
91-
message + (options.trailingNewline ? "\n" : ""),
92-
{
93-
encoding: "utf8",
94-
},
95-
);
96-
}
9745
}
9846

9947
export type ProgressReporter = Progress<{ message: string }>;

extensions/ql-vscode/src/legacy-query-server/queryserver-client.ts

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { dirname } from "path";
21
import { ensureFile } from "fs-extra";
32

43
import { DisposableObject } from "../pure/disposable-object";
@@ -13,10 +12,8 @@ import {
1312
progress,
1413
ProgressMessage,
1514
WithProgressId,
16-
compileQuery,
1715
} from "../pure/legacy-messages";
1816
import { ProgressCallback, ProgressTask } from "../commandRunner";
19-
import { findQueryLogFile } from "../run-queries-shared";
2017
import { ServerProcess } from "../json-rpc-server";
2118

2219
type WithProgressReporting = (
@@ -56,7 +53,7 @@ export class QueryServerClient extends DisposableObject {
5653
this.queryServerStartListeners.push(e);
5754
};
5855

59-
public activeQueryLogFile: string | undefined;
56+
public activeQueryLogger: Logger;
6057

6158
constructor(
6259
readonly config: QueryServerConfig,
@@ -65,6 +62,9 @@ export class QueryServerClient extends DisposableObject {
6562
withProgressReporting: WithProgressReporting,
6663
) {
6764
super();
65+
// Since no query is active when we initialize, just point the "active query logger" to the
66+
// default logger.
67+
this.activeQueryLogger = this.logger;
6868
// When the query server configuration changes, restart the query server.
6969
if (config.onDidChangeConfiguration !== undefined) {
7070
this.push(
@@ -177,9 +177,8 @@ export class QueryServerClient extends DisposableObject {
177177
args,
178178
this.logger,
179179
(data) =>
180-
this.logger.log(data.toString(), {
180+
this.activeQueryLogger.log(data.toString(), {
181181
trailingNewline: false,
182-
additionalLogLocation: this.activeQueryLogFile,
183182
}),
184183
undefined, // no listener for stdout
185184
progressReporter,
@@ -240,8 +239,6 @@ export class QueryServerClient extends DisposableObject {
240239
): Promise<R> {
241240
const id = this.nextProgress++;
242241
this.progressCallbacks[id] = progress;
243-
244-
this.updateActiveQuery(type.method, parameter);
245242
try {
246243
if (this.serverProcess === undefined) {
247244
throw new Error("No query server process found.");
@@ -255,18 +252,4 @@ export class QueryServerClient extends DisposableObject {
255252
delete this.progressCallbacks[id];
256253
}
257254
}
258-
259-
/**
260-
* Updates the active query every time there is a new request to compile.
261-
* The active query is used to specify the side log.
262-
*
263-
* This isn't ideal because in situations where there are queries running
264-
* in parallel, each query's log messages are interleaved. Fixing this
265-
* properly will require a change in the query server.
266-
*/
267-
private updateActiveQuery(method: string, parameter: any): void {
268-
if (method === compileQuery.method) {
269-
this.activeQueryLogFile = findQueryLogFile(dirname(parameter.resultPath));
270-
}
271-
}
272255
}

extensions/ql-vscode/src/legacy-query-server/run-queries.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
} from "../helpers";
1616
import { ProgressCallback } from "../commandRunner";
1717
import { QueryMetadata } from "../pure/interface-types";
18-
import { extLogger } from "../common";
18+
import { extLogger, Logger, TeeLogger } from "../common";
1919
import * as messages from "../pure/legacy-messages";
2020
import { InitialQueryInfo, LocalQueryInfo } from "../query-results";
2121
import * as qsClient from "./queryserver-client";
@@ -66,7 +66,8 @@ export class QueryInProgress {
6666
dbItem: DatabaseItem,
6767
progress: ProgressCallback,
6868
token: CancellationToken,
69-
queryInfo?: LocalQueryInfo,
69+
logger: Logger,
70+
queryInfo: LocalQueryInfo | undefined,
7071
): Promise<messages.EvaluationResult> {
7172
if (!dbItem.contents || dbItem.error) {
7273
throw new Error("Can't run query on invalid database.");
@@ -137,7 +138,7 @@ export class QueryInProgress {
137138
await this.queryEvalInfo.addQueryLogs(
138139
queryInfo,
139140
qs.cliServer,
140-
qs.logger,
141+
logger,
141142
);
142143
} else {
143144
void showAndLogWarningMessage(
@@ -162,6 +163,7 @@ export class QueryInProgress {
162163
program: messages.QlProgram,
163164
progress: ProgressCallback,
164165
token: CancellationToken,
166+
logger: Logger,
165167
): Promise<messages.CompilationMessage[]> {
166168
let compiled: messages.CheckQueryResult | undefined;
167169
try {
@@ -190,16 +192,19 @@ export class QueryInProgress {
190192
target,
191193
};
192194

195+
// Update the active query logger every time there is a new request to compile.
196+
// This isn't ideal because in situations where there are queries running
197+
// in parallel, each query's log messages are interleaved. Fixing this
198+
// properly will require a change in the query server.
199+
qs.activeQueryLogger = logger;
193200
compiled = await qs.sendRequest(
194201
messages.compileQuery,
195202
params,
196203
token,
197204
progress,
198205
);
199206
} finally {
200-
void qs.logger.log(" - - - COMPILATION DONE - - - ", {
201-
additionalLogLocation: this.queryEvalInfo.logPath,
202-
});
207+
void logger.log(" - - - COMPILATION DONE - - - ");
203208
}
204209
return (compiled?.messages || []).filter(
205210
(msg) => msg.severity === messages.Severity.ERROR,
@@ -386,6 +391,8 @@ export async function compileAndRunQueryAgainstDatabase(
386391
metadata,
387392
templates,
388393
);
394+
const logger = new TeeLogger(qs.logger, query.queryEvalInfo.logPath);
395+
389396
await query.queryEvalInfo.createTimestampFile();
390397

391398
let upgradeDir: tmp.DirectoryResult | undefined;
@@ -402,7 +409,7 @@ export async function compileAndRunQueryAgainstDatabase(
402409
);
403410
let errors;
404411
try {
405-
errors = await query.compile(qs, qlProgram, progress, token);
412+
errors = await query.compile(qs, qlProgram, progress, token, logger);
406413
} catch (e) {
407414
if (
408415
e instanceof ResponseError &&
@@ -422,6 +429,7 @@ export async function compileAndRunQueryAgainstDatabase(
422429
dbItem,
423430
progress,
424431
token,
432+
logger,
425433
queryInfo,
426434
);
427435
if (result.resultType !== messages.QueryResultType.SUCCESS) {
@@ -439,18 +447,14 @@ export async function compileAndRunQueryAgainstDatabase(
439447
result,
440448
successful: result.resultType === messages.QueryResultType.SUCCESS,
441449
logFileLocation: result.logFileLocation,
442-
dispose: () => {
443-
qs.logger.removeAdditionalLogLocation(result.logFileLocation);
444-
},
445450
};
446451
} else {
447452
// Error dialogs are limited in size and scrollability,
448453
// so we include a general description of the problem,
449454
// and direct the user to the output window for the detailed compilation messages.
450455
// However we don't show quick eval errors there so we need to display them anyway.
451-
void qs.logger.log(
456+
void logger.log(
452457
`Failed to compile query ${initialInfo.queryPath} against database scheme ${qlProgram.dbschemePath}:`,
453-
{ additionalLogLocation: query.queryEvalInfo.logPath },
454458
);
455459

456460
const formattedMessages: string[] = [];
@@ -459,9 +463,7 @@ export async function compileAndRunQueryAgainstDatabase(
459463
const message = error.message || "[no error message available]";
460464
const formatted = `ERROR: ${message} (${error.position.fileName}:${error.position.line}:${error.position.column}:${error.position.endLine}:${error.position.endColumn})`;
461465
formattedMessages.push(formatted);
462-
void qs.logger.log(formatted, {
463-
additionalLogLocation: query.queryEvalInfo.logPath,
464-
});
466+
void logger.log(formatted);
465467
}
466468
if (initialInfo.isQuickEval && formattedMessages.length <= 2) {
467469
// If there are more than 2 error messages, they will not be displayed well in a popup
@@ -484,9 +486,8 @@ export async function compileAndRunQueryAgainstDatabase(
484486
try {
485487
await upgradeDir?.cleanup();
486488
} catch (e) {
487-
void qs.logger.log(
489+
void logger.log(
488490
`Could not clean up the upgrades dir. Reason: ${getErrorMessage(e)}`,
489-
{ additionalLogLocation: query.queryEvalInfo.logPath },
490491
);
491492
}
492493
}
@@ -535,9 +536,6 @@ function createSyntheticResult(
535536
runId: 0,
536537
},
537538
successful: false,
538-
dispose: () => {
539-
/**/
540-
},
541539
};
542540
}
543541

extensions/ql-vscode/src/query-history/query-history-manager.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,6 @@ export class QueryHistoryManager extends DisposableObject {
556556
!(await pathExists(item.completedQuery?.query.querySaveDir))
557557
) {
558558
this.treeDataProvider.remove(item);
559-
item.completedQuery?.dispose();
560559
}
561560
}),
562561
);
@@ -577,7 +576,6 @@ export class QueryHistoryManager extends DisposableObject {
577576
// Removing in progress local queries is not supported. They must be cancelled first.
578577
if (item.status !== QueryStatus.InProgress) {
579578
this.treeDataProvider.remove(item);
580-
item.completedQuery?.dispose();
581579

582580
// User has explicitly asked for this query to be removed.
583581
// We need to delete it from disk as well.

0 commit comments

Comments
 (0)