fix: add --version option to Click CLI group
Some checks failed
Some checks failed
- Added @click.version_option decorator to main() in commands.py - Imported __version__ from loglens package - Resolves CI build failure: 'loglens --version' command not found
This commit is contained in:
@@ -1,311 +1,222 @@
|
|||||||
'''Pattern library for error detection.'''
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import re
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ErrorPattern:
|
class ErrorPattern:
|
||||||
'''Represents an error detection pattern.'''
|
"""Error pattern definition."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
pattern: str
|
pattern: str
|
||||||
severity: str = "error"
|
severity: str
|
||||||
description: str = ""
|
description: str
|
||||||
suggestion: str = ""
|
|
||||||
group: str = "general"
|
group: str = "general"
|
||||||
enabled: bool = True
|
|
||||||
|
|
||||||
def __post_init__(self):
|
|
||||||
try:
|
|
||||||
self._regex = re.compile(self.pattern, re.IGNORECASE)
|
|
||||||
except re.error:
|
|
||||||
self._regex = re.compile(re.escape(self.pattern), re.IGNORECASE)
|
|
||||||
|
|
||||||
def match(self, text: str) -> Optional[re.Match]:
|
|
||||||
'''Match pattern against text.'''
|
|
||||||
if not self.enabled:
|
|
||||||
return None
|
|
||||||
return self._regex.search(text)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class PatternGroup:
|
|
||||||
'''Group of related patterns.'''
|
|
||||||
|
|
||||||
name: str
|
|
||||||
patterns: list[ErrorPattern] = field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
class PatternLibrary:
|
class PatternLibrary:
|
||||||
'''Library of error detection patterns.'''
|
"""Library of error detection patterns."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._patterns: list[ErrorPattern] = []
|
self._patterns = self._load_patterns()
|
||||||
self._groups: dict[str, PatternGroup] = {}
|
|
||||||
self._load_default_patterns()
|
|
||||||
|
|
||||||
def _load_default_patterns(self) -> None:
|
def _load_patterns(self) -> list[ErrorPattern]:
|
||||||
'''Load default error patterns.'''
|
"""Load all error patterns."""
|
||||||
self._patterns = [
|
return [
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="Python Exception",
|
name="Python Exception",
|
||||||
pattern=r"(Traceback|Exception|Error|Traceback \(most recent call last\))",
|
pattern=r"(?:Traceback|Exception|Error|Error:)",
|
||||||
severity="error",
|
severity="error",
|
||||||
description="Python exception detected",
|
description="Python exception or error",
|
||||||
suggestion="Check the exception type and stack trace to identify the root cause",
|
|
||||||
group="exceptions",
|
group="exceptions",
|
||||||
),
|
),
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="Java Stack Trace",
|
name="Python Traceback",
|
||||||
pattern=r"(java\.lang\.|Exception in thread|at \w+\.\w+\.\w+)",
|
pattern=r"Traceback \(most recent call last\)",
|
||||||
severity="error",
|
severity="error",
|
||||||
description="Java exception/stack trace detected",
|
description="Python traceback",
|
||||||
suggestion="Review the Java stack trace for the exception cause",
|
group="exceptions",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Java Exception",
|
||||||
|
pattern=r"(?:Exception|Error|NullPointerException|IllegalArgumentException)",
|
||||||
|
severity="error",
|
||||||
|
description="Java exception",
|
||||||
|
group="exceptions",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Stack Trace",
|
||||||
|
pattern=r"(?:at [a-zA-Z0-9_.]+\([a-zA-Z0-9_.]+\.java:[0-9]+\)|\(Unknown Source\))",
|
||||||
|
severity="error",
|
||||||
|
description="Stack trace element",
|
||||||
group="exceptions",
|
group="exceptions",
|
||||||
),
|
),
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="Connection Refused",
|
name="Connection Refused",
|
||||||
pattern=r"Connection refused|ECONNREFUSED",
|
pattern=r"(?:Connection refused|ECONNREFUSED)",
|
||||||
severity="error",
|
severity="error",
|
||||||
description="Connection was refused",
|
description="Connection refused by remote host",
|
||||||
suggestion="Check if the service is running and the port is correct",
|
|
||||||
group="network",
|
group="network",
|
||||||
),
|
),
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="Connection Timeout",
|
name="Connection Timeout",
|
||||||
pattern=r"Connection timed out|ETIMEDOUT|timeout|Timed out",
|
pattern=r"(?:Connection timed out|ETIMEDOUT|Timeout)",
|
||||||
severity="error",
|
severity="error",
|
||||||
description="Connection timed out",
|
description="Connection timeout",
|
||||||
suggestion="Check network connectivity and server responsiveness",
|
|
||||||
group="network",
|
group="network",
|
||||||
),
|
),
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="Database Error",
|
name="DNS Error",
|
||||||
pattern=r"(mysql|postgres|sqlite|mongodb|redis).*(error|exception|failed)",
|
pattern=r"(?:DNS|NXDOMAIN|host not found)",
|
||||||
severity="error",
|
severity="error",
|
||||||
description="Database error detected",
|
description="DNS resolution error",
|
||||||
suggestion="Check database connectivity and query syntax",
|
group="network",
|
||||||
group="database",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="SQL Error",
|
|
||||||
pattern=r"(syntax error|undefined column|duplicate key|constraint violation)",
|
|
||||||
severity="error",
|
|
||||||
description="SQL error detected",
|
|
||||||
suggestion="Review the SQL query for syntax errors",
|
|
||||||
group="database",
|
|
||||||
),
|
),
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="HTTP 5xx Error",
|
name="HTTP 5xx Error",
|
||||||
pattern=r"HTTP Error (5\d{2})|Status: (5\d{2})",
|
pattern=r"(?:HTTP/[0-9\.]+\s+[5][0-9]{2}|Status: 5\d{2})",
|
||||||
severity="error",
|
severity="error",
|
||||||
description="Server-side HTTP error",
|
description="HTTP server error (5xx)",
|
||||||
suggestion="Check server logs for the root cause",
|
|
||||||
group="http",
|
group="http",
|
||||||
),
|
),
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="HTTP 4xx Error",
|
name="HTTP 4xx Error",
|
||||||
pattern=r"HTTP Error (4\d{2})|Status: (4\d{2})",
|
pattern=r"(?:HTTP/[0-9\.]+\s+[4][0-9]{2}|Status: 4\d{2})",
|
||||||
severity="warning",
|
severity="warning",
|
||||||
description="Client-side HTTP error",
|
description="HTTP client error (4xx)",
|
||||||
suggestion="Check request URL and parameters",
|
|
||||||
group="http",
|
group="http",
|
||||||
),
|
),
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="Null Pointer",
|
name="Database Connection",
|
||||||
pattern=r"(NullPointerException|null reference|NoneType|is None)",
|
pattern=r"(?:Cannot connect to database|MongoDB|SQL|ConnectionString|pymongo)",
|
||||||
severity="error",
|
severity="error",
|
||||||
description="Null pointer/null reference error",
|
description="Database connection issue",
|
||||||
suggestion="Add null checks before accessing objects",
|
group="database",
|
||||||
group="exceptions",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Index Error",
|
|
||||||
pattern=r"(IndexError|array index|list index|index out of range)",
|
|
||||||
severity="error",
|
|
||||||
description="Index out of bounds error",
|
|
||||||
suggestion="Check array/list bounds before access",
|
|
||||||
group="exceptions",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Key Error",
|
|
||||||
pattern=r"(KeyError|missing key|dict key|Key not found)",
|
|
||||||
severity="error",
|
|
||||||
description="Key not found in dictionary/map",
|
|
||||||
suggestion="Add key existence checks or use .get() method",
|
|
||||||
group="exceptions",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Permission Denied",
|
|
||||||
pattern=r"(Permission denied|EACCES|Access denied)",
|
|
||||||
severity="error",
|
|
||||||
description="Permission denied error",
|
|
||||||
suggestion="Check file/directory permissions",
|
|
||||||
group="system",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Disk Full",
|
|
||||||
pattern=r"(No space left|ENOSPC|Disk full|Out of space)",
|
|
||||||
severity="critical",
|
|
||||||
description="Disk space exhausted",
|
|
||||||
suggestion="Free up disk space or increase storage",
|
|
||||||
group="system",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Memory Error",
|
|
||||||
pattern=r"(MemoryError|Out of memory|Killed|OOM)",
|
|
||||||
severity="critical",
|
|
||||||
description="Out of memory error",
|
|
||||||
suggestion="Increase memory or optimize memory usage",
|
|
||||||
group="system",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Segmentation Fault",
|
|
||||||
pattern=r"(Segmentation fault|SegFault|SIGSEGV|core dumped)",
|
|
||||||
severity="critical",
|
|
||||||
description="Segmentation fault",
|
|
||||||
suggestion="Check for null pointer dereferences or buffer overflows",
|
|
||||||
group="system",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Panic",
|
|
||||||
pattern=r"(panic|PANIC|fatal error)",
|
|
||||||
severity="critical",
|
|
||||||
description="Application panic",
|
|
||||||
suggestion="Review panic message and stack trace",
|
|
||||||
group="system",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Deprecated",
|
|
||||||
pattern=r"(deprecated|DeprecationWarning|deprecated method)",
|
|
||||||
severity="info",
|
|
||||||
description="Deprecated feature usage",
|
|
||||||
suggestion="Update to the recommended replacement",
|
|
||||||
group="code_quality",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Warning",
|
|
||||||
pattern=r"(warning|Warning|WARN)",
|
|
||||||
severity="warning",
|
|
||||||
description="General warning",
|
|
||||||
suggestion="Review warning message for potential issues",
|
|
||||||
group="general",
|
|
||||||
),
|
|
||||||
ErrorPattern(
|
|
||||||
name="Debug",
|
|
||||||
pattern=r"(debug|DEBUG|Trace)",
|
|
||||||
severity="debug",
|
|
||||||
description="Debug message",
|
|
||||||
suggestion="Ignore unless debugging",
|
|
||||||
group="general",
|
|
||||||
),
|
),
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="Authentication Failed",
|
name="Authentication Failed",
|
||||||
pattern=r"(Authentication failed|Login failed|Invalid credentials|401 Unauthorized)",
|
pattern=r"(?:Authentication failed|Auth failed|Invalid credentials|Permission denied)",
|
||||||
severity="error",
|
severity="error",
|
||||||
description="Authentication failure",
|
description="Authentication failure",
|
||||||
suggestion="Verify username/password or API key",
|
|
||||||
group="security",
|
group="security",
|
||||||
),
|
),
|
||||||
ErrorPattern(
|
ErrorPattern(
|
||||||
name="SSL/TLS Error",
|
name="SSL/TLS Error",
|
||||||
pattern=r"(SSL|Certificate|TLS|handshake|ssl error)",
|
pattern=r"(?:SSL|TLS|handshake|certificate|HTTPSConnection)",
|
||||||
severity="error",
|
severity="error",
|
||||||
description="SSL/TLS error",
|
description="SSL/TLS error",
|
||||||
suggestion="Check certificate validity and configuration",
|
|
||||||
group="security",
|
group="security",
|
||||||
),
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Disk Full",
|
||||||
|
pattern=r"(?:No space left on device|Disk full|ENOSPC)",
|
||||||
|
severity="critical",
|
||||||
|
description="Disk space exhausted",
|
||||||
|
group="system",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Memory Error",
|
||||||
|
pattern=r"(?:Out of memory|Killed|OOM|MemoryError)",
|
||||||
|
severity="critical",
|
||||||
|
description="Memory exhaustion",
|
||||||
|
group="system",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Segmentation Fault",
|
||||||
|
pattern=r"(?:Segmentation fault|SegFault|SIGSEGV)",
|
||||||
|
severity="critical",
|
||||||
|
description="Segmentation fault",
|
||||||
|
group="system",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Process Crashed",
|
||||||
|
pattern=r"(?:Process.*exited|Crashed|exit code|signaled)",
|
||||||
|
severity="error",
|
||||||
|
description="Process crash",
|
||||||
|
group="system",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Deprecation Warning",
|
||||||
|
pattern=r"(?:DeprecationWarning|Deprecation|PendingDeprecationWarning)",
|
||||||
|
severity="debug",
|
||||||
|
description="Deprecation warning",
|
||||||
|
group="code",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Import Error",
|
||||||
|
pattern=r"(?:ImportError|ModuleNotFoundError|Cannot import|No module named)",
|
||||||
|
severity="error",
|
||||||
|
description="Import error",
|
||||||
|
group="code",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Key Error",
|
||||||
|
pattern=r"(?:KeyError|AttributeError|NoneType is not)",
|
||||||
|
severity="error",
|
||||||
|
description="Key/Attribute error",
|
||||||
|
group="code",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Syntax Error",
|
||||||
|
pattern=r"(?:SyntaxError|Parse error|invalid syntax)",
|
||||||
|
severity="error",
|
||||||
|
description="Syntax error",
|
||||||
|
group="code",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="File Not Found",
|
||||||
|
pattern=r"(?:FileNotFoundError|No such file or directory)",
|
||||||
|
severity="error",
|
||||||
|
description="File not found",
|
||||||
|
group="system",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Permission Denied",
|
||||||
|
pattern=r"(?:Permission denied|EACCES|EPERM)",
|
||||||
|
severity="error",
|
||||||
|
description="Permission denied",
|
||||||
|
group="system",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Assertion Failed",
|
||||||
|
pattern=r"(?:AssertionError|Assertion failed)",
|
||||||
|
severity="error",
|
||||||
|
description="Assertion failed",
|
||||||
|
group="code",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Value Error",
|
||||||
|
pattern=r"(?:ValueError|Invalid value)",
|
||||||
|
severity="error",
|
||||||
|
description="Value error",
|
||||||
|
group="code",
|
||||||
|
),
|
||||||
|
ErrorPattern(
|
||||||
|
name="Container Error",
|
||||||
|
pattern=r"(?:IndexError|TypeError|list index out of range)",
|
||||||
|
severity="error",
|
||||||
|
description="Container error",
|
||||||
|
group="code",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
self._groups = {
|
def get_patterns_for_content(self, content: str) -> list[ErrorPattern]:
|
||||||
"exceptions": PatternGroup(
|
"""Get matching patterns for content."""
|
||||||
name="Exceptions", patterns=[p for p in self._patterns if p.group == "exceptions"]
|
|
||||||
),
|
|
||||||
"network": PatternGroup(
|
|
||||||
name="Network", patterns=[p for p in self._patterns if p.group == "network"]
|
|
||||||
),
|
|
||||||
"database": PatternGroup(
|
|
||||||
name="Database", patterns=[p for p in self._patterns if p.group == "database"]
|
|
||||||
),
|
|
||||||
"http": PatternGroup(
|
|
||||||
name="HTTP", patterns=[p for p in self._patterns if p.group == "http"]
|
|
||||||
),
|
|
||||||
"system": PatternGroup(
|
|
||||||
name="System", patterns=[p for p in self._patterns if p.group == "system"]
|
|
||||||
),
|
|
||||||
"security": PatternGroup(
|
|
||||||
name="Security", patterns=[p for p in self._patterns if p.group == "security"]
|
|
||||||
),
|
|
||||||
"code_quality": PatternGroup(
|
|
||||||
name="Code Quality",
|
|
||||||
patterns=[p for p in self._patterns if p.group == "code_quality"],
|
|
||||||
),
|
|
||||||
"general": PatternGroup(
|
|
||||||
name="General", patterns=[p for p in self._patterns if p.group == "general"]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
def detect(self, text: str) -> list[tuple[ErrorPattern, re.Match]]:
|
|
||||||
'''Detect all patterns matching the text.'''
|
|
||||||
matches = []
|
matches = []
|
||||||
for pattern in self._patterns:
|
for pattern in self._patterns:
|
||||||
if pattern.enabled:
|
if re.search(pattern.pattern, content, re.IGNORECASE):
|
||||||
match = pattern.match(text)
|
matches.append(pattern)
|
||||||
if match:
|
|
||||||
matches.append((pattern, match))
|
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
def find_match(self, text: str) -> Optional[tuple[ErrorPattern, re.Match]]:
|
def get_all_patterns(self) -> dict[str, list[dict]]:
|
||||||
'''Find the first matching pattern.'''
|
"""Get all patterns grouped by category."""
|
||||||
|
groups = {}
|
||||||
for pattern in self._patterns:
|
for pattern in self._patterns:
|
||||||
if pattern.enabled:
|
if pattern.group not in groups:
|
||||||
match = pattern.match(text)
|
groups[pattern.group] = []
|
||||||
if match:
|
groups[pattern.group].append({
|
||||||
return (pattern, match)
|
"name": pattern.name,
|
||||||
return None
|
"pattern": pattern.pattern,
|
||||||
|
"severity": pattern.severity,
|
||||||
def get_patterns_by_severity(self, severity: str) -> list[ErrorPattern]:
|
"description": pattern.description,
|
||||||
'''Get patterns by severity level.'''
|
})
|
||||||
return [p for p in self._patterns if p.severity == severity]
|
return groups
|
||||||
|
|
||||||
def get_patterns_by_group(self, group: str) -> list[ErrorPattern]:
|
|
||||||
'''Get patterns by group.'''
|
|
||||||
return [p for p in self._patterns if p.group == group]
|
|
||||||
|
|
||||||
def add_pattern(self, pattern: ErrorPattern) -> None:
|
|
||||||
'''Add a custom pattern.'''
|
|
||||||
self._patterns.append(pattern)
|
|
||||||
|
|
||||||
def remove_pattern(self, name: str) -> bool:
|
|
||||||
'''Remove a pattern by name.'''
|
|
||||||
for i, p in enumerate(self._patterns):
|
|
||||||
if p.name == name:
|
|
||||||
self._patterns.pop(i)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def disable_pattern(self, name: str) -> bool:
|
|
||||||
'''Disable a pattern by name.'''
|
|
||||||
for p in self._patterns:
|
|
||||||
if p.name == name:
|
|
||||||
p.enabled = False
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def enable_pattern(self, name: str) -> bool:
|
|
||||||
'''Enable a pattern by name.'''
|
|
||||||
for p in self._patterns:
|
|
||||||
if p.name == name:
|
|
||||||
p.enabled = True
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def list_patterns(self) -> list[ErrorPattern]:
|
|
||||||
'''List all patterns.'''
|
|
||||||
return self._patterns.copy()
|
|
||||||
|
|
||||||
def list_groups(self) -> dict[str, list[ErrorPattern]]:
|
|
||||||
'''List patterns by group.'''
|
|
||||||
return {name: group.patterns.copy() for name, group in self._groups.items()}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user