Skip to content

Commit ff6f6cd

Browse files
authored
Merge pull request #3515 from github/koesie10/tee-logger-file-handle
Use file handle in TeeLogger
2 parents a5239fb + e52a08d commit ff6f6cd

File tree

5 files changed

+77
-28
lines changed

5 files changed

+77
-28
lines changed

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

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import { appendFile, ensureFile } from "fs-extra";
1+
import { ensureFile } from "fs-extra";
2+
import { open } from "node:fs/promises";
3+
import type { FileHandle } from "node:fs/promises";
24
import { isAbsolute } from "path";
35
import { getErrorMessage } from "../helpers-pure";
46
import type { Logger, LogOptions } from "./logger";
7+
import type { Disposable } from "../disposable-object";
58

69
/**
710
* An implementation of {@link Logger} that sends the output both to another {@link Logger}
@@ -10,9 +13,10 @@ import type { Logger, LogOptions } from "./logger";
1013
* The first time a message is written, an additional banner is written to the underlying logger
1114
* pointing the user to the "side log" file.
1215
*/
13-
export class TeeLogger implements Logger {
16+
export class TeeLogger implements Logger, Disposable {
1417
private emittedRedirectMessage = false;
1518
private error = false;
19+
private fileHandle: FileHandle | undefined = undefined;
1620

1721
public constructor(
1822
private readonly logger: Logger,
@@ -37,11 +41,15 @@ export class TeeLogger implements Logger {
3741

3842
if (!this.error) {
3943
try {
44+
if (!this.fileHandle) {
45+
await ensureFile(this.location);
46+
47+
this.fileHandle = await open(this.location, "a");
48+
}
49+
4050
const trailingNewline = options.trailingNewline ?? true;
41-
await ensureFile(this.location);
4251

43-
await appendFile(
44-
this.location,
52+
await this.fileHandle.appendFile(
4553
message + (trailingNewline ? "\n" : ""),
4654
{
4755
encoding: "utf8",
@@ -50,6 +58,14 @@ export class TeeLogger implements Logger {
5058
} catch (e) {
5159
// Write an error message to the primary log, and stop trying to write to the side log.
5260
this.error = true;
61+
try {
62+
await this.fileHandle?.close();
63+
} catch (e) {
64+
void this.logger.log(
65+
`Failed to close file handle: ${getErrorMessage(e)}`,
66+
);
67+
}
68+
this.fileHandle = undefined;
5369
const errorMessage = getErrorMessage(e);
5470
await this.logger.log(
5571
`Error writing to additional log file: ${errorMessage}`,
@@ -65,4 +81,15 @@ export class TeeLogger implements Logger {
6581
show(preserveFocus?: boolean): void {
6682
this.logger.show(preserveFocus);
6783
}
84+
85+
dispose(): void {
86+
try {
87+
void this.fileHandle?.close();
88+
} catch (e) {
89+
void this.logger.log(
90+
`Failed to close file handle: ${getErrorMessage(e)}`,
91+
);
92+
}
93+
this.fileHandle = undefined;
94+
}
6895
}

extensions/ql-vscode/src/language-support/contextual/query-resolver.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,12 @@ export async function runContextualQuery(
9292
void extLogger.log(
9393
`Running contextual query ${query}; results will be stored in ${queryRun.outputDir.querySaveDir}`,
9494
);
95-
const results = await queryRun.evaluate(
96-
progress,
97-
token,
98-
new TeeLogger(qs.logger, queryRun.outputDir.logPath),
99-
);
100-
await cleanup?.();
101-
return results;
95+
const teeLogger = new TeeLogger(qs.logger, queryRun.outputDir.logPath);
96+
97+
try {
98+
return await queryRun.evaluate(progress, token, teeLogger);
99+
} finally {
100+
await cleanup?.();
101+
teeLogger.dispose();
102+
}
102103
}

extensions/ql-vscode/src/local-queries/local-query-run.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { redactableError } from "../common/errors";
2525
import type { LocalQueries } from "./local-queries";
2626
import { tryGetQueryMetadata } from "../codeql-cli/query-metadata";
2727
import { telemetryListener } from "../common/vscode/telemetry";
28+
import type { Disposable } from "../common/disposable-object";
2829

2930
function formatResultMessage(result: CoreQueryResults): string {
3031
switch (result.resultType) {
@@ -61,7 +62,11 @@ export class LocalQueryRun {
6162
private readonly localQueries: LocalQueries,
6263
private readonly queryInfo: LocalQueryInfo,
6364
private readonly dbItem: DatabaseItem,
64-
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
65+
/**
66+
* The logger is only available while the query is running and will be disposed of when the
67+
* query completes.
68+
*/
69+
public readonly logger: Logger & Disposable, // Public so that other clients, like the debug adapter, know where to send log output
6570
private readonly queryHistoryManager: QueryHistoryManager,
6671
private readonly cliServer: CodeQLCliServer,
6772
) {}
@@ -92,6 +97,8 @@ export class LocalQueryRun {
9297
// Note we must update the query history view after showing results as the
9398
// display and sorting might depend on the number of results
9499
await this.queryHistoryManager.refreshTreeView();
100+
101+
this.logger.dispose();
95102
}
96103

97104
/**
@@ -110,6 +117,8 @@ export class LocalQueryRun {
110117
err.message = `Error running query: ${err.message}`;
111118
this.queryInfo.failureReason = err.message;
112119
await this.queryHistoryManager.refreshTreeView();
120+
121+
this.logger.dispose();
113122
}
114123

115124
/**

extensions/ql-vscode/src/local-queries/run-query.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,27 @@ export async function runQuery({
4747
undefined,
4848
);
4949

50-
const completedQuery = await queryRun.evaluate(
51-
progress,
52-
token,
53-
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
50+
const teeLogger = new TeeLogger(
51+
queryRunner.logger,
52+
queryRun.outputDir.logPath,
5453
);
5554

56-
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
57-
void showAndLogExceptionWithTelemetry(
58-
extLogger,
59-
telemetryListener,
60-
redactableError`Failed to run ${basename(queryPath)} query: ${
61-
completedQuery.message ?? "No message"
62-
}`,
63-
);
64-
return;
55+
try {
56+
const completedQuery = await queryRun.evaluate(progress, token, teeLogger);
57+
58+
if (completedQuery.resultType !== QueryResultType.SUCCESS) {
59+
void showAndLogExceptionWithTelemetry(
60+
extLogger,
61+
telemetryListener,
62+
redactableError`Failed to run ${basename(queryPath)} query: ${
63+
completedQuery.message ?? "No message"
64+
}`,
65+
);
66+
return;
67+
}
68+
69+
return completedQuery;
70+
} finally {
71+
teeLogger.dispose();
6572
}
66-
return completedQuery;
6773
}

extensions/ql-vscode/test/common/logging/output-channel-logger.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { dirSync } from "tmp";
55
import type { BaseLogger, Logger } from "../../../src/common/logging";
66
import { TeeLogger } from "../../../src/common/logging";
77
import { OutputChannelLogger } from "../../../src/common/logging/vscode";
8+
import type { Disposable } from "../../../src/common/disposable-object";
89

910
jest.setTimeout(999999);
1011

@@ -66,6 +67,8 @@ describe("OutputChannelLogger tests", function () {
6667

6768
// should have created 1 side log
6869
expect(readdirSync(tempFolders.storagePath.name)).toEqual(["hucairz"]);
70+
71+
hucairz.dispose();
6972
});
7073

7174
it("should create a side log", async () => {
@@ -86,12 +89,15 @@ describe("OutputChannelLogger tests", function () {
8689
expect(
8790
readFileSync(join(tempFolders.storagePath.name, "second"), "utf8"),
8891
).toBe("yyy\n");
92+
93+
first.dispose();
94+
second.dispose();
8995
});
9096

9197
function createSideLogger(
9298
logger: Logger,
9399
additionalLogLocation: string,
94-
): BaseLogger {
100+
): BaseLogger & Disposable {
95101
return new TeeLogger(
96102
logger,
97103
join(tempFolders.storagePath.name, additionalLogLocation),

0 commit comments

Comments
 (0)