diff --git a/loglens/analyzers/patterns.py b/loglens/analyzers/patterns.py index 303575d..b5c461d 100644 --- a/loglens/analyzers/patterns.py +++ b/loglens/analyzers/patterns.py @@ -1,222 +1,311 @@ -from dataclasses import dataclass +"""Pattern library for error detection.""" + +import re +from dataclasses import dataclass, field from typing import Optional @dataclass class ErrorPattern: - """Error pattern definition.""" + """Represents an error detection pattern.""" name: str pattern: str - severity: str - description: str + severity: str = "error" + description: str = "" + suggestion: str = "" 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: """Library of error detection patterns.""" def __init__(self): - self._patterns = self._load_patterns() + self._patterns: list[ErrorPattern] = [] + self._groups: dict[str, PatternGroup] = {} + self._load_default_patterns() - def _load_patterns(self) -> list[ErrorPattern]: - """Load all error patterns.""" - return [ + def _load_default_patterns(self) -> None: + """Load default error patterns.""" + self._patterns = [ ErrorPattern( name="Python Exception", - pattern=r"(?:Traceback|Exception|Error|Error:)", + pattern=r"(Traceback|Exception|Error|Traceback \(most recent call last\))", severity="error", - description="Python exception or error", + description="Python exception detected", + suggestion="Check the exception type and stack trace to identify the root cause", group="exceptions", ), ErrorPattern( - name="Python Traceback", - pattern=r"Traceback \(most recent call last\)", + name="Java Stack Trace", + pattern=r"(java\.lang\.|Exception in thread|at \w+\.\w+\.\w+)", severity="error", - description="Python traceback", - 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", + description="Java exception/stack trace detected", + suggestion="Review the Java stack trace for the exception cause", group="exceptions", ), ErrorPattern( name="Connection Refused", - pattern=r"(?:Connection refused|ECONNREFUSED)", + pattern=r"Connection refused|ECONNREFUSED", severity="error", - description="Connection refused by remote host", + description="Connection was refused", + suggestion="Check if the service is running and the port is correct", group="network", ), ErrorPattern( name="Connection Timeout", - pattern=r"(?:Connection timed out|ETIMEDOUT|Timeout)", + pattern=r"Connection timed out|ETIMEDOUT|timeout|Timed out", severity="error", - description="Connection timeout", + description="Connection timed out", + suggestion="Check network connectivity and server responsiveness", group="network", ), ErrorPattern( - name="DNS Error", - pattern=r"(?:DNS|NXDOMAIN|host not found)", + name="Database Error", + pattern=r"(mysql|postgres|sqlite|mongodb|redis).*(error|exception|failed)", severity="error", - description="DNS resolution error", - group="network", + description="Database error detected", + suggestion="Check database connectivity and query syntax", + 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( name="HTTP 5xx Error", - pattern=r"(?:HTTP/[0-9\.]+\s+[5][0-9]{2}|Status: 5\d{2})", + pattern=r"HTTP Error (5\d{2})|Status: (5\d{2})", severity="error", - description="HTTP server error (5xx)", + description="Server-side HTTP error", + suggestion="Check server logs for the root cause", group="http", ), ErrorPattern( name="HTTP 4xx Error", - pattern=r"(?:HTTP/[0-9\.]+\s+[4][0-9]{2}|Status: 4\d{2})", + pattern=r"HTTP Error (4\d{2})|Status: (4\d{2})", severity="warning", - description="HTTP client error (4xx)", + description="Client-side HTTP error", + suggestion="Check request URL and parameters", group="http", ), ErrorPattern( - name="Database Connection", - pattern=r"(?:Cannot connect to database|MongoDB|SQL|ConnectionString|pymongo)", + name="Null Pointer", + pattern=r"(NullPointerException|null reference|NoneType|is None)", severity="error", - description="Database connection issue", - group="database", + description="Null pointer/null reference error", + suggestion="Add null checks before accessing objects", + group="exceptions", ), ErrorPattern( - name="Authentication Failed", - pattern=r"(?:Authentication failed|Auth failed|Invalid credentials|Permission denied)", + name="Index Error", + pattern=r"(IndexError|array index|list index|index out of range)", severity="error", - description="Authentication failure", - group="security", + description="Index out of bounds error", + suggestion="Check array/list bounds before access", + group="exceptions", ), ErrorPattern( - name="SSL/TLS Error", - pattern=r"(?:SSL|TLS|handshake|certificate|HTTPSConnection)", + name="Key Error", + pattern=r"(KeyError|missing key|dict key|Key not found)", severity="error", - description="SSL/TLS error", - group="security", + 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 on device|Disk full|ENOSPC)", + 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"(?:Out of memory|Killed|OOM|MemoryError)", + pattern=r"(MemoryError|Out of memory|Killed|OOM)", severity="critical", - description="Memory exhaustion", + description="Out of memory error", + suggestion="Increase memory or optimize memory usage", group="system", ), ErrorPattern( name="Segmentation Fault", - pattern=r"(?:Segmentation fault|SegFault|SIGSEGV)", + 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="Process Crashed", - pattern=r"(?:Process.*exited|Crashed|exit code|signaled)", - severity="error", - description="Process crash", + name="Panic", + pattern=r"(panic|PANIC|fatal error)", + severity="critical", + description="Application panic", + suggestion="Review panic message and stack trace", group="system", ), ErrorPattern( - name="Deprecation Warning", - pattern=r"(?:DeprecationWarning|Deprecation|PendingDeprecationWarning)", + 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="Deprecation warning", - group="code", + description="Debug message", + suggestion="Ignore unless debugging", + group="general", ), ErrorPattern( - name="Import Error", - pattern=r"(?:ImportError|ModuleNotFoundError|Cannot import|No module named)", + name="Authentication Failed", + pattern=r"(Authentication failed|Login failed|Invalid credentials|401 Unauthorized)", severity="error", - description="Import error", - group="code", + description="Authentication failure", + suggestion="Verify username/password or API key", + group="security", ), ErrorPattern( - name="Key Error", - pattern=r"(?:KeyError|AttributeError|NoneType is not)", + name="SSL/TLS Error", + pattern=r"(SSL|Certificate|TLS|handshake|ssl error)", 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", + description="SSL/TLS error", + suggestion="Check certificate validity and configuration", + group="security", ), ] - def get_patterns_for_content(self, content: str) -> list[ErrorPattern]: - """Get matching patterns for content.""" + self._groups = { + "exceptions": PatternGroup( + 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 = [] for pattern in self._patterns: - if re.search(pattern.pattern, content, re.IGNORECASE): - matches.append(pattern) + if pattern.enabled: + match = pattern.match(text) + if match: + matches.append((pattern, match)) return matches - def get_all_patterns(self) -> dict[str, list[dict]]: - """Get all patterns grouped by category.""" - groups = {} + def find_match(self, text: str) -> Optional[tuple[ErrorPattern, re.Match]]: + """Find the first matching pattern.""" for pattern in self._patterns: - if pattern.group not in groups: - groups[pattern.group] = [] - groups[pattern.group].append({ - "name": pattern.name, - "pattern": pattern.pattern, - "severity": pattern.severity, - "description": pattern.description, - }) - return groups + if pattern.enabled: + match = pattern.match(text) + if match: + return (pattern, match) + return None + + def get_patterns_by_severity(self, severity: str) -> list[ErrorPattern]: + """Get patterns by severity level.""" + return [p for p in self._patterns if p.severity == severity] + + 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()}