Skip to content

Commit 5ad754a

Browse files
authored
Merge pull request #1178 from github/aeisenberg/log-history
Save log files to the query history directory
2 parents c609377 + 4f04f9d commit 5ad754a

9 files changed

Lines changed: 132 additions & 201 deletions

File tree

extensions/ql-vscode/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Fix a bug where database upgrades could not be resolved if some of the target pack's dependencies are outside of the workspace. [#1138](https://github.com/github/vscode-codeql/pull/1138)
66
- Open the query server logs for query errors (instead of the extension log). This will make it easier to track down query errors. [#1158](https://github.com/github/vscode-codeql/pull/1158)
77
- Fix a bug where queries took a long time to run if there are no folders in the workspace. [#1157](https://github.com/github/vscode-codeql/pull/1157)
8+
- [BREAKING CHANGE] The `codeQL.runningQueries.customLogDirectory` setting is deprecated and no longer has any function. Instead, all query log files will be stored in the query history directory, next to the query results. [#1178](https://github.com/github/vscode-codeql/pull/1178)
9+
- Add a _Open query directory_ command for query items. This command opens the directory containing all artifacts for a query. [#1179](https://github.com/github/vscode-codeql/pull/1179)
810

911
## 1.5.11 - 10 February 2022
1012

extensions/ql-vscode/package.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@
208208
null
209209
],
210210
"default": null,
211-
"description": "Path to a directory where the CodeQL extension should store query server logs. If empty, the extension stores logs in a temporary workspace folder and deletes the contents after each run."
211+
"description": "Path to a directory where the CodeQL extension should store query server logs. If empty, the extension stores logs in a temporary workspace folder and deletes the contents after each run.",
212+
"markdownDeprecationMessage": "This property is deprecated and no longer has any effect. All query logs are stored in the query history folder next to the query results."
212213
},
213214
"codeQL.runningQueries.quickEvalCodelens": {
214215
"type": "boolean",
@@ -507,6 +508,10 @@
507508
"command": "codeQLQueryHistory.showQueryLog",
508509
"title": "Show Query Log"
509510
},
511+
{
512+
"command": "codeQLQueryHistory.openQueryDirectory",
513+
"title": "Open query directory"
514+
},
510515
{
511516
"command": "codeQLQueryHistory.cancel",
512517
"title": "Cancel"
@@ -696,6 +701,11 @@
696701
"group": "9_qlCommands",
697702
"when": "viewItem == rawResultsItem || viewItem == interpretedResultsItem"
698703
},
704+
{
705+
"command": "codeQLQueryHistory.openQueryDirectory",
706+
"group": "9_qlCommands",
707+
"when": "view == codeQLQueryHistory && !hasRemoteServer"
708+
},
699709
{
700710
"command": "codeQLQueryHistory.showQueryText",
701711
"group": "9_qlCommands",
@@ -886,6 +896,10 @@
886896
"command": "codeQLQueryHistory.showQueryLog",
887897
"when": "false"
888898
},
899+
{
900+
"command": "codeQLQueryHistory.openQueryDirectory",
901+
"when": "false"
902+
},
889903
{
890904
"command": "codeQLQueryHistory.cancel",
891905
"when": "false"

extensions/ql-vscode/src/extension.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,9 +1111,6 @@ function getContextStoragePath(ctx: ExtensionContext) {
11111111
}
11121112

11131113
async function initializeLogging(ctx: ExtensionContext): Promise<void> {
1114-
const storagePath = getContextStoragePath(ctx);
1115-
await logger.setLogStoragePath(storagePath, false);
1116-
await ideServerLogger.setLogStoragePath(storagePath, false);
11171114
ctx.subscriptions.push(logger);
11181115
ctx.subscriptions.push(queryServerLogger);
11191116
ctx.subscriptions.push(ideServerLogger);

extensions/ql-vscode/src/logging.ts

Lines changed: 13 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { window as Window, OutputChannel, Progress, Disposable } from 'vscode';
1+
import { window as Window, OutputChannel, Progress } from 'vscode';
22
import { DisposableObject } from './pure/disposable-object';
33
import * as fs from 'fs-extra';
44
import * as path from 'path';
@@ -26,18 +26,6 @@ export interface Logger {
2626
* @param location log to remove
2727
*/
2828
removeAdditionalLogLocation(location: string | undefined): void;
29-
30-
/**
31-
* The base location where all side log files are stored.
32-
*/
33-
getBaseLocation(): string | undefined;
34-
35-
/**
36-
* Sets the location where logs are stored.
37-
* @param storagePath The path where logs are stored.
38-
* @param isCustomLogDirectory Whether the logs are stored in a custom, user-specified directory.
39-
*/
40-
setLogStoragePath(storagePath: string, isCustomLogDirectory: boolean): Promise<void>;
4129
}
4230

4331
export type ProgressReporter = Progress<{ message: string }>;
@@ -46,27 +34,15 @@ export type ProgressReporter = Progress<{ message: string }>;
4634
export class OutputChannelLogger extends DisposableObject implements Logger {
4735
public readonly outputChannel: OutputChannel;
4836
private readonly additionalLocations = new Map<string, AdditionalLogLocation>();
49-
private additionalLogLocationPath: string | undefined;
5037
isCustomLogDirectory: boolean;
5138

52-
constructor(private title: string) {
39+
constructor(title: string) {
5340
super();
5441
this.outputChannel = Window.createOutputChannel(title);
5542
this.push(this.outputChannel);
5643
this.isCustomLogDirectory = false;
5744
}
5845

59-
async setLogStoragePath(storagePath: string, isCustomLogDirectory: boolean): Promise<void> {
60-
this.additionalLogLocationPath = path.join(storagePath, this.title);
61-
62-
this.isCustomLogDirectory = isCustomLogDirectory;
63-
64-
if (!this.isCustomLogDirectory) {
65-
// clear out any old state from previous runs
66-
await fs.remove(this.additionalLogLocationPath);
67-
}
68-
}
69-
7046
/**
7147
* This function is asynchronous and will only resolve once the message is written
7248
* to the side log (if required). It is not necessary to await the results of this
@@ -84,18 +60,20 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
8460
this.outputChannel.append(message);
8561
}
8662

87-
if (this.additionalLogLocationPath && options.additionalLogLocation) {
88-
const logPath = path.join(this.additionalLogLocationPath, options.additionalLogLocation);
63+
if (options.additionalLogLocation) {
64+
if (!path.isAbsolute(options.additionalLogLocation)) {
65+
throw new Error(`Additional Log Location must be an absolute path: ${options.additionalLogLocation}`);
66+
}
67+
const logPath = options.additionalLogLocation;
8968
let additional = this.additionalLocations.get(logPath);
9069
if (!additional) {
9170
const msg = `| Log being saved to ${logPath} |`;
9271
const separator = new Array(msg.length).fill('-').join('');
9372
this.outputChannel.appendLine(separator);
9473
this.outputChannel.appendLine(msg);
9574
this.outputChannel.appendLine(separator);
96-
additional = new AdditionalLogLocation(logPath, !this.isCustomLogDirectory);
75+
additional = new AdditionalLogLocation(logPath);
9776
this.additionalLocations.set(logPath, additional);
98-
this.track(additional);
9977
}
10078

10179
await additional.log(message, options);
@@ -115,26 +93,15 @@ export class OutputChannelLogger extends DisposableObject implements Logger {
11593
}
11694

11795
removeAdditionalLogLocation(location: string | undefined): void {
118-
if (this.additionalLogLocationPath && location) {
119-
const logPath = location.startsWith(this.additionalLogLocationPath)
120-
? location
121-
: path.join(this.additionalLogLocationPath, location);
122-
const additional = this.additionalLocations.get(logPath);
123-
if (additional) {
124-
this.disposeAndStopTracking(additional);
125-
this.additionalLocations.delete(logPath);
126-
}
96+
if (location) {
97+
this.additionalLocations.delete(location);
12798
}
12899
}
129-
130-
getBaseLocation() {
131-
return this.additionalLogLocationPath;
132-
}
133100
}
134101

135-
class AdditionalLogLocation extends Disposable {
136-
constructor(private location: string, private shouldDeleteLogs: boolean) {
137-
super(() => { /**/ });
102+
class AdditionalLogLocation {
103+
constructor(private location: string) {
104+
/**/
138105
}
139106

140107
async log(message: string, options = {} as LogOptions): Promise<void> {
@@ -147,12 +114,6 @@ class AdditionalLogLocation extends Disposable {
147114
encoding: 'utf8'
148115
});
149116
}
150-
151-
async dispose(): Promise<void> {
152-
if (this.shouldDeleteLogs) {
153-
await fs.remove(this.location);
154-
}
155-
}
156117
}
157118

158119
/** The global logger for the extension. */

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,12 @@ export class QueryHistoryManager extends DisposableObject {
400400
this.handleShowQueryLog.bind(this)
401401
)
402402
);
403+
this.push(
404+
commandRunner(
405+
'codeQLQueryHistory.openQueryDirectory',
406+
this.handleOpenQueryDirectory.bind(this)
407+
)
408+
);
403409
this.push(
404410
commandRunner(
405411
'codeQLQueryHistory.cancel',
@@ -703,6 +709,34 @@ export class QueryHistoryManager extends DisposableObject {
703709
}
704710
}
705711

712+
async handleOpenQueryDirectory(
713+
singleItem: QueryHistoryInfo,
714+
multiSelect: QueryHistoryInfo[]
715+
) {
716+
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
717+
718+
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
719+
return;
720+
}
721+
722+
let p: string | undefined;
723+
if (finalSingleItem.t === 'local') {
724+
if (finalSingleItem.completedQuery) {
725+
p = finalSingleItem.completedQuery.query.querySaveDir;
726+
}
727+
} else if (finalSingleItem.t === 'remote') {
728+
p = path.join(this.queryStorageDir, finalSingleItem.queryId);
729+
}
730+
731+
if (p) {
732+
try {
733+
await commands.executeCommand('revealFileInOS', Uri.file(p));
734+
} catch (e) {
735+
throw new Error(`Failed to open ${p}: ${e.message}`);
736+
}
737+
}
738+
}
739+
706740
async handleCancel(
707741
singleItem: QueryHistoryInfo,
708742
multiSelect: QueryHistoryInfo[]

extensions/ql-vscode/src/queryserver-client.ts

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as cp from 'child_process';
22
import * as path from 'path';
3+
import * as fs from 'fs-extra';
4+
35
import { DisposableObject } from './pure/disposable-object';
46
import { Disposable, CancellationToken, commands } from 'vscode';
57
import { createMessageConnection, MessageConnection, RequestType } from 'vscode-jsonrpc';
@@ -9,8 +11,6 @@ import { Logger, ProgressReporter } from './logging';
911
import { completeQuery, EvaluationResult, progress, ProgressMessage, WithProgressId } from './pure/messages';
1012
import * as messages from './pure/messages';
1113
import { ProgressCallback, ProgressTask } from './commandRunner';
12-
import * as fs from 'fs-extra';
13-
import * as helpers from './helpers';
1414

1515
type ServerOpts = {
1616
logger: Logger;
@@ -68,7 +68,7 @@ export class QueryServerClient extends DisposableObject {
6868
this.queryServerStartListeners.push(e);
6969
}
7070

71-
public activeQueryName: string | undefined;
71+
public activeQueryLogFile: string | undefined;
7272

7373
constructor(
7474
readonly config: QueryServerConfig,
@@ -89,26 +89,6 @@ export class QueryServerClient extends DisposableObject {
8989
this.evaluationResultCallbacks = {};
9090
}
9191

92-
async initLogger() {
93-
let storagePath = this.opts.contextStoragePath;
94-
let isCustomLogDirectory = false;
95-
if (this.config.customLogDirectory) {
96-
try {
97-
if (!(await fs.pathExists(this.config.customLogDirectory))) {
98-
await fs.mkdir(this.config.customLogDirectory);
99-
}
100-
void this.logger.log(`Saving query server logs to user-specified directory: ${this.config.customLogDirectory}.`);
101-
storagePath = this.config.customLogDirectory;
102-
isCustomLogDirectory = true;
103-
} catch (e) {
104-
void helpers.showAndLogErrorMessage(`${this.config.customLogDirectory} is not a valid directory. Logs will be stored in a temporary workspace directory instead.`);
105-
}
106-
}
107-
108-
await this.logger.setLogStoragePath(storagePath, isCustomLogDirectory);
109-
110-
}
111-
11292
get logger(): Logger {
11393
return this.opts.logger;
11494
}
@@ -150,7 +130,6 @@ export class QueryServerClient extends DisposableObject {
150130

151131
/** Starts a new query server process, sending progress messages to the given reporter. */
152132
private async startQueryServerImpl(progressReporter: ProgressReporter): Promise<void> {
153-
await this.initLogger();
154133
const ramArgs = await this.cliServer.resolveRam(this.config.queryMemoryMb, progressReporter);
155134
const args = ['--threads', this.config.numThreads.toString()].concat(ramArgs);
156135

@@ -172,10 +151,13 @@ export class QueryServerClient extends DisposableObject {
172151
}
173152

174153
if (await this.cliServer.cliConstraints.supportsStructuredEvalLog()) {
154+
const structuredLogFile = `${this.opts.contextStoragePath}/structured-evaluator-log.json`;
155+
await fs.ensureFile(structuredLogFile);
156+
175157
args.push('--evaluator-log');
176-
args.push(`${this.opts.contextStoragePath}/structured-evaluator-log.json`);
158+
args.push(structuredLogFile);
177159

178-
// We hard-code the verbosity level to 5 and minify to false.
160+
// We hard-code the verbosity level to 5 and minify to false.
179161
// This will be the behavior of the per-query structured logging in the CLI after 2.8.3.
180162
args.push('--evaluator-log-level');
181163
args.push('5');
@@ -186,7 +168,7 @@ export class QueryServerClient extends DisposableObject {
186168
}
187169

188170
if (cli.shouldDebugQueryServer()) {
189-
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9010,server=y,suspend=n,quiet=y');
171+
args.push('-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9010,server=n,suspend=y,quiet=y');
190172
}
191173

192174
const child = cli.spawnServer(
@@ -197,7 +179,7 @@ export class QueryServerClient extends DisposableObject {
197179
this.logger,
198180
data => this.logger.log(data.toString(), {
199181
trailingNewline: false,
200-
additionalLogLocation: this.activeQueryName
182+
additionalLogLocation: this.activeQueryLogFile
201183
}),
202184
undefined, // no listener for stdout
203185
progressReporter
@@ -208,10 +190,6 @@ export class QueryServerClient extends DisposableObject {
208190
if (!(res.runId in this.evaluationResultCallbacks)) {
209191
void this.logger.log(`No callback associated with run id ${res.runId}, continuing without executing any callback`);
210192
} else {
211-
const baseLocation = this.logger.getBaseLocation();
212-
if (baseLocation && this.activeQueryName) {
213-
res.logFileLocation = path.join(baseLocation, this.activeQueryName);
214-
}
215193
this.evaluationResultCallbacks[res.runId](res);
216194
}
217195
return {};
@@ -272,8 +250,11 @@ export class QueryServerClient extends DisposableObject {
272250
*/
273251
private updateActiveQuery(method: string, parameter: any): void {
274252
if (method === messages.compileQuery.method) {
275-
const queryPath = parameter?.queryToCheck?.queryPath || 'unknown';
276-
this.activeQueryName = `query-${path.basename(queryPath)}-${this.nextProgress}.log`;
253+
this.activeQueryLogFile = findQueryLogFile(path.dirname(parameter.resultPath));
277254
}
278255
}
279256
}
257+
258+
export function findQueryLogFile(resultPath: string): string {
259+
return path.join(resultPath, 'query.log');
260+
}

extensions/ql-vscode/src/remote-queries/remote-query-history-item.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ export interface RemoteQueryHistoryItem {
1010
status: QueryStatus;
1111
completed: boolean;
1212
readonly queryId: string,
13-
label: string, // TODO, the query label should have interpolation like local queries
14-
remoteQuery: RemoteQuery,
13+
label: string; // TODO, the query label should have interpolation like local queries
14+
remoteQuery: RemoteQuery;
1515
}

0 commit comments

Comments
 (0)