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 Log(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 Log(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 Log(
 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 Log([event for event in self.events if event.level in levels], self.path)
 92
 93    def filter_messages(
 94        self,
 95        include_patterns: list[str] = ["*"],
 96        exclude_patterns: list[str] = [],
 97        case_sensitive: bool = True,
 98    ):
 99        """Returns a new `Log` object containing events with messages matching those in `include_patterns`, but not matching `exclude_patterns`.
100
101        Both lists can contain wildcard patterns."""
102        return Log(
103            [
104                event
105                for event in self.events
106                if event.message
107                in younotyou(
108                    [event.message], include_patterns, exclude_patterns, case_sensitive
109                )
110            ],
111            self.path,
112        )
113
114    @classmethod
115    def load_log(cls, logpath: Pathish) -> Self:
116        """Load a `Log` object from the log file at `logpath`."""
117        logpath = Pathier(logpath)
118        events = cls._split_log_into_events(logpath.read_text(encoding="utf-8"))
119        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.

Event(level: str, date: datetime.datetime, message: str)
@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 Log(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 Log(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 Log(
 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 Log([event for event in self.events if event.level in levels], self.path)
 93
 94    def filter_messages(
 95        self,
 96        include_patterns: list[str] = ["*"],
 97        exclude_patterns: list[str] = [],
 98        case_sensitive: bool = True,
 99    ):
100        """Returns a new `Log` object containing events with messages matching those in `include_patterns`, but not matching `exclude_patterns`.
101
102        Both lists can contain wildcard patterns."""
103        return Log(
104            [
105                event
106                for event in self.events
107                if event.message
108                in younotyou(
109                    [event.message], include_patterns, exclude_patterns, case_sensitive
110                )
111            ],
112            self.path,
113        )
114
115    @classmethod
116    def load_log(cls, logpath: Pathish) -> Self:
117        """Load a `Log` object from the log file at `logpath`."""
118        logpath = Pathier(logpath)
119        events = cls._split_log_into_events(logpath.read_text(encoding="utf-8"))
120        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 Log(
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 Log([event for event in self.events if event.level in levels], self.path)

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):
 94    def filter_messages(
 95        self,
 96        include_patterns: list[str] = ["*"],
 97        exclude_patterns: list[str] = [],
 98        case_sensitive: bool = True,
 99    ):
100        """Returns a new `Log` object containing events with messages matching those in `include_patterns`, but not matching `exclude_patterns`.
101
102        Both lists can contain wildcard patterns."""
103        return Log(
104            [
105                event
106                for event in self.events
107                if event.message
108                in younotyou(
109                    [event.message], include_patterns, exclude_patterns, case_sensitive
110                )
111            ],
112            self.path,
113        )

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:
115    @classmethod
116    def load_log(cls, logpath: Pathish) -> Self:
117        """Load a `Log` object from the log file at `logpath`."""
118        logpath = Pathier(logpath)
119        events = cls._split_log_into_events(logpath.read_text(encoding="utf-8"))
120        return cls(cls._parse_events(events), logpath)

Load a Log object from the log file at logpath.