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    @property
 39    def num_events(self) -> int:
 40        return len(self.events)
 41
 42    @staticmethod
 43    def _parse_events(events: list[str]) -> list[Event]:
 44        """Convert a list of loggi event strings into a list of `Event` objects."""
 45        sep = "|-|"
 46        to_datetime = lambda date: datetime.strptime(date, "%x %X")
 47        logs = []
 48        for event in events:
 49            level, date, message = event.split(sep, maxsplit=3)
 50            logs.append(Event(level, to_datetime(date), message))
 51        return logs
 52
 53    @staticmethod
 54    def _split_log_into_events(log: str) -> list[str]:
 55        """Decompose a string of loggi events into a list of events, accounting for multi-line events."""
 56        events = []
 57        event = ""
 58        for line in log.splitlines(True):
 59            if re.findall("[A-Z]+\\|\\-\\|", line):
 60                if event:
 61                    events.append(event.strip("\n"))
 62                event = line
 63            else:
 64                event += line
 65        if event:
 66            events.append(event.strip("\n"))
 67        return events
 68
 69    def chronosort(self):
 70        """Sort this object's events by date."""
 71        self.events = sorted(self.events, key=lambda event: event.date)
 72
 73    def filter_dates(
 74        self, start: datetime = datetime.fromtimestamp(0), stop: datetime | None = None
 75    ) -> Self:
 76        """Returns a new `Log` object containing events between `start` and `stop`, inclusive."""
 77        if not stop:
 78            stop = datetime.now()
 79        return Log(
 80            [event for event in self.events if start <= event.date <= stop], self.path
 81        )
 82
 83    def filter_levels(self, levels: list[str]) -> Self:
 84        """Returns a new `Log` object containing events with the specified levels."""
 85        return Log([event for event in self.events if event.level in levels], self.path)
 86
 87    def filter_messages(
 88        self,
 89        include_patterns: list[str] = ["*"],
 90        exclude_patterns: list[str] = [],
 91        case_sensitive: bool = True,
 92    ):
 93        """Returns a new `Log` object containing events with messages matching those in `include_patterns`, but not matching `exclude_patterns`.
 94
 95        Both lists can contain wildcard patterns."""
 96        return Log(
 97            [
 98                event
 99                for event in self.events
100                if event.message
101                in younotyou(
102                    [event.message], include_patterns, exclude_patterns, case_sensitive
103                )
104            ],
105            self.path,
106        )
107
108    @classmethod
109    def load_log(cls, logpath: Pathish) -> Self:
110        """Load a `Log` object from the log file at `logpath`."""
111        logpath = Pathier(logpath)
112        events = cls._split_log_into_events(logpath.read_text(encoding="utf-8"))
113        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    @property
 40    def num_events(self) -> int:
 41        return len(self.events)
 42
 43    @staticmethod
 44    def _parse_events(events: list[str]) -> list[Event]:
 45        """Convert a list of loggi event strings into a list of `Event` objects."""
 46        sep = "|-|"
 47        to_datetime = lambda date: datetime.strptime(date, "%x %X")
 48        logs = []
 49        for event in events:
 50            level, date, message = event.split(sep, maxsplit=3)
 51            logs.append(Event(level, to_datetime(date), message))
 52        return logs
 53
 54    @staticmethod
 55    def _split_log_into_events(log: str) -> list[str]:
 56        """Decompose a string of loggi events into a list of events, accounting for multi-line events."""
 57        events = []
 58        event = ""
 59        for line in log.splitlines(True):
 60            if re.findall("[A-Z]+\\|\\-\\|", line):
 61                if event:
 62                    events.append(event.strip("\n"))
 63                event = line
 64            else:
 65                event += line
 66        if event:
 67            events.append(event.strip("\n"))
 68        return events
 69
 70    def chronosort(self):
 71        """Sort this object's events by date."""
 72        self.events = sorted(self.events, key=lambda event: event.date)
 73
 74    def filter_dates(
 75        self, start: datetime = datetime.fromtimestamp(0), stop: datetime | None = None
 76    ) -> Self:
 77        """Returns a new `Log` object containing events between `start` and `stop`, inclusive."""
 78        if not stop:
 79            stop = datetime.now()
 80        return Log(
 81            [event for event in self.events if start <= event.date <= stop], self.path
 82        )
 83
 84    def filter_levels(self, levels: list[str]) -> Self:
 85        """Returns a new `Log` object containing events with the specified levels."""
 86        return Log([event for event in self.events if event.level in levels], self.path)
 87
 88    def filter_messages(
 89        self,
 90        include_patterns: list[str] = ["*"],
 91        exclude_patterns: list[str] = [],
 92        case_sensitive: bool = True,
 93    ):
 94        """Returns a new `Log` object containing events with messages matching those in `include_patterns`, but not matching `exclude_patterns`.
 95
 96        Both lists can contain wildcard patterns."""
 97        return Log(
 98            [
 99                event
100                for event in self.events
101                if event.message
102                in younotyou(
103                    [event.message], include_patterns, exclude_patterns, case_sensitive
104                )
105            ],
106            self.path,
107        )
108
109    @classmethod
110    def load_log(cls, logpath: Pathish) -> Self:
111        """Load a `Log` object from the log file at `logpath`."""
112        logpath = Pathier(logpath)
113        events = cls._split_log_into_events(logpath.read_text(encoding="utf-8"))
114        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):
70    def chronosort(self):
71        """Sort this object's events by date."""
72        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:
74    def filter_dates(
75        self, start: datetime = datetime.fromtimestamp(0), stop: datetime | None = None
76    ) -> Self:
77        """Returns a new `Log` object containing events between `start` and `stop`, inclusive."""
78        if not stop:
79            stop = datetime.now()
80        return Log(
81            [event for event in self.events if start <= event.date <= stop], self.path
82        )

Returns a new Log object containing events between start and stop, inclusive.

def filter_levels(self, levels: list[str]) -> Self:
84    def filter_levels(self, levels: list[str]) -> Self:
85        """Returns a new `Log` object containing events with the specified levels."""
86        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):
 88    def filter_messages(
 89        self,
 90        include_patterns: list[str] = ["*"],
 91        exclude_patterns: list[str] = [],
 92        case_sensitive: bool = True,
 93    ):
 94        """Returns a new `Log` object containing events with messages matching those in `include_patterns`, but not matching `exclude_patterns`.
 95
 96        Both lists can contain wildcard patterns."""
 97        return Log(
 98            [
 99                event
100                for event in self.events
101                if event.message
102                in younotyou(
103                    [event.message], include_patterns, exclude_patterns, case_sensitive
104                )
105            ],
106            self.path,
107        )

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:
109    @classmethod
110    def load_log(cls, logpath: Pathish) -> Self:
111        """Load a `Log` object from the log file at `logpath`."""
112        logpath = Pathier(logpath)
113        events = cls._split_log_into_events(logpath.read_text(encoding="utf-8"))
114        return cls(cls._parse_events(events), logpath)

Load a Log object from the log file at logpath.