diff --git a/loglens/parsers/base.py b/loglens/parsers/base.py index b16382d..8329c34 100644 --- a/loglens/parsers/base.py +++ b/loglens/parsers/base.py @@ -1,3 +1,5 @@ +"""Base parser class and data structures.""" + from abc import ABC, abstractmethod from dataclasses import dataclass, field from datetime import datetime @@ -11,43 +13,77 @@ class LogFormat(Enum): JSON = "json" SYSLOG = "syslog" APACHE = "apache" - RAW = "raw" + UNKNOWN = "unknown" @dataclass -class ParsedEntry: - """Parsed log entry.""" +class ParsedLogEntry: + """Represents a parsed log entry.""" raw_line: str - format: LogFormat - timestamp: Optional[str] - level: Optional[str] - message: str - metadata: dict[str, Any] = field(default_factory=dict) - severity: str = "info" + timestamp: Optional[datetime] = None + level: Optional[str] = None + message: str = "" + source: Optional[str] = None + host: Optional[str] = None + facility: Optional[str] = None + severity: Optional[str] = None + logger: Optional[str] = None + extra: dict[str, Any] = field(default_factory=dict) + line_number: int = 0 + error_pattern: Optional[str] = None - def to_dict(self) -> dict: + def to_dict(self) -> dict[str, Any]: """Convert to dictionary.""" - return { + result = { "raw_line": self.raw_line, - "format": self.format.value, - "timestamp": self.timestamp, - "level": self.level, "message": self.message, - "metadata": self.metadata, - "severity": self.severity, + "line_number": self.line_number, } + if self.timestamp: + result["timestamp"] = self.timestamp.isoformat() + if self.level: + result["level"] = self.level + if self.source: + result["source"] = self.source + if self.host: + result["host"] = self.host + if self.facility: + result["facility"] = self.facility + if self.severity: + result["severity"] = self.severity + if self.logger: + result["logger"] = self.logger + if self.extra: + result["extra"] = self.extra + if self.error_pattern: + result["error_pattern"] = self.error_pattern + return result -class BaseParser(ABC): - """Base parser class.""" +class LogParser(ABC): + """Abstract base class for log parsers.""" + + format_name: str = "base" @abstractmethod - def get_format(self) -> LogFormat: - """Get the format this parser handles.""" + def parse(self, line: str, line_number: int = 0) -> Optional[ParsedLogEntry]: + """Parse a single log line.""" pass @abstractmethod - def parse(self, line: str) -> Optional[ParsedEntry]: - """Parse a log line.""" + def can_parse(self, line: str) -> bool: + """Check if this parser can handle the given line.""" pass + + def parse_batch(self, lines: list[str]) -> list[ParsedLogEntry]: + """Parse multiple lines.""" + results = [] + for i, line in enumerate(lines, 1): + try: + entry = self.parse(line, i) + if entry: + results.append(entry) + except Exception: + results.append(ParsedLogEntry(raw_line=line, message="Parse error", line_number=i)) + return results