docs for muutils v0.6.18
View Source on GitHub

muutils.logger.loggingstream


 1from __future__ import annotations
 2
 3import time
 4from dataclasses import dataclass, field
 5from typing import Any, Callable
 6
 7from muutils.logger.simplelogger import AnyIO, NullIO
 8from muutils.misc import sanitize_fname
 9
10
11@dataclass
12class LoggingStream:
13    """properties of a logging stream
14
15    - `name: str` name of the stream
16    - `aliases: set[str]` aliases for the stream
17            (calls to these names will be redirected to this stream. duplicate alises will result in errors)
18            TODO: perhaps duplicate alises should result in duplicate writes?
19    - `file: str|bool|AnyIO|None` file to write to
20            - if `None`, will write to standard log
21            - if `True`, will write to `name + ".log"`
22            - if `False` will "write" to `NullIO` (throw it away)
23            - if a string, will write to that file
24            - if a fileIO type object, will write to that object
25    - `default_level: int|None` default level for this stream
26    - `default_contents: dict[str, Callable[[], Any]]` default contents for this stream
27    - `last_msg: tuple[float, Any]|None` last message written to this stream (timestamp, message)
28    """
29
30    name: str | None
31    aliases: set[str | None] = field(default_factory=set)
32    file: str | bool | AnyIO | None = None
33    default_level: int | None = None
34    default_contents: dict[str, Callable[[], Any]] = field(default_factory=dict)
35    handler: AnyIO | None = None
36
37    # TODO: implement last-message caching
38    # last_msg: tuple[float, Any]|None = None
39
40    def make_handler(self) -> AnyIO | None:
41        if self.file is None:
42            return None
43        elif isinstance(self.file, str):
44            # if its a string, open a file
45            return open(
46                self.file,
47                "w",
48                encoding="utf-8",
49            )
50        elif isinstance(self.file, bool):
51            # if its a bool and true, open a file with the same name as the stream (in the current dir)
52            # TODO: make this happen in the same dir as the main logfile?
53            if self.file:
54                return open(  # type: ignore[return-value]
55                    f"{sanitize_fname(self.name)}.log.jsonl",
56                    "w",
57                    encoding="utf-8",
58                )
59            else:
60                return NullIO()
61        else:
62            # if its neither, check it has `.write()` and `.flush()` methods
63            if (
64                (
65                    not hasattr(self.file, "write")
66                    or (not callable(self.file.write))
67                    or (not hasattr(self.file, "flush"))
68                    or (not callable(self.file.flush))
69                )
70                or (not hasattr(self.file, "close"))
71                or (not callable(self.file.close))
72            ):
73                raise ValueError(f"stream {self.name} has invalid handler {self.file}")
74            # ignore type check because we know it has a .write() method,
75            # assume the user knows what they're doing
76            return self.file  # type: ignore
77
78    def __post_init__(self):
79        self.aliases = set(self.aliases)
80        if any(x.startswith("_") for x in self.aliases if x is not None):
81            raise ValueError(
82                "stream names or aliases cannot start with an underscore, sorry"
83            )
84        self.aliases.add(self.name)
85        self.default_contents["_timestamp"] = time.time
86        self.default_contents["_stream"] = lambda: self.name
87        self.handler = self.make_handler()
88
89    def __del__(self):
90        if self.handler is not None:
91            self.handler.flush()
92            self.handler.close()
93
94    def __str__(self):
95        return f"LoggingStream(name={self.name}, aliases={self.aliases}, file={self.file}, default_level={self.default_level}, default_contents={self.default_contents})"

