Spaces:
Sleeping
Sleeping
Download app/core/parser.py from Ilaa-1505/Error-Clustering-System: direct link, hf CLI and curl.
- Browser
- Download file 6.73 kB
-
https://huggingface.co/spaces/Ilaa-1505/Error-Clustering-System/resolve/main/app/core/parser.py
- Command line
-
hf download hf://spaces/Ilaa-1505/Error-Clustering-System/app/core/parser.py
-
curl -L -o parser.py https://huggingface.co/spaces/Ilaa-1505/Error-Clustering-System/resolve/main/app/core/parser.py
6.73 kB
| import re | |
| import json | |
| import hashlib | |
| from dataclasses import dataclass | |
| class ParsedLog: | |
| raw: str | |
| cleaned: str | |
| error_type: str | |
| module: str | |
| timestamp: str | |
| log_format: str | |
| hash: str | |
| class LogParser: | |
| NOISE_PATTERNS = [ | |
| r'\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}[\.\,\d]*[Z]?', | |
| r'\[\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4}\]', | |
| r'\d{6}\s+\d{6}\s+\d+', | |
| r'\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}', | |
| r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?\b', | |
| r'line \d+', | |
| r'0x[0-9a-fA-F]+', | |
| r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}', | |
| r'\b(blk_[-\d]+)\b', | |
| r'\bpid\s*=?\s*\d+\b', | |
| r'\[\d+\]', | |
| r'\brhost=\S+', | |
| r'\buid=\d+\s+euid=\d+\b', | |
| r'\btty=\S+', | |
| r'\blogname=\S*', | |
| r'\bruser=\S*', | |
| ] | |
| TIMESTAMP_PATTERNS = [ | |
| re.compile(r'(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2})'), | |
| re.compile(r'\[(\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4})\]'), | |
| re.compile(r'(\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2})'), | |
| re.compile(r'(\d{6}\s+\d{6})'), | |
| ] | |
| FORMAT_PATTERNS = [ | |
| (re.compile(r'\[\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4}\]'), 'apache'), | |
| (re.compile(r'^\d{6}\s+\d{6}\s+\d+'), 'hdfs'), | |
| (re.compile(r'^\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\w+\s+\w+[\[\(]'), 'syslog'), | |
| (re.compile(r'^\{.*\}$'), 'json'), | |
| (re.compile(r'Traceback \(most recent call last\)'), 'python_traceback'), | |
| (re.compile(r'\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}[\.\,]\d+\s+[A-Z]+\s+\w+\s+-'), 'java'), | |
| (re.compile(r'\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}'), 'standard'), | |
| ] | |
| ERROR_TYPE_PATTERN = re.compile( | |
| r'\b([A-Z][a-zA-Z]+(?:Error|Exception|Warning|Fault|Panic|Failure))\b' | |
| ) | |
| MODULE_PATTERN = re.compile( | |
| r'\b([\w]+\.py|[\w]+\.java|[\w]+\.js|[\w]+\.rb|[\w]+\.go)\b' | |
| ) | |
| SYSLOG_SERVICE_PATTERN = re.compile( | |
| r'\d{2}:\d{2}:\d{2}\s+\w+\s+([\w]+)[\(\[]' | |
| ) | |
| SEVERITY_WORDS = re.compile( | |
| r'\b(error|warn|warning|critical|fatal|failure|failed|exception)\b', | |
| re.IGNORECASE | |
| ) | |
| def detect_format(self, line: str) -> str: | |
| for pattern, fmt in self.FORMAT_PATTERNS: | |
| if pattern.search(line): | |
| return fmt | |
| return 'unknown' | |
| def extract_timestamp(self, line: str) -> str: | |
| for pattern in self.TIMESTAMP_PATTERNS: | |
| match = pattern.search(line) | |
| if match: | |
| return match.group(1) | |
| return "" | |
| def extract_error_type(self, line: str) -> str: | |
| match = self.ERROR_TYPE_PATTERN.search(line) | |
| if match: | |
| return match.group(1) | |
| syslog_msg = re.search(r'\]:\s*([^;]+)', line) | |
| if syslog_msg: | |
| msg = syslog_msg.group(1).strip() | |
| if msg: | |
| return msg.title()[:50] | |
| sev = self.SEVERITY_WORDS.search(line) | |
| return sev.group(1).title() if sev else "Unknown" | |
| def extract_module(self, line: str) -> str: | |
| match = self.MODULE_PATTERN.search(line) | |
| if match: | |
| return match.group(1) | |
| syslog_match = self.SYSLOG_SERVICE_PATTERN.search(line) | |
| if syslog_match: | |
| return syslog_match.group(1) | |
| return "" | |
| def extract_message(self, line: str, fmt: str) -> str: | |
| if fmt == 'json': | |
| try: | |
| data = json.loads(line) | |
| return (data.get("message") or data.get("msg") | |
| or data.get("error") or line) | |
| except (json.JSONDecodeError, AttributeError): | |
| return line | |
| if fmt == 'apache': | |
| match = re.sub(r'\[.*?\]\s*', '', line).strip() | |
| return match or line | |
| if fmt == 'hdfs': | |
| match = re.match(r'\d{6}\s+\d{6}\s+\d+\s+\w+\s+[\w\.\$]+:\s*(.*)', line) | |
| return match.group(1) if match else line | |
| if fmt == 'syslog': | |
| match = re.match(r'\w{3}\s+\d+\s+[\d:]+\s+\w+\s+[\w\(\)]+\[\d+\]:\s*(.*)', line) | |
| return match.group(1) if match else line | |
| if fmt == 'java': | |
| match = re.match(r'[\d\-\s:,]+\s+\w+\s+[\w\.]+\s+-\s*(.*)', line) | |
| return match.group(1) if match else line | |
| return line | |
| def clean(self, line: str) -> str: | |
| cleaned = line | |
| for pattern in self.NOISE_PATTERNS: | |
| cleaned = re.sub(pattern, '', cleaned) | |
| cleaned = cleaned.lower() | |
| cleaned = re.sub(r'[^\w\s\.\:\-]', ' ', cleaned) | |
| cleaned = re.sub(r'\s+', ' ', cleaned).strip() | |
| return cleaned | |
| def is_error_line(self, line: str, fmt: str) -> bool: | |
| if fmt in ('python_traceback', 'json'): | |
| return True | |
| if re.search(r'\b(ERROR|WARN|WARNING|CRITICAL|FATAL|FAILURE)\b', line): | |
| return True | |
| if fmt == 'syslog' and self.SEVERITY_WORDS.search(line): | |
| return True | |
| if fmt == 'apache' and re.search(r'\[(error|warn)\]', line, re.IGNORECASE): | |
| return True | |
| return False | |
| def parse_line(self, line: str) -> 'ParsedLog | None': | |
| line = line.strip() | |
| if not line: | |
| return None | |
| fmt = self.detect_format(line) | |
| if not self.is_error_line(line, fmt): | |
| return None | |
| message = self.extract_message(line, fmt) | |
| cleaned = self.clean(message) | |
| if len(cleaned) < 5: | |
| return None | |
| return ParsedLog( | |
| raw=line, | |
| cleaned=cleaned, | |
| error_type=self.extract_error_type(line), | |
| module=self.extract_module(line), | |
| timestamp=self.extract_timestamp(line), | |
| log_format=fmt, | |
| hash=hashlib.md5(cleaned.encode()).hexdigest() | |
| ) | |
| def parse_many(self, raw_text: str) -> dict: | |
| lines = raw_text.strip().split('\n') | |
| parsed = [] | |
| seen_hashes = set() | |
| duplicates = 0 | |
| skipped = 0 | |
| format_counts = {} | |
| for line in lines: | |
| result = self.parse_line(line) | |
| if result is None: | |
| skipped += 1 | |
| continue | |
| if result.hash in seen_hashes: | |
| duplicates += 1 | |
| continue | |
| seen_hashes.add(result.hash) | |
| parsed.append(result) | |
| format_counts[result.log_format] = format_counts.get(result.log_format, 0) + 1 | |
| return { | |
| "logs": parsed, | |
| "stats": { | |
| "total_lines": len(lines), | |
| "valid": len(parsed), | |
| "duplicates": duplicates, | |
| "skipped_info": skipped, | |
| "formats_detected": format_counts, | |
| } | |
| } |