@@ -8,7 +8,12 @@ public static class LogLineClassifier
88 private static readonly TimeSpan RegexTimeout = TimeSpan . FromMilliseconds ( 250 ) ;
99
1010 private static readonly Regex ChatOrPlayerEventRegex = new (
11- @"^\[\d{2}:\d{2}:\d{2}\sINFO\]:\s(?:<[^>]+>\s.*|[^\s:]+\s(?:joined|left) the game|\[[^\]]+\]\s.*)$" ,
11+ @"^\[.*?\](?:\s*\[.*?\])*:\s*(?:<[^>]+>\s.*|[^\s:]+\s(?:joined|left) the game)$" ,
12+ RegexOptions . Compiled | RegexOptions . CultureInvariant ,
13+ RegexTimeout ) ;
14+
15+ private static readonly Regex LogPrefixRegex = new (
16+ @"^\[.*?\](?:\s*\[.*?\])*:\s*" ,
1217 RegexOptions . Compiled | RegexOptions . CultureInvariant ,
1318 RegexTimeout ) ;
1419
@@ -19,48 +24,62 @@ public static LogLevel Classify(string? text, LogLevel previousLevel)
1924 return LogLevel . Info ;
2025 }
2126
22- if ( ContainsAny ( text , "/ERROR]" , "[ERROR]" , " ERROR]" , "Exception" , "Fatal" , "FATAL" , "Error:" , " SEVERE" , " CRITICAL" ) )
27+ string trimmed = text . TrimStart ( ) ;
28+ if ( trimmed . StartsWith ( "at " , StringComparison . Ordinal ) ||
29+ trimmed . StartsWith ( "..." , StringComparison . Ordinal ) ||
30+ text . Contains ( "Caused by:" , StringComparison . Ordinal ) )
2331 {
24- return LogLevel . Error ;
32+ return previousLevel ;
2533 }
2634
27- if ( ContainsAny ( text , "/WARN]" , "[WARN]" , " WARN]" , " WARN" , "Warning" , "WARNING" , "****" , "***" ) )
35+ if ( text . StartsWith ( "> " , StringComparison . Ordinal ) )
2836 {
29- return LogLevel . Warn ;
37+ return LogLevel . System ;
3038 }
3139
32- if ( ContainsAny ( text , "/DEBUG]" , "[DEBUG]" , " DEBUG]" ) )
40+ if ( IsChatOrPlayerEvent ( text ) )
3341 {
34- return LogLevel . Debug ;
42+ return LogLevel . Chat ;
3543 }
3644
37- if ( ContainsAny ( text , "/TRACE]" , "[TRACE]" , " TRACE]" ) )
45+ string searchTarget = text . ToUpperInvariant ( ) ;
46+ try
3847 {
39- return LogLevel . Trace ;
48+ Match prefixMatch = LogPrefixRegex . Match ( text ) ;
49+ if ( prefixMatch . Success )
50+ {
51+ searchTarget = prefixMatch . Value . ToUpperInvariant ( ) ;
52+ }
53+ }
54+ catch ( RegexMatchTimeoutException )
55+ {
56+ // Fallback to full text if regex times out
4057 }
4158
42- if ( text . Contains ( "Done (" , StringComparison . Ordinal ) ||
43- text . Contains ( "Server started" , StringComparison . OrdinalIgnoreCase ) )
59+ if ( ContainsAny ( searchTarget , "ERROR" , "FATAL" , "SEVERE" , "CRITICAL" , "EXCEPTION" ) )
4460 {
45- return LogLevel . Info ;
61+ return LogLevel . Error ;
4662 }
4763
48- if ( IsChatOrPlayerEvent ( text ) )
64+ if ( ContainsAny ( searchTarget , "WARN" , "****" , "***" ) )
4965 {
50- return LogLevel . Chat ;
66+ return LogLevel . Warn ;
5167 }
5268
53- string trimmed = text . TrimStart ( ) ;
54- if ( trimmed . StartsWith ( "at " , StringComparison . Ordinal ) ||
55- trimmed . StartsWith ( "..." , StringComparison . Ordinal ) ||
56- text . Contains ( "Caused by:" , StringComparison . Ordinal ) )
69+ if ( ContainsAny ( searchTarget , "DEBUG" ) )
5770 {
58- return previousLevel ;
71+ return LogLevel . Debug ;
5972 }
6073
61- if ( text . StartsWith ( "> " , StringComparison . Ordinal ) )
74+ if ( ContainsAny ( searchTarget , "TRACE" ) )
6275 {
63- return LogLevel . System ;
76+ return LogLevel . Trace ;
77+ }
78+
79+ if ( text . Contains ( "Done (" , StringComparison . Ordinal ) ||
80+ text . Contains ( "Server started" , StringComparison . OrdinalIgnoreCase ) )
81+ {
82+ return LogLevel . Info ; // Could add a Success level later, but Info is fine
6483 }
6584
6685 return LogLevel . Info ;
0 commit comments