Skip to content

Commit a8def17

Browse files
committed
feat: Enhance logging in ResultsProvider and UiProvider, implement pagination for GitHub alerts, and add structured logging for scan statistics
1 parent c001c46 commit a8def17

File tree

4 files changed

+302
-30
lines changed

4 files changed

+302
-30
lines changed

src/providers/resultsProvider.ts

Lines changed: 199 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as vscode from 'vscode';
22
import { ScanResult, FlowStep } from '../services/codeqlService';
3+
import { LoggerService } from '../services/loggerService';
34

45
export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
56
private _onDidChangeTreeData: vscode.EventEmitter<ResultItem | undefined | null | void> = new vscode.EventEmitter<ResultItem | undefined | null | void>();
@@ -8,32 +9,57 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
89
private results: ScanResult[] = [];
910
private diagnosticCollection: vscode.DiagnosticCollection;
1011
private hasBeenScanned: boolean = false;
12+
private logger: LoggerService;
1113

1214
constructor() {
1315
// Create a diagnostic collection for CodeQL security issues
1416
this.diagnosticCollection = vscode.languages.createDiagnosticCollection('codeql-security');
17+
this.logger = LoggerService.getInstance();
1518
}
1619

1720
refresh(): void {
21+
this.logger.debug('ResultsProvider', 'Refreshing tree view');
1822
this._onDidChangeTreeData.fire();
1923
}
2024

2125
setResults(results: ScanResult[]): void {
26+
this.logger.logServiceCall('ResultsProvider', 'setResults', 'started', { count: results.length });
27+
28+
// Count results by severity
29+
const severityCounts: { [severity: string]: number } = {};
30+
results.forEach(result => {
31+
const severity = result.severity || 'unknown';
32+
severityCounts[severity] = (severityCounts[severity] || 0) + 1;
33+
});
34+
2235
this.results = results;
2336
this.hasBeenScanned = true;
2437
this.updateDiagnostics(results);
2538
this.refresh();
39+
40+
// Generate comprehensive statistics if we have results
41+
if (results.length > 0) {
42+
this.logResultsStatistics();
43+
}
44+
45+
this.logger.logServiceCall('ResultsProvider', 'setResults', 'completed', {
46+
totalCount: results.length,
47+
severityCounts: severityCounts
48+
});
2649
}
2750

2851
getResults(): ScanResult[] {
52+
this.logger.debug('ResultsProvider', 'Getting results', { count: this.results.length });
2953
return this.results;
3054
}
3155

3256
clearResults(): void {
57+
this.logger.logServiceCall('ResultsProvider', 'clearResults', 'started');
3358
this.results = [];
3459
this.hasBeenScanned = false;
3560
this.diagnosticCollection.clear();
3661
this.refresh();
62+
this.logger.logServiceCall('ResultsProvider', 'clearResults', 'completed');
3763
}
3864