@dataclass
class LoggingStream:
12@dataclass
13class LoggingStream:
14    """properties of a logging stream
15
16    - `name: str` name of the stream
17    - `aliases: set[str]` aliases for the stream
18            (calls to these names will be redirected to this stream. duplicate alises will result in errors)
19            TODO: perhaps duplicate alises should result in duplicate writes?
20    - `file: str|bool|AnyIO|None` file to write to
21            - if `None`, will write to standard log
22            - if `True`, will write to `name + ".log"`
23            - if `False` will "write" to `NullIO` (throw it away)
24            - if a string, will write to that file
25            - if a fileIO type object, will write to that object
26    - `default_level: int|None` default level for this stream
27    - `default_contents: dict[str, Callable[[], Any]]` default contents for this stream
28    - `last_msg: tuple[float, Any]|None` last message written to this stream (timestamp, message)
29    """
30
31    name: str | None
32    aliases: set[str | None] = field(default_factory=set)
33    file: str | bool | AnyIO | None = None
34    default_level: int | None = None
35    default_contents: dict[str, Callable[[], Any]] = field(default_factory=dict)
36    handler: AnyIO | None = None
37
38    # TODO: implement last-message caching
39    # last_msg: tuple[float, Any]|None = None
40
41    def make_handler(self) -> AnyIO | None:
42        if self.file is None:
43            return None
44        elif isinstance(self.file, str):
45            # if its a string, open a file
46            return open(
47                self.file,
48                "w",
49                encoding="utf-8",
50            )
51        elif isinstance(self.file, bool):
52            # if its a bool and true, open a file with the same name as the stream (in the current dir)
53            # TODO: make this happen in the same dir as the main logfile?
54            if self.file:
55                return open(  # type: ignore[return-value]
56                    f"{sanitize_fname(self.name)}.log.jsonl",
57                    "w",
58                    encoding="utf-8",
59                )
60            else:
61                return NullIO()
62        else:
63            # if its neither, check it has `.write()` and `.flush()` methods
64            if (
65                (
66                    not hasattr(self.file, "write")
67                    or (not callable(self.file.write))
68                    or (not hasattr(self.file, "flush"))
69                    or (not callable(self.file.flush))
70                )
71                or (not hasattr(self.file, "close"))
72                or (not callable(self.file.close))
73            ):
74                raise ValueError(f"stream {self.name} has invalid handler {self.file}")
75            # ignore type check because we know it has a .write() method,
76            # assume the user knows what they're doing
77            return self.file  # type: ignore
78
79    def __post_init__(self):
80        self.aliases = set(self.aliases)
81        if any(x.startswith("_") for x in self.aliases if x is not None):
82            raise ValueError(
83                "stream names or aliases cannot start with an underscore, sorry"
84            )
85        self.aliases.add(self.name)
86        self.default_contents["_timestamp"] = time.time
87        self.default_contents["_stream"] = lambda: self.name
88        self.handler = self.make_handler()
89
90    def __del__(self):
91        if self.handler is not None:
92            self.handler.flush()
93            self.handler.close()
94
95    def __str__(self):
96        return f"LoggingStream(name={self.name}, aliases={self.aliases}, file={self.file}, default_level={self.default_level}, default_contents={self.default_contents})"

properties of a logging stream

  • name: str name of the stream
  • aliases: set[str] aliases for the stream (calls to these names will be redirected to this stream. duplicate alises will result in errors) TODO: perhaps duplicate alises should result in duplicate writes?
  • file: str|bool|AnyIO|None file to write to
    • if None, will write to standard log
    • if True, will write to name + ".log"
    • if False will "write" to NullIO (throw it away)
    • if a string, will write to that file
    • if a fileIO type object, will write to that object
  • default_level: int|None default level for this stream
  • default_contents: dict[str, Callable[[], Any]] default contents for this stream
  • last_msg: tuple[float, Any]|None last message written to this stream (timestamp, message)
LoggingStream( name: str | None, aliases: set[str | None] = <factory>, file: Union[str, bool, TextIO, muutils.logger.simplelogger.NullIO, NoneType] = None, default_level: int | None = None, default_contents: dict[str, typing.Callable[[], typing.Any]] = <factory>, handler: Union[TextIO, muutils.logger.simplelogger.NullIO, NoneType] = None)
name: str | None
aliases: set[str | None]
file: Union[str, bool, TextIO, muutils.logger.simplelogger.NullIO, NoneType] = None
default_level: int | None = None
default_contents: dict[str, typing.Callable[[], typing.Any]]
handler: Union[TextIO, muutils.logger.simplelogger.NullIO, NoneType] = None
def make_handler(self) -> Union[TextIO, muutils.logger.simplelogger.NullIO, NoneType]:
41    def make_handler(self) -> AnyIO | None:
42        if self.file is None:
43            return None
44        elif isinstance(self.file, str):
45            # if its a string, open a file
46            return open(
47                self.file,
48                "w",
49                encoding="utf-8",
50            )
51        elif isinstance(self.file, bool):
52            # if its a bool and true, open a file with the same name as the stream (in the current dir)
53            # TODO: make this happen in the same dir as the main logfile?
54            if self.file:
55                return open(  # type: ignore[return-value]
56                    f"{sanitize_fname(self.name)}.log.jsonl",
57                    "w",
58                    encoding="utf-8",
59                )
60            else:
61                return NullIO()
62        else:
63            # if its neither, check it has `.write()` and `.flush()` methods
64            if (
65                (
66                    not hasattr(self.file, "write")
67                    or (not callable(self.file.write))
68                    or (not hasattr(self.file, "flush"))
69                    or (not callable(self.file.flush))
70                )
71                or (not hasattr(self.file, "close"))
72                or (not callable(self.file.close))
73            ):
74                raise ValueError(f"stream {self.name} has invalid handler {self.file}")
75            # ignore type check because we know it has a .write() method,
76            # assume the user knows what they're doing
77            return self.file  # type: ignore