Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions extensions/ql-vscode/src/common/logging/tee-logger.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { appendFile, ensureFile } from "fs-extra";
import { ensureFile } from "fs-extra";
import { open } from "node:fs/promises";
import type { FileHandle } from "node:fs/promises";
import { isAbsolute } from "path";
import { getErrorMessage } from "../helpers-pure";
import type { Logger, LogOptions } from "./logger";
import type { Disposable } from "../disposable-object";

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

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

if (!this.error) {
try {
if (!this.fileHandle) {
await ensureFile(this.location);

this.fileHandle = await open(this.location, "a");
}

const trailingNewline = options.trailingNewline ?? true;
await ensureFile(this.location);

await appendFile(
this.location,
await this.fileHandle.appendFile(
message + (trailingNewline ? "\n" : ""),
{
encoding: "utf8",
Expand All @@ -50,6 +58,14 @@ export class TeeLogger implements Logger {
} catch (e) {
// Write an error message to the primary log, and stop trying to write to the side log.
this.error = true;
try {
await this.fileHandle?.close();
} catch (e) {
void this.logger.log(
`Failed to close file handle: ${getErrorMessage(e)}`,
);
}
this.fileHandle = undefined;
Comment thread
koesie10 marked this conversation as resolved.
const errorMessage = getErrorMessage(e);
await this.logger.log(
`Error writing to additional log file: ${errorMessage}`,
Expand All @@ -65,4 +81,15 @@ export class TeeLogger implements Logger {
show(preserveFocus?: boolean): void {
this.logger.show(preserveFocus);
}

dispose(): void {
try {
void this.fileHandle?.close();
} catch (e) {
void this.logger.log(
`Failed to close file handle: ${getErrorMessage(e)}`,
);
}
this.fileHandle = undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,12 @@ export async function runContextualQuery(
void extLogger.log(
`Running contextual query ${query}; results will be stored in ${queryRun.outputDir.querySaveDir}`,
);
const results = await queryRun.evaluate(
progress,
token,
new TeeLogger(qs.logger, queryRun.outputDir.logPath),
);
await cleanup?.();
return results;
const teeLogger = new TeeLogger(qs.logger, queryRun.outputDir.logPath);

try {
return await queryRun.evaluate(progress, token, teeLogger);
} finally {
await cleanup?.();
teeLogger.dispose();
}
}
11 changes: 10 additions & 1 deletion extensions/ql-vscode/src/local-queries/local-query-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { redactableError } from "../common/errors";
import type { LocalQueries } from "./local-queries";
import { tryGetQueryMetadata } from "../codeql-cli/query-metadata";
import { telemetryListener } from "../common/vscode/telemetry";
import type { Disposable } from "../common/disposable-object";

function formatResultMessage(result: CoreQueryResults): string {
switch (result.resultType) {
Expand Down Expand Up @@ -61,7 +62,11 @@ export class LocalQueryRun {
private readonly localQueries: LocalQueries,
private readonly queryInfo: LocalQueryInfo,
private readonly dbItem: DatabaseItem,
public readonly logger: Logger, // Public so that other clients, like the debug adapter, know where to send log output
/**
* The logger is only available while the query is running and will be disposed of when the
* query completes.
*/
public readonly logger: Logger & Disposable, // Public so that other clients, like the debug adapter, know where to send log output
private readonly queryHistoryManager: QueryHistoryManager,
private readonly cliServer: CodeQLCliServer,
) {}
Expand Down Expand Up @@ -92,6 +97,8 @@ export class LocalQueryRun {
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
await this.queryHistoryManager.refreshTreeView();

this.logger.dispose();
}

/**
Expand All @@ -110,6 +117,8 @@ export class LocalQueryRun {
err.message = `Error running query: ${err.message}`;
this.queryInfo.failureReason = err.message;
await this.queryHistoryManager.refreshTreeView();

this.logger.dispose();
}

/**
Expand Down
34 changes: 20 additions & 14 deletions extensions/ql-vscode/src/local-queries/run-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,27 @@ export async function runQuery({
undefined,
);

const completedQuery = await queryRun.evaluate(
progress,
token,
new TeeLogger(queryRunner.logger, queryRun.outputDir.logPath),
const teeLogger = new TeeLogger(
queryRunner.logger,
queryRun.outputDir.logPath,
);

if (completedQuery.resultType !== QueryResultType.SUCCESS) {
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError`Failed to run ${basename(queryPath)} query: ${
completedQuery.message ?? "No message"
}`,
);
return;
try {
const completedQuery = await queryRun.evaluate(progress, token, teeLogger);

if (completedQuery.resultType !== QueryResultType.SUCCESS) {
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError`Failed to run ${basename(queryPath)} query: ${
completedQuery.message ?? "No message"
}`,
);
return;
}

return completedQuery;
} finally {
teeLogger.dispose();
}
return completedQuery;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { dirSync } from "tmp";
import type { BaseLogger, Logger } from "../../../src/common/logging";
import { TeeLogger } from "../../../src/common/logging";
import { OutputChannelLogger } from "../../../src/common/logging/vscode";
import type { Disposable } from "../../../src/common/disposable-object";

jest.setTimeout(999999);

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

// should have created 1 side log
expect(readdirSync(tempFolders.storagePath.name)).toEqual(["hucairz"]);

hucairz.dispose();
});

it("should create a side log", async () => {
Expand All @@ -86,12 +89,15 @@ describe("OutputChannelLogger tests", function () {
expect(
readFileSync(join(tempFolders.storagePath.name, "second"), "utf8"),
).toBe("yyy\n");

first.dispose();
second.dispose();
});

function createSideLogger(
logger: Logger,
additionalLogLocation: string,
): BaseLogger {
): BaseLogger & Disposable {
return new TeeLogger(
logger,
join(tempFolders.storagePath.name, additionalLogLocation),
Expand Down