3965
getTreeItem(element: ResultItem): vscode.TreeItem {
@@ -43,6 +69,8 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
4369
getChildren(element?: ResultItem): Thenable<ResultItem[]> {
4470
if (!element) {
4571
// Root level - group by language
72+
this.logger.debug('ResultsProvider', 'Getting root level tree items');
73+
4674
if (!this.results || this.results.length === 0) {
4775
// Show different messages based on whether a scan has been run
4876
const message = this.hasBeenScanned
@@ -52,6 +80,8 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
5280
? 'No security vulnerabilities were found in the scanned code'
5381
: 'Click "CodeQL: Run Scan" to analyze your code for security vulnerabilities';
5482

83+
this.logger.debug('ResultsProvider', `Showing empty state: ${message}`);
84+
5585
return Promise.resolve([
5686
new ResultItem(
5787
message,
@@ -68,6 +98,12 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
6898
}
6999

70100
const groups = this.groupByLanguage(this.results);
101+
102+
this.logger.debug('ResultsProvider', 'Grouping results by language', {
103+
languages: Object.keys(groups),
104+
counts: Object.fromEntries(Object.entries(groups).map(([lang, results]) => [lang, results.length]))
105+
});
106+
71107
return Promise.resolve(
72108
Object.entries(groups).map(([language, results]) =>
73109
new ResultItem(
@@ -81,8 +117,14 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
81117
);
82118
} else if (element.type === 'language') {
83119
// Second level - group by severity within language
120+
this.logger.debug('ResultsProvider', `Getting issues for language: ${element.language}`, {
121+
language: element.language,
122+
count: element.results?.length || 0
123+
});
124+
84125
if (!element.results || element.results.length === 0) {
85126
// Show "no results" for this language
127+
this.logger.debug('ResultsProvider', `No results for language: ${element.language}`);
86128
return Promise.resolve([
87129
new ResultItem(
88130
'✅ No security alerts found',
@@ -100,6 +142,13 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
100142

101143
const severityGroups = this.groupBySeverity(element.results);
102144
const sortedSeverities = this.sortSeverityGroups(severityGroups);
145+
146+
this.logger.debug('ResultsProvider', `Grouped issues by severity for ${element.language}`, {
147+
language: element.language,
148+
severities: Object.keys(severityGroups),
149+
counts: Object.fromEntries(Object.entries(severityGroups).map(([sev, results]) => [sev, results.length]))
150+
});
151+
103152
return Promise.resolve(
104153
sortedSeverities.map(([severity, results]) =>
105154
new ResultItem(
@@ -115,6 +164,12 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
115164
);
116165
} else if (element.type === 'severity') {
117166
// Third level - individual results
167+
this.logger.debug('ResultsProvider', `Getting individual results for ${element.language}/${element.severity}`, {
168+
language: element.language,
169+
severity: element.severity,
170+
count: element.results?.length || 0
171+
});
172+
118173
return Promise.resolve(
119174
element.results!.map(result =>
120175
new ResultItem(
@@ -132,6 +187,14 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
132187
} else if (element.type === 'result' && element.result?.flowSteps) {
133188
// Fourth level - flow steps (hidden by default)
134189
const flowSteps = element.result.flowSteps;
190+
191+
this.logger.debug('ResultsProvider', `Expanding flow steps for result: ${element.result.ruleId}`, {
192+
ruleId: element.result.ruleId,
193+
steps: flowSteps.length,
194+
file: element.result.location.file,
195+
line: element.result.location.startLine
196+
});
197+
135198
return Promise.resolve(
136199
flowSteps.map((step, index) => {
137200
const isSource = index === 0;
@@ -162,6 +225,8 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
162225
}
163226

164227
private groupByLanguage(results: ScanResult[]): { [language: string]: ScanResult[] } {
228+
this.logger.logServiceCall('ResultsProvider', 'groupByLanguage', 'started', { count: results.length });
229+
165230
// Get configured languages from settings
166231
const config = vscode.workspace.getConfiguration("codeql-scanner");
167232
const configuredLanguages = config.get<string[]>("languages", []);
@@ -183,18 +248,33 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
183248
}
184249
});
185250

251+
this.logger.logServiceCall('ResultsProvider', 'groupByLanguage', 'completed', {
252+
languages: Object.keys(groups),
253+
configuredLanguages: configuredLanguages,
254+
counts: Object.fromEntries(Object.entries(groups).map(([lang, results]) => [lang, results.length]))
255+
});
256+
186257
return groups;
187258
}
188259

189260
private groupBySeverity(results: ScanResult[]): { [severity: string]: ScanResult[] } {
190-
return results.reduce((groups, result) => {
261+
this.logger.debug('ResultsProvider', 'Grouping results by severity', { count: results.length });
262+
263+
const groups = results.reduce((groups, result) => {
191264
const severity = result.severity || 'unknown';
192265
if (!groups[severity]) {
193266
groups[severity] = [];
194267
}
195268
groups[severity].push(result);
196269
return groups;
197270
}, {} as { [severity: string]: ScanResult[] });
271+
272+
this.logger.debug('ResultsProvider', 'Severity grouping completed', {
273+
severities: Object.keys(groups),
274+
counts: Object.fromEntries(Object.entries(groups).map(([sev, results]) => [sev, results.length]))
275+
});
276+
277+
return groups;
198278
}
199279

200280
private getSeverityDisplayName(severity: string): string {
@@ -212,9 +292,13 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
212292
}
213293

214294
private sortSeverityGroups(severityGroups: { [severity: string]: ScanResult[] }): [string, ScanResult[]][] {
295+
this.logger.debug('ResultsProvider', 'Sorting severity groups', {
296+
severities: Object.keys(severityGroups)
297+
});
298+
215299
const severityOrder = ['critical', 'high', 'error', 'medium', 'warning', 'low', 'info', 'unknown'];
216300

217-
return Object.entries(severityGroups).sort(([a], [b]) => {
301+
const sorted = Object.entries(severityGroups).sort(([a], [b]) => {
218302
const aIndex = severityOrder.indexOf(a);
219303
const bIndex = severityOrder.indexOf(b);
220304

@@ -225,13 +309,22 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
225309

226310
return aIndex - bIndex;
227311
});
312+
313+
this.logger.debug('ResultsProvider', 'Severity groups sorted', {
314+
sortedOrder: sorted.map(([severity]) => severity)
315+
});
316+
317+
return sorted;
228318
}
229319

230320
private updateDiagnostics(results: ScanResult[]): void {
321+
this.logger.logServiceCall('ResultsProvider', 'updateDiagnostics', 'started', { count: results.length });
322+
231323
// Clear existing diagnostics
232324
this.diagnosticCollection.clear();
233325

234326
if (!results || results.length === 0) {
327+
this.logger.debug('ResultsProvider', 'No diagnostics to display');
235328
return;
236329
}
237330

@@ -323,26 +416,120 @@ export class ResultsProvider implements vscode.TreeDataProvider<ResultItem> {
323416
diagnosticsMap.forEach((diagnostics, uriString) => {
324417
this.diagnosticCollection.set(vscode.Uri.parse(uriString), diagnostics);
325418
});
419+
420+
this.logger.logServiceCall('ResultsProvider', 'updateDiagnostics', 'completed', {
421+
fileCount: diagnosticsMap.size,
422+
diagnosticCount: Array.from(diagnosticsMap.values()).reduce((sum, diags) => sum + diags.length, 0)
423+
});
326424
}
327425

328426
private mapToVSCodeSeverity(severity: string): vscode.DiagnosticSeverity {
427+
const originalSeverity = severity;
428+
let vscSeverity: vscode.DiagnosticSeverity;
429+
329430
switch (severity?.toLowerCase()) {
330431
case 'critical':
331432
case 'high':
332433
case 'error':
333-
return vscode.DiagnosticSeverity.Error;
434+
vscSeverity = vscode.DiagnosticSeverity.Error;
435+
break;
334436
case 'medium':
335437
case 'warning':
336-
return vscode.DiagnosticSeverity.Warning;
438+
vscSeverity = vscode.DiagnosticSeverity.Warning;
439+
break;
337440
case 'low':
338441
case 'info':
339-
return vscode.DiagnosticSeverity.Information;
442+
vscSeverity = vscode.DiagnosticSeverity.Information;
443+
break;
340444
default:
341-
return vscode.DiagnosticSeverity.Warning;
445+
vscSeverity = vscode.DiagnosticSeverity.Warning;
446+
break;
342447
}
448+
449+
this.logger.debug('ResultsProvider', 'Mapped severity', {
450+
from: originalSeverity,
451+
to: vscode.DiagnosticSeverity[vscSeverity]
452+
});
453+
454+
return vscSeverity;
455+
}
456+
457+
/**
458+
* Logs comprehensive statistics about the scan results
459+
* This provides a detailed breakdown of issues by language and severity
460+
*/
461+
private logResultsStatistics(): void {
462+
if (!this.results || this.results.length === 0) {
463+
this.logger.info('ResultsStatistics', 'No scan results to analyze');
464+
return;
465+
}
466+
467+
// Overall statistics
468+
const totalAlerts = this.results.length;
469+
470+
// Group by language
471+
const languageGroups = this.groupByLanguage(this.results);
472+
const languageStats = Object.entries(languageGroups).map(([lang, results]) => ({
473+
language: lang,
474+
count: results.length,
475+
percentage: Math.round((results.length / totalAlerts) * 100)
476+
}));
477+
478+
// Group by severity
479+
const severityCounts: { [severity: string]: number } = {};
480+
this.results.forEach(result => {
481+
const severity = result.severity || 'unknown';
482+
severityCounts[severity] = (severityCounts[severity] || 0) + 1;
483+
});
484+
485+
// Count rules
486+
const ruleMap: { [ruleId: string]: number } = {};
487+
this.results.forEach(result => {
488+
const ruleId = result.ruleId || 'unknown';
489+
ruleMap[ruleId] = (ruleMap[ruleId] || 0) + 1;
490+
});
491+
492+
// Find top rules
493+
const topRules = Object.entries(ruleMap)
494+
.sort(([, a], [, b]) => b - a)
495+
.slice(0, 5)
496+
.map(([rule, count]) => ({ rule, count, percentage: Math.round((count / totalAlerts) * 100) }));
497+
498+
// Count files with issues
499+
const fileMap: { [file: string]: number } = {};
500+
this.results.forEach(result => {
501+
if (result.location && result.location.file) {
502+
const fileName = result.location.file.split('/').pop() || 'unknown';
503+
fileMap[fileName] = (fileMap[fileName] || 0) + 1;
504+
}
505+
});
506+
507+
// Find files with most issues
508+
const topFiles = Object.entries(fileMap)
509+
.sort(([, a], [, b]) => b - a)
510+
.slice(0, 5)
511+
.map(([file, count]) => ({ file, count, percentage: Math.round((count / totalAlerts) * 100) }));
512+
513+
// Count data flow results
514+
const dataFlowCount = this.results.filter(r => r.flowSteps && r.flowSteps.length > 0).length;
515+
const dataFlowPercentage = Math.round((dataFlowCount / totalAlerts) * 100);
516+
517+
// Log the comprehensive statistics using the specialized method
518+
this.logger.logScanStatistics('ResultsProvider', {
519+
totalCount: totalAlerts,
520+
byLanguage: languageStats,
521+
bySeverity: severityCounts,
522+
topRules,
523+
topFiles,
524+
dataFlow: {
525+
count: dataFlowCount,
526+
percentage: dataFlowPercentage
527+
}
528+
});
343529
}
344530

345531
dispose(): void {
532+
this.logger.info('ResultsProvider', 'Disposing ResultsProvider');
346533
this.diagnosticCollection.dispose();
347534
}
348535
}
@@ -485,11 +672,14 @@ export class ResultItem extends vscode.TreeItem {
485672
}
486673

487674
private getCommand(): vscode.Command | undefined {
675+
const logger = LoggerService.getInstance();
676+
488677
if (this.type === 'result' && this.result) {
489678
return {
490-
command: 'vscode.open',
679+
command: 'codeql-scanner.resultSelected',
491680
title: 'Open File',
492681
arguments: [
682+
this,
493683
vscode.Uri.file(this.result.location.file),
494684
{
495685
selection: new vscode.Range(
@@ -503,9 +693,10 @@ export class ResultItem extends vscode.TreeItem {
503693
};
504694
} else if (this.type === 'flowStep' && this.flowStep) {
505695
return {
506-
command: 'vscode.open',
696+
command: 'codeql-scanner.flowStepSelected',
507697
title: 'Open Flow Step',
508698
arguments: [
699+
this,
509700
vscode.Uri.file(this.flowStep.file),
510701
{
511702
selection: new vscode.Range(

0 commit comments

Comments
 (0)