Skip to content

Commit 09f67c7

Browse files
author
Timothy Dodd
committed
feat: integrate LogProcessingService for log transformation and filtering
1 parent b5229ed commit 09f67c7

5 files changed

Lines changed: 275 additions & 199 deletions

File tree

src/LogMkWeb/src/app/_pages/main-log-page/log-filter-controls/log-filter-controls.component.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { LiveUpdatesService } from '../../../_services/live-updates.service';
1515
import { LogGroupingService } from '../../../_services/log-grouping.service';
1616
import { LineNumbersService } from '../../../_services/line-numbers.service';
1717
import { LogApiService } from '../../../_services/log.api';
18+
import { LogProcessingService } from '../../../_services/log-processing.service';
1819
import { TimestampService } from '../../../_services/timestamp.service';
1920
import { ViewModeService } from '../../../_services/view-mode.service';
2021
import { AudioService } from '../../../_services/audio.service';
@@ -365,6 +366,7 @@ import { LogFilterState } from '../_services/log-filter-state';
365366
export class LogFilterControlsComponent {
366367
logService = inject(LogApiService);
367368
logFilterState = inject(LogFilterState);
369+
logProcessingService = inject(LogProcessingService);
368370
exportService = inject(ExportService);
369371
toastr = inject(ToastrService);
370372
elementRef = inject(ElementRef);
@@ -664,7 +666,7 @@ export class LogFilterControlsComponent {
664666
// Temporary method to generate mock logs for export demo
665667
// In production, this would come from your actual log data source
666668
private generateMockExportLogs() {
667-
const mockLogs = [];
669+
const rawMockLogs = [];
668670
const deployments = ['production', 'staging', 'development'];
669671
const pods = ['web-app-123', 'api-server-456', 'database-789'];
670672
const levels = ['Information', 'Warning', 'Error', 'Debug'];
@@ -677,21 +679,28 @@ export class LogFilterControlsComponent {
677679
'Debug: Processing batch job #1234'
678680
];
679681

682+
// Generate raw logs
680683
for (let i = 0; i < 50; i++) {
681684
const timestamp = new Date(Date.now() - Math.random() * 86400000 * 7); // Last 7 days
682-
mockLogs.push({
685+
const pod = pods[Math.floor(Math.random() * pods.length)];
686+
const level = levels[Math.floor(Math.random() * levels.length)];
687+
const message = messages[Math.floor(Math.random() * messages.length)];
688+
689+
rawMockLogs.push({
683690
id: i + 1,
684691
deployment: deployments[Math.floor(Math.random() * deployments.length)],
685-
timeStamp: timestamp, // Keep as Date object
686-
pod: pods[Math.floor(Math.random() * pods.length)],
687-
logLevel: levels[Math.floor(Math.random() * levels.length)],
688-
line: `${messages[Math.floor(Math.random() * messages.length)]} - ID: ${i + 1}`,
689-
view: `${messages[Math.floor(Math.random() * messages.length)]} - ID: ${i + 1}`,
690-
podColor: '#00eaff'
692+
timeStamp: timestamp,
693+
pod: pod,
694+
logLevel: level,
695+
line: `[${timestamp.toISOString()}] ${level}: ${message} - ID: ${i + 1}`, // Include timestamp in line
696+
view: '', // Will be set by transformation
697+
podColor: '', // Will be set by transformation
698+
sequenceNumber: i + 1
691699
});
692700
}
693701

694-
return mockLogs;
702+
// Use service to transform logs with proper pod colors and cleaned lines
703+
return this.logProcessingService.transformLogs(rawMockLogs as any);
695704
}
696705

697706
// Memory management methods

src/LogMkWeb/src/app/_pages/main-log-page/log-viewport/log-viewport.component.ts

Lines changed: 44 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { LineNumbersService } from '../../../_services/line-numbers.service';
1313
import { LiveUpdatesService } from '../../../_services/live-updates.service';
1414
import { LogGroup, LogGroupingService } from '../../../_services/log-grouping.service';
1515
import { LogApiService } from '../../../_services/log.api';
16+
import { LogProcessingService } from '../../../_services/log-processing.service';
1617
import { MemoryManagementService } from '../../../_services/memory-management.service';
1718
import { Log, SignalRService } from '../../../_services/signalr.service';
1819
import { ViewModeService } from '../../../_services/view-mode.service';
@@ -190,6 +191,7 @@ export class LogViewportComponent {
190191
destroyRef = inject(DestroyRef);
191192
logApi = inject(LogApiService);
192193
signalRService = inject(SignalRService);
194+
logProcessingService = inject(LogProcessingService);
193195
logs = signal<Log[]>([]);
194196
groupedLogs = signal<(Log | LogGroup)[]>([]);
195197
logFilterState = inject(LogFilterState);
@@ -286,59 +288,54 @@ export class LogViewportComponent {
286288
switchMap(() => {
287289
this.page++;
288290

289-
// Extract include/exclude arrays from tri-state values
290-
const triLogLevel = this.logFilterState.triStateLogLevel();
291-
const triPod = this.logFilterState.triStatePod();
292-
const includeLogLevel = triLogLevel?.included?.length ? triLogLevel.included : null;
293-
const excludeLogLevel = triLogLevel?.excluded?.length ? triLogLevel.excluded : null;
294-
const includePod = triPod?.included?.length ? triPod.included : null;
295-
const excludePod = triPod?.excluded?.length ? triPod.excluded : null;
291+
// Extract include/exclude arrays from tri-state values using service
292+
const filters = this.logProcessingService.extractTriStateFilters(
293+
this.logFilterState.triStateLogLevel(),
294+
this.logFilterState.triStatePod()
295+
);
296296

297297
const customRange = this.logFilterState.customTimeRange();
298298
if (customRange) {
299299
return this.logApi
300300
.getLogs(
301-
includeLogLevel,
302-
includePod,
301+
filters.includeLogLevel,
302+
filters.includePod,
303303
this.logFilterState.searchString(),
304304
customRange.start,
305305
customRange.end,
306306
this.page,
307307
200,
308-
excludeLogLevel,
309-
excludePod,
308+
filters.excludeLogLevel,
309+
filters.excludePod,
310310
''
311311
);
312312
} else {
313313
return this.logApi
314314
.getLogs(
315-
includeLogLevel,
316-
includePod,
315+
filters.includeLogLevel,
316+
filters.includePod,
317317
this.logFilterState.searchString(),
318318
this.logFilterState.selectedTimeRange(),
319319
undefined,
320320
this.page,
321321
200,
322-
excludeLogLevel,
323-
excludePod,
322+
filters.excludeLogLevel,
323+
filters.excludePod,
324324
''
325325
);
326326
}
327327
}),
328328
tap((z) => {
329-
const ni = (z.items ?? []).map((z) => {
330-
return {
331-
...z,
332-
podColor: getPodColor(z.pod),
333-
view: this.cleanLogLine(z.line),
334-
};
335-
});;
336-
if (ni.length === 0) return;
329+
if (!z.items || z.items.length === 0) return;
330+
331+
// Use service to process and append logs
337332
const index = this.logs().length;
338-
this.logs.update((items) => {
339-
const newItems = [...items, ...ni];
340-
// Apply memory management
341-
return this.applyMemoryManagement(newItems);
333+
this.logs.update((existingLogs) => {
334+
return this.logProcessingService.processAppendLogs(
335+
z.items ?? [],
336+
existingLogs,
337+
this.memoryManagementService.maxLogsInMemory()
338+
);
342339
});
343340
this.scrollToIndex(index);
344341
})
@@ -356,47 +353,27 @@ export class LogViewportComponent {
356353
switchMap(([search, date, custom, triLogLevel, triPod]) => {
357354
this.page = 1;
358355

359-
// Extract include/exclude arrays from tri-state values
360-
const includeLogLevel = triLogLevel?.included?.length ? triLogLevel.included : null;
361-
const excludeLogLevel = triLogLevel?.excluded?.length ? triLogLevel.excluded : null;
362-
const includePod = triPod?.included?.length ? triPod.included : null;
363-
const excludePod = triPod?.excluded?.length ? triPod.excluded : null;
356+
// Extract include/exclude arrays from tri-state values using service
357+
const filters = this.logProcessingService.extractTriStateFilters(triLogLevel, triPod);
364358

365359
if (custom) {
366-
return this.logApi.getLogs(includeLogLevel, includePod, search, custom.start, custom.end, this.page, 200, excludeLogLevel, excludePod, '');
360+
return this.logApi.getLogs(filters.includeLogLevel, filters.includePod, search, custom.start, custom.end, this.page, 200, filters.excludeLogLevel, filters.excludePod, '');
367361
} else {
368-
return this.logApi.getLogs(includeLogLevel, includePod, search, date, undefined, this.page, 200, excludeLogLevel, excludePod, '');
362+
return this.logApi.getLogs(filters.includeLogLevel, filters.includePod, search, date, undefined, this.page, 200, filters.excludeLogLevel, filters.excludePod, '');
369363
}
370364
}),
371365
tap((l) => {
372-
const items = (l.items ?? []).map((z) => {
373-
return {
374-
...z,
375-
podColor: getPodColor(z.pod),
376-
view: this.cleanLogLine(z.line),
377-
};
378-
});
379-
380-
// Exclude filtering is now handled by the backend API
381-
this.logs.set(items);
366+
// Use service to transform logs
367+
const transformedLogs = this.logProcessingService.transformLogs(l.items ?? []);
368+
this.logs.set(transformedLogs);
382369
}),
383370
takeUntilDestroyed()
384371
)
385372
.subscribe(() => {
386373
this.startSignalR();
387374
});
388375
}
389-
390-
cleanLogLine(line: string): string {
391-
// Matches:
392-
// [2025-04-15 17:52:04] INFO ...
393-
// 04:09:34 fail: ...
394-
// 2025-04-17T00:15:51Z WRN ...
395-
return line.replace(
396-
/^(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]\s+\b(INFO|DEBUG|ERROR|WARN|TRACE|FATAL)\b\s*|^\d{2}:\d{2}:\d{2}\s+\w+:\s*|^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\s+\b(INFO|DEBUG|ERROR|WARN|TRACE|FATAL|INF|DBG|ERR|WRN|TRC|FTL)\b\s*)/,
397-
''
398-
);
399-
}
376+
400377
monitoring = false;
401378
private startSignalR() {
402379
if (this.monitoring) return;
@@ -412,6 +389,7 @@ export class LogViewportComponent {
412389
const date = this.logFilterState.selectedTimeRange();
413390
const customRange = this.logFilterState.customTimeRange();
414391

392+
// Filter logs based on current filter state
415393
const filteredLogs = logs.filter((log) => {
416394
if (!log) return false;
417395

@@ -432,12 +410,6 @@ export class LogViewportComponent {
432410
(!pod || pod.includes(log.pod)) &&
433411
timeMatches
434412
);
435-
}).map(z=>{
436-
return {
437-
...z,
438-
podColor: getPodColor(z.pod),
439-
view: this.cleanLogLine(z.line),
440-
};
441413
});
442414

443415
// Apply exclude filters to real-time logs (still needed for SignalR since it sends all logs)
@@ -458,33 +430,18 @@ export class LogViewportComponent {
458430
this.audioService.playAlert(log.logLevel);
459431
});
460432

433+
// Use service to process new logs with transformation, sorting, deduplication, and memory management
461434
this.logs.update((existingLogs) => {
462-
// Sort new logs the same way as backend: TimeStamp DESC, SequenceNumber ASC
463-
const sortedNewLogs = allLogsToProcess.sort((a, b) => {
464-
const timeDiff = new Date(b.timeStamp).getTime() - new Date(a.timeStamp).getTime();
465-
if (timeDiff !== 0) return timeDiff; // Newer first (DESC)
466-
return a.sequenceNumber - b.sequenceNumber; // Lower sequence first (ASC)
467-
});
468-
469-
// Remove duplicates - filter out any new logs that are older than or equal to the newest existing log
470-
let filteredNewLogs = sortedNewLogs;
471-
if (existingLogs.length > 0) {
472-
const newestExisting = existingLogs[0];
473-
const newestTimestamp = new Date(newestExisting.timeStamp).getTime();
474-
475-
filteredNewLogs = sortedNewLogs.filter(log => {
476-
const logTimestamp = new Date(log.timeStamp).getTime();
477-
// Keep logs that are newer, or same timestamp but different ID
478-
return logTimestamp > newestTimestamp ||
479-
(logTimestamp === newestTimestamp && log.id !== newestExisting.id);
480-
});
481-
}
482-
483-
// Prepend new logs to the beginning (they're already sorted newest first)
484-
const combinedLogs = [...filteredNewLogs, ...existingLogs];
485-
486-
// Apply memory management
487-
return this.applyMemoryManagement(combinedLogs);
435+
return this.logProcessingService.processNewLogs(
436+
allLogsToProcess,
437+
existingLogs,
438+
{
439+
transform: true,
440+
sort: true,
441+
deduplicate: true,
442+
maxLogs: this.memoryManagementService.maxLogsInMemory()
443+
}
444+
);
488445
});
489446

490447
// Clear queued logs after processing
@@ -775,105 +732,4 @@ export class LogViewportComponent {
775732
});
776733
}
777734

778-
private applyMemoryManagement(logs: Log[]): Log[] {
779-
const maxLogs = this.memoryManagementService.maxLogsInMemory();
780-
781-
// Simple limit enforcement - keep the most recent logs
782-
if (logs.length > maxLogs) {
783-
const overage = logs.length - maxLogs;
784-
console.log(`Memory management: Removing ${overage} logs. Total: ${logs.length} -> ${logs.length - overage}`);
785-
return logs.slice(-maxLogs); // Keep the most recent logs
786-
}
787-
788-
// Check if auto-cleanup is needed
789-
if (this.memoryManagementService.shouldCleanup()) {
790-
const targetCount = this.memoryManagementService.getTargetLogCountAfterCleanup();
791-
if (targetCount < logs.length) {
792-
const removedCount = logs.length - targetCount;
793-
console.log(`Auto-cleanup: Removing ${removedCount} logs. Total: ${logs.length} -> ${targetCount}`);
794-
return logs.slice(-targetCount); // Keep the most recent logs
795-
}
796-
}
797-
798-
return logs;
799-
}
800-
801-
}
802-
const podColorCache = new Map<string, string>();
803-
804-
export function getPodColor(podName: string): string {
805-
if (podColorCache.has(podName)) {
806-
return podColorCache.get(podName)!;
807-
}
808-
809-
// Generate multiple hash values for better distribution with overflow protection
810-
let hash1 = 0;
811-
let hash2 = 0;
812-
for (let i = 0; i < podName.length; i++) {
813-
const char = podName.charCodeAt(i);
814-
hash1 = (char + ((hash1 << 5) - hash1)) >>> 0; // Use unsigned right shift to keep 32-bit
815-
hash2 = (char + ((hash2 << 3) - hash2) + i) >>> 0;
816-
}
817-
818-
// Create distinct color palette with wider ranges and better separation
819-
const colorSchemes = [
820-
// Bright, distinct color ranges for better visibility
821-
{ r: [220, 255], g: [50, 120], b: [50, 120] }, // Warm reds/oranges
822-
{ r: [50, 120], g: [220, 255], b: [50, 120] }, // Bright greens
823-
{ r: [50, 120], g: [50, 120], b: [220, 255] }, // Bright blues
824-
{ r: [220, 255], g: [220, 255], b: [50, 120] }, // Bright yellows
825-
{ r: [220, 255], g: [50, 120], b: [220, 255] }, // Bright magentas
826-
{ r: [50, 120], g: [220, 255], b: [220, 255] }, // Bright cyans
827-
{ r: [180, 220], g: [140, 200], b: [50, 100] }, // Orange variations
828-
{ r: [140, 200], g: [50, 100], b: [180, 220] }, // Purple variations
829-
{ r: [50, 100], g: [180, 220], b: [140, 200] }, // Teal variations
830-
{ r: [240, 255], g: [160, 200], b: [160, 200] }, // Light corals
831-
{ r: [160, 200], g: [240, 255], b: [160, 200] }, // Light greens
832-
{ r: [160, 200], g: [160, 200], b: [240, 255] } // Light blues
833-
];
834-
835-
// Select color scheme based on first hash
836-
const schemeIndex = hash1 % colorSchemes.length;
837-
const scheme = colorSchemes[schemeIndex];
838-
839-
// Generate RGB values within the selected scheme's ranges with safety checks
840-
const rRange = Math.max(1, scheme.r[1] - scheme.r[0]);
841-
const gRange = Math.max(1, scheme.g[1] - scheme.g[0]);
842-
const bRange = Math.max(1, scheme.b[1] - scheme.b[0]);
843-
844-
const r = scheme.r[0] + (hash1 % rRange);
845-
const g = scheme.g[0] + (hash2 % gRange);
846-
const b = scheme.b[0] + (Math.abs(hash1 ^ hash2) % bRange);
847-
848-
// Ensure minimum contrast for readability
849-
let finalR = clamp(r);
850-
let finalG = clamp(g);
851-
let finalB = clamp(b);
852-
853-
// Boost colors that are too dim for dark theme
854-
if (getLuminance(finalR, finalG, finalB) < 0.3) {
855-
finalR = clamp(finalR + 60);
856-
finalG = clamp(finalG + 60);
857-
finalB = clamp(finalB + 60);
858-
}
859-
860-
const color = `rgb(${finalR}, ${finalG}, ${finalB})`;
861-
podColorCache.set(podName, color);
862-
return color;
863-
}
864-
865-
function clamp(v: number): number {
866-
return Math.max(0, Math.min(255, v));
867-
}
868-
869-
// Relative luminance calculation (WCAG contrast model)
870-
function getLuminance(r: number, g: number, b: number): number {
871-
const toLinear = (c: number) => {
872-
const sRGB = c / 255;
873-
return sRGB <= 0.03928
874-
? sRGB / 12.92
875-
: Math.pow((sRGB + 0.055) / 1.055, 2.4);
876-
};
877-
const l = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
878-
return l;
879735
}

0 commit comments

Comments
 (0)