@@ -13,6 +13,7 @@ import { LineNumbersService } from '../../../_services/line-numbers.service';
1313import { LiveUpdatesService } from '../../../_services/live-updates.service' ;
1414import { LogGroup , LogGroupingService } from '../../../_services/log-grouping.service' ;
1515import { LogApiService } from '../../../_services/log.api' ;
16+ import { LogProcessingService } from '../../../_services/log-processing.service' ;
1617import { MemoryManagementService } from '../../../_services/memory-management.service' ;
1718import { Log , SignalRService } from '../../../_services/signalr.service' ;
1819import { 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 ( I N F O | D E B U G | E R R O R | W A R N | T R A C E | F A T A L ) \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 ( I N F O | D E B U G | E R R O R | W A R N | T R A C E | F A T A L | I N F | D B G | E R R | W R N | T R C | F T L ) \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