Skip to content

Commit d13d14c

Browse files
committed
feat: add grouping by topics for log entries and update version to 1.15.0
1 parent 805a202 commit d13d14c

File tree

3 files changed

+129
-32
lines changed

3 files changed

+129
-32
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ All notable changes to the "magento-log-viewer" extension will be documented in
88

99
## Latest Release
1010

11+
### [1.15.0] - 2025-10-01
12+
13+
- feat: Added support for an additional grouping level for log entries by title
14+
1115
### [1.14.0] - 2025-07-02
1216

1317
- feat: Added Quick Search/Filter Box functionality for log entries

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "magento-log-viewer",
33
"displayName": "Magento Log Viewer",
44
"description": "A Visual Studio Code extension to view and manage Magento log files.",
5-
"version": "1.14.0",
5+
"version": "1.15.0",
66
"publisher": "MathiasElle",
77
"icon": "resources/logo.png",
88
"repository": {

src/logViewer.ts

Lines changed: 124 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -219,41 +219,60 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
219219

220220
return Array.from(groupedByType.entries()).map(([level, entries]) => {
221221
if (this.groupByMessage) {
222-
const groupedByMessage = new Map<string, { line: string, lineNumber: number }[]>();
223-
224-
entries.forEach(entry => {
225-
// Apply additional search filtering on message level
226-
if (this.matchesSearchTerm(entry.message) || this.matchesSearchTerm(entry.line)) {
227-
const messageGroup = groupedByMessage.get(entry.message) || [];
228-
messageGroup.push({ line: entry.line, lineNumber: entry.lineNumber });
229-
groupedByMessage.set(entry.message, messageGroup);
222+
// Erste Gruppierung nach Themen (z.B. "Broken reference")
223+
const groupedByTopic = this.groupByTopics(entries);
224+
225+
const topicGroups = Array.from(groupedByTopic.entries()).map(([topic, topicEntries]) => {
226+
// Zweite Gruppierung nach spezifischen Nachrichten innerhalb des Themas
227+
const groupedByMessage = new Map<string, { line: string, lineNumber: number }[]>();
228+
229+
topicEntries.forEach(entry => {
230+
// Apply additional search filtering on message level
231+
if (this.matchesSearchTerm(entry.message) || this.matchesSearchTerm(entry.line)) {
232+
const messageGroup = groupedByMessage.get(entry.message) || [];
233+
messageGroup.push({ line: entry.line, lineNumber: entry.lineNumber });
234+
groupedByMessage.set(entry.message, messageGroup);
235+
}
236+
});
237+
238+
const messageGroups = Array.from(groupedByMessage.entries()).map(([message, messageEntries]) => {
239+
const count = messageEntries.length;
240+
const label = `${message} (${count})`;
241+
return new LogItem(label, vscode.TreeItemCollapsibleState.Collapsed, undefined,
242+
messageEntries.map(entry => {
243+
const lineNumber = (entry.lineNumber + 1).toString().padStart(2, '0');
244+
// Format the timestamp in the log entry
245+
const formattedLine = formatTimestamp(entry.line);
246+
return new LogItem(
247+
`Line ${lineNumber}: ${formattedLine}`,
248+
vscode.TreeItemCollapsibleState.None,
249+
{
250+
command: 'magento-log-viewer.openFileAtLine',
251+
title: 'Open Log File at Line',
252+
arguments: [filePath, entry.lineNumber]
253+
}
254+
);
255+
}).sort((a, b) => a.label.localeCompare(b.label)) // Sort entries alphabetically
256+
);
257+
}).sort((a, b) => a.label.localeCompare(b.label)); // Sort message groups alphabetically
258+
259+
// Erstelle Themen-Gruppe nur wenn sie Nachrichten enthält
260+
if (messageGroups.length > 0) {
261+
const topicCount = topicEntries.length;
262+
const topicLabel = topic === 'Other' ? `${topic} (${topicCount})` : `${topic} (${topicCount})`;
263+
return new LogItem(topicLabel, vscode.TreeItemCollapsibleState.Collapsed, undefined, messageGroups);
230264
}
265+
return null;
266+
}).filter((item): item is LogItem => item !== null).sort((a, b) => {
267+
// "Other" immer am Ende
268+
if (a.label.startsWith('Other')) { return 1; }
269+
if (b.label.startsWith('Other')) { return -1; }
270+
return a.label.localeCompare(b.label);
231271
});
232272

233-
const messageGroups = Array.from(groupedByMessage.entries()).map(([message, messageEntries]) => {
234-
const count = messageEntries.length;
235-
const label = `${message} (${count})`;
236-
return new LogItem(label, vscode.TreeItemCollapsibleState.Collapsed, undefined,
237-
messageEntries.map(entry => {
238-
const lineNumber = (entry.lineNumber + 1).toString().padStart(2, '0');
239-
// Format the timestamp in the log entry
240-
const formattedLine = formatTimestamp(entry.line);
241-
return new LogItem(
242-
`Line ${lineNumber}: ${formattedLine}`,
243-
vscode.TreeItemCollapsibleState.None,
244-
{
245-
command: 'magento-log-viewer.openFileAtLine',
246-
title: 'Open Log File at Line',
247-
arguments: [filePath, entry.lineNumber]
248-
}
249-
);
250-
}).sort((a, b) => a.label.localeCompare(b.label)) // Sort entries alphabetically
251-
);
252-
}).sort((a, b) => a.label.localeCompare(b.label)); // Sort message groups alphabetically
253-
254273
// Only add log level if it has matching entries after filtering
255-
if (messageGroups.length > 0) {
256-
const logFile = new LogItem(`${level} (${entries.length}, grouped)`, vscode.TreeItemCollapsibleState.Collapsed, undefined, messageGroups);
274+
if (topicGroups.length > 0) {
275+
const logFile = new LogItem(`${level} (${entries.length}, grouped)`, vscode.TreeItemCollapsibleState.Collapsed, undefined, topicGroups);
257276
logFile.iconPath = getIconForLogLevel(level);
258277
return logFile;
259278
}
@@ -343,6 +362,80 @@ export class LogViewerProvider implements vscode.TreeDataProvider<LogItem>, vsco
343362
LogViewerProvider.statusBarItem.text = `Magento Log-Entries: ${totalEntries}${searchInfo}`;
344363
}
345364

365+
/**
366+
* Gruppiert Log-Einträge nach wiederkehrenden Themen
367+
*/
368+
private groupByTopics(entries: { message: string, line: string, lineNumber: number }[]): Map<string, { message: string, line: string, lineNumber: number }[]> {
369+
const groupedByTopic = new Map<string, { message: string, line: string, lineNumber: number }[]>();
370+
371+
entries.forEach(entry => {
372+
let assigned = false;
373+
374+
// Erste Priorität: Dynamische Erkennung von Themen im Format "[Thema]:"
375+
const dynamicTopicMatch = entry.message.match(/^([^:]+):/);
376+
if (dynamicTopicMatch) {
377+
const topic = dynamicTopicMatch[1].trim();
378+
const topicEntries = groupedByTopic.get(topic) || [];
379+
topicEntries.push(entry);
380+
groupedByTopic.set(topic, topicEntries);
381+
assigned = true;
382+
}
383+
384+
// Fallback: Statische Themen-Muster für Nachrichten ohne ":" Format
385+
if (!assigned) {
386+
const fallbackPatterns = [
387+
{ pattern: /database|sql|transaction/i, topic: 'Database' },
388+
{ pattern: /cache|redis|varnish/i, topic: 'Cache' },
389+
{ pattern: /session/i, topic: 'Session' },
390+
{ pattern: /payment/i, topic: 'Payment' },
391+
{ pattern: /checkout/i, topic: 'Checkout' },
392+
{ pattern: /catalog/i, topic: 'Catalog' },
393+
{ pattern: /customer/i, topic: 'Customer' },
394+
{ pattern: /order/i, topic: 'Order' },
395+
{ pattern: /shipping/i, topic: 'Shipping' },
396+
{ pattern: /tax/i, topic: 'Tax' },
397+
{ pattern: /inventory/i, topic: 'Inventory' },
398+
{ pattern: /indexer/i, topic: 'Indexer' },
399+
{ pattern: /cron/i, topic: 'Cron' },
400+
{ pattern: /email|newsletter/i, topic: 'Email' },
401+
{ pattern: /search|algolia|elasticsearch/i, topic: 'Search' },
402+
{ pattern: /api|graphql|rest|soap/i, topic: 'API' },
403+
{ pattern: /admin/i, topic: 'Admin' },
404+
{ pattern: /frontend|backend/i, topic: 'Frontend/Backend' },
405+
{ pattern: /theme|layout|template|block|widget/i, topic: 'Theme/Layout' },
406+
{ pattern: /module|plugin|observer|event/i, topic: 'Module/Plugin' },
407+
{ pattern: /url|rewrite/i, topic: 'URL' },
408+
{ pattern: /media|image|upload/i, topic: 'Media' },
409+
{ pattern: /import|export/i, topic: 'Import/Export' },
410+
{ pattern: /translation|locale/i, topic: 'Translation' },
411+
{ pattern: /store|website|scope/i, topic: 'Store' },
412+
{ pattern: /config/i, topic: 'Configuration' },
413+
{ pattern: /memory|timeout|performance/i, topic: 'Performance' },
414+
{ pattern: /security|authentication|authorization|permission|access|login|logout/i, topic: 'Security' }
415+
];
416+
417+
for (const { pattern, topic } of fallbackPatterns) {
418+
if (pattern.test(entry.message)) {
419+
const topicEntries = groupedByTopic.get(topic) || [];
420+
topicEntries.push(entry);
421+
groupedByTopic.set(topic, topicEntries);
422+
assigned = true;
423+
break;
424+
}
425+
}
426+
}
427+
428+
// Falls kein Thema gefunden wurde, füge zu "Other" hinzu
429+
if (!assigned) {
430+
const otherEntries = groupedByTopic.get('Other') || [];
431+
otherEntries.push(entry);
432+
groupedByTopic.set('Other', otherEntries);
433+
}
434+
});
435+
436+
return groupedByTopic;
437+
}
438+
346439
dispose() {
347440
this._onDidChangeTreeData.dispose();
348441
if (LogViewerProvider.statusBarItem) {

0 commit comments

Comments
 (0)