loggi.models
1import re 2from dataclasses import dataclass 3from datetime import datetime 4 5from pathier import Pathier, Pathish 6from typing_extensions import Self 7from younotyou import younotyou 8 9root = Pathier(__file__).parent 10 11 12@dataclass 13class Event: 14 """Class representing a logged event.""" 15 16 level: str 17 date: datetime 18 message: str 19 20 def __str__(self) -> str: 21 sep = "|-|" 22 return sep.join([self.level, str(self.date), self.message]) 23 24 25@dataclass 26class Log: 27 """Class representing a log file as a list of Events.""" 28 29 events: list[Event] 30 path: Pathier | None = None 31 32 def __add__(self, log: Self) -> Self: 33 return self.__class__(self.events + log.events) 34 35 def __str__(self) -> str: 36 return "\n".join(str(event) for event in self.events) 37 38 def __getitem__(self, subscript: slice) -> Self: 39 return self.__class__(self.events[subscript], self.path) 40 41 def __len__(self) -> int: 42 return len(self.events) 43 44 @property 45 def num_events(self) -> int: 46 return len(self.events) 47 48 @staticmethod 49 def _parse_events(events: list[str]) -> list[Event]: 50 """Convert a list of loggi event strings into a list of `Event` objects.""" 51 sep = "|-|" 52 to_datetime = lambda date: datetime.strptime(date, "%x %X") 53 logs = [] 54 for event in events: 55 level, date, message = event.split(sep, maxsplit=3) 56 logs.append(Event(level, to_datetime(date), message)) 57 return logs 58 59 @staticmethod 60 def _split_log_into_events(log: str) -> list[str]: 61 """Decompose a string of loggi events into a list of events, accounting for multi-line events.""" 62 events = [] 63 event = "" 64 for line in log.splitlines(True): 65 if re.findall("[A-Z]+\\|\\-\\|", line): 66 if event: 67 events.append(event.strip("\n")) 68 event = line 69 else: 70 event += line 71 if event: 72 events.append(event.strip("\n")) 73 return events 74 75 def chronosort(self): 76 """Sort this object's events by date.""" 77 self.events = sorted(self.events, key=lambda event: event.date) 78 79 def filter_dates( 80 self, start: datetime = datetime.fromtimestamp(0), stop: datetime | None = None 81 ) -> Self: 82 """Returns a new `Log` object containing events between `start` and `stop`, inclusive.""" 83 if not stop: 84 stop = datetime.now() 85 return self.__class__( 86 [event for event in self.events if start <= event.date <= stop], self.path 87 ) 88 89 def filter_levels(self, levels: list[str]) -> Self: 90 """Returns a new `Log` object containing events with the specified levels.""" 91 return self.__class__( 92 [event for event in self.events if event.level in levels], self.path 93 ) 94 95 def filter_messages( 96 self, 97 include_patterns: list[str] = ["*"], 98 exclude_patterns: list[str] = [], 99 case_sensitive: bool = True, 100 ): 101 """Returns a new `Log` object containing events with messages matching those in `include_patterns`, but not matching `exclude_patterns`. 102 103 Both lists can contain wildcard patterns.""" 104 return Log( 105 [ 106 event 107 for event in self.events 108 if event.message 109 in younotyou( 110 [event.message], include_patterns, exclude_patterns, case_sensitive 111 ) 112 ], 113 self.path, 114 ) 115 116 @classmethod 117 def load_log(cls, logpath: Pathish) -> Self: 118 """Load a `Log` object from the log file at `logpath`.""" 119 logpath = Pathier(logpath) 120 events = cls._split_log_into_events(logpath.read_text(encoding="utf-8")) 121 return cls(cls._parse_events(events), logpath)
@dataclass
class
Event:
13@dataclass 14class Event: 15 """Class representing a logged event.""" 16 17 level: str 18 date: datetime 19 message: str 20 21 def __str__(self) -> str: 22 sep = "|-|" 23 return sep.join([self.level, str(self.date), self.message])
Class representing a logged event.
@dataclass
class
Log:
26@dataclass 27class Log: 28 """Class representing a log file as a list of Events.""" 29 30 events: list[Event] 31 path: Pathier | None = None 32 33 def __add__(self, log: Self) -> Self: 34 return self.__class__(self.events + log.events) 35 36 def __str__(self) -> str: 37 return "\n".join(str(event) for event in self.events) 38 39 def __getitem__(self, subscript: slice) -> Self: 40 return self.__class__(self.events[subscript], self.path) 41 42 def __len__(self) -> int: 43 return len(self.events) 44 45 @property 46 def num_events(self) -> int: 47 return len(self.events) 48 49 @staticmethod 50 def _parse_events(events: list[str]) -> list[Event]: 51 """Convert a list of loggi event strings into a list of `Event` objects.""" 52 sep = "|-|" 53 to_datetime = lambda date: datetime.strptime(date, "%x %X") 54 logs = [] 55 for event in events: 56 level, date, message = event.split(sep, maxsplit=3) 57 logs.append(Event(level, to_datetime(date), message)) 58 return logs 59 60 @staticmethod 61 def _split_log_into_events(log: str) -> list[str]: 62 """Decompose a string of loggi events into a list of events, accounting for multi-line events.""" 63 events = [] 64 event = "" 65 for line in log.splitlines(True): 66 if re.findall("[A-Z]+\\|\\-\\|", line): 67 if event: 68 events.append(event.strip("\n")) 69 event = line 70 else: 71 event += line 72 if event: 73 events.append(event.strip("\n")) 74 return events 75 76 def chronosort(self): 77 """Sort this object's events by date.""" 78 self.events = sorted(self.events, key=lambda event: event.date) 79 80 def filter_dates( 81 self, start: datetime = datetime.fromtimestamp(0), stop: datetime | None = None 82 ) -> Self: 83 """Returns a new `Log` object containing events between `start` and `stop`, inclusive.""" 84 if not stop: 85 stop = datetime.now() 86 return self.__class__( 87 [event for event in self.events if start <= event.date <= stop], self.path 88 ) 89 90 def filter_levels(self, levels: list[str]) -> Self: 91 """Returns a new `Log` object containing events with the specified levels.""" 92 return self.__class__( 93 [event for event in self.events if event.level in levels], self.path 94 ) 95 96 def filter_messages( 97 self, 98 include_patterns: list[str] = ["*"], 99 exclude_patterns: list[str] = [], 100 case_sensitive: bool = True, 101 ): 102 """Returns a new `Log` object containing events with messages matching those in `include_patterns`, but not matching `exclude_patterns`. 103 104 Both lists can contain wildcard patterns.""" 105 return Log( 106 [ 107 event 108 for event in self.events 109 if event.message 110 in younotyou( 111 [event.message], include_patterns, exclude_patterns, case_sensitive 112 ) 113 ], 114 self.path, 115 ) 116 117 @classmethod 118 def load_log(cls, logpath: Pathish) -> Self: 119 """Load a `Log` object from the log file at `logpath`.""" 120 logpath = Pathier(logpath) 121 events = cls._split_log_into_events(logpath.read_text(encoding="utf-8")) 122 return cls(cls._parse_events(events), logpath)
Class representing a log file as a list of Events.
Log( events: list[loggi.models.Event], path: pathier.pathier.Pathier | None = None)
def
chronosort(self):
76 def chronosort(self): 77 """Sort this object's events by date.""" 78 self.events = sorted(self.events, key=lambda event: event.date)
Sort this object's events by date.
def
filter_dates( self, start: datetime.datetime = datetime.datetime(1969, 12, 31, 18, 0), stop: datetime.datetime | None = None) -> Self:
80 def filter_dates( 81 self, start: datetime = datetime.fromtimestamp(0), stop: datetime | None = None 82 ) -> Self: 83 """Returns a new `Log` object containing events between `start` and `stop`, inclusive.""" 84 if not stop: 85 stop = datetime.now() 86 return self.__class__( 87 [event for event in self.events if start <= event.date <= stop], self.path 88 )
Returns a new Log
object containing events between start
and stop
, inclusive.
def
filter_levels(self, levels: list[str]) -> Self:
90 def filter_levels(self, levels: list[str]) -> Self: 91 """Returns a new `Log` object containing events with the specified levels.""" 92 return self.__class__( 93 [event for event in self.events if event.level in levels], self.path 94 )
Returns a new Log
object containing events with the specified levels.
def
filter_messages( self, include_patterns: list[str] = ['*'], exclude_patterns: list[str] = [], case_sensitive: bool = True):
96 def filter_messages( 97 self, 98 include_patterns: list[str] = ["*"], 99 exclude_patterns: list[str] = [], 100 case_sensitive: bool = True, 101 ): 102 """Returns a new `Log` object containing events with messages matching those in `include_patterns`, but not matching `exclude_patterns`. 103 104 Both lists can contain wildcard patterns.""" 105 return Log( 106 [ 107 event 108 for event in self.events 109 if event.message 110 in younotyou( 111 [event.message], include_patterns, exclude_patterns, case_sensitive 112 ) 113 ], 114 self.path, 115 )
Returns a new Log
object containing events with messages matching those in include_patterns
, but not matching exclude_patterns
.
Both lists can contain wildcard patterns.
@classmethod
def
load_log(cls, logpath: pathier.pathier.Pathier | pathlib.Path | str) -> Self:
117 @classmethod 118 def load_log(cls, logpath: Pathish) -> Self: 119 """Load a `Log` object from the log file at `logpath`.""" 120 logpath = Pathier(logpath) 121 events = cls._split_log_into_events(logpath.read_text(encoding="utf-8")) 122 return cls(cls._parse_events(events), logpath)
Load a Log
object from the log file at logpath
.