bear-utils 0.7.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bear_utils/__init__.py +13 -0
- bear_utils/ai/__init__.py +30 -0
- bear_utils/ai/ai_helpers/__init__.py +130 -0
- bear_utils/ai/ai_helpers/_common.py +19 -0
- bear_utils/ai/ai_helpers/_config.py +24 -0
- bear_utils/ai/ai_helpers/_parsers.py +188 -0
- bear_utils/ai/ai_helpers/_types.py +20 -0
- bear_utils/cache/__init__.py +119 -0
- bear_utils/cli/__init__.py +4 -0
- bear_utils/cli/commands.py +59 -0
- bear_utils/cli/prompt_helpers.py +166 -0
- bear_utils/cli/shell/__init__.py +0 -0
- bear_utils/cli/shell/_base_command.py +74 -0
- bear_utils/cli/shell/_base_shell.py +390 -0
- bear_utils/cli/shell/_common.py +19 -0
- bear_utils/config/__init__.py +11 -0
- bear_utils/config/config_manager.py +92 -0
- bear_utils/config/dir_manager.py +64 -0
- bear_utils/config/settings_manager.py +232 -0
- bear_utils/constants/__init__.py +16 -0
- bear_utils/constants/_exceptions.py +3 -0
- bear_utils/constants/_lazy_typing.py +15 -0
- bear_utils/constants/date_related.py +36 -0
- bear_utils/constants/time_related.py +22 -0
- bear_utils/database/__init__.py +6 -0
- bear_utils/database/_db_manager.py +104 -0
- bear_utils/events/__init__.py +16 -0
- bear_utils/events/events_class.py +52 -0
- bear_utils/events/events_module.py +65 -0
- bear_utils/extras/__init__.py +17 -0
- bear_utils/extras/_async_helpers.py +15 -0
- bear_utils/extras/_tools.py +178 -0
- bear_utils/extras/platform_utils.py +53 -0
- bear_utils/extras/wrappers/__init__.py +0 -0
- bear_utils/extras/wrappers/add_methods.py +98 -0
- bear_utils/files/__init__.py +4 -0
- bear_utils/files/file_handlers/__init__.py +3 -0
- bear_utils/files/file_handlers/_base_file_handler.py +93 -0
- bear_utils/files/file_handlers/file_handler_factory.py +278 -0
- bear_utils/files/file_handlers/json_file_handler.py +44 -0
- bear_utils/files/file_handlers/log_file_handler.py +33 -0
- bear_utils/files/file_handlers/txt_file_handler.py +34 -0
- bear_utils/files/file_handlers/yaml_file_handler.py +57 -0
- bear_utils/files/ignore_parser.py +298 -0
- bear_utils/graphics/__init__.py +4 -0
- bear_utils/graphics/bear_gradient.py +140 -0
- bear_utils/graphics/image_helpers.py +39 -0
- bear_utils/gui/__init__.py +3 -0
- bear_utils/gui/gui_tools/__init__.py +5 -0
- bear_utils/gui/gui_tools/_settings.py +37 -0
- bear_utils/gui/gui_tools/_types.py +12 -0
- bear_utils/gui/gui_tools/qt_app.py +145 -0
- bear_utils/gui/gui_tools/qt_color_picker.py +119 -0
- bear_utils/gui/gui_tools/qt_file_handler.py +138 -0
- bear_utils/gui/gui_tools/qt_input_dialog.py +306 -0
- bear_utils/logging/__init__.py +25 -0
- bear_utils/logging/logger_manager/__init__.py +0 -0
- bear_utils/logging/logger_manager/_common.py +47 -0
- bear_utils/logging/logger_manager/_console_junk.py +131 -0
- bear_utils/logging/logger_manager/_styles.py +91 -0
- bear_utils/logging/logger_manager/loggers/__init__.py +0 -0
- bear_utils/logging/logger_manager/loggers/_base_logger.py +238 -0
- bear_utils/logging/logger_manager/loggers/_base_logger.pyi +50 -0
- bear_utils/logging/logger_manager/loggers/_buffer_logger.py +55 -0
- bear_utils/logging/logger_manager/loggers/_console_logger.py +249 -0
- bear_utils/logging/logger_manager/loggers/_console_logger.pyi +64 -0
- bear_utils/logging/logger_manager/loggers/_file_logger.py +141 -0
- bear_utils/logging/logger_manager/loggers/_level_sin.py +58 -0
- bear_utils/logging/logger_manager/loggers/_logger.py +18 -0
- bear_utils/logging/logger_manager/loggers/_sub_logger.py +110 -0
- bear_utils/logging/logger_manager/loggers/_sub_logger.pyi +38 -0
- bear_utils/logging/loggers.py +76 -0
- bear_utils/monitoring/__init__.py +10 -0
- bear_utils/monitoring/host_monitor.py +350 -0
- bear_utils/time/__init__.py +16 -0
- bear_utils/time/_helpers.py +91 -0
- bear_utils/time/_time_class.py +316 -0
- bear_utils/time/_timer.py +80 -0
- bear_utils/time/_tools.py +17 -0
- bear_utils/time/time_manager.py +218 -0
- bear_utils-0.7.11.dist-info/METADATA +260 -0
- bear_utils-0.7.11.dist-info/RECORD +83 -0
- bear_utils-0.7.11.dist-info/WHEEL +4 -0
@@ -0,0 +1,249 @@
|
|
1
|
+
# region Imports
|
2
|
+
from functools import cached_property
|
3
|
+
from logging import DEBUG, Formatter, Handler, Logger
|
4
|
+
from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler
|
5
|
+
from queue import Queue
|
6
|
+
from typing import override
|
7
|
+
|
8
|
+
from prompt_toolkit import PromptSession
|
9
|
+
from rich.text import Text
|
10
|
+
from rich.theme import Theme
|
11
|
+
from rich.traceback import Traceback
|
12
|
+
|
13
|
+
from bear_utils.constants.date_related import DATE_TIME_FORMAT
|
14
|
+
|
15
|
+
from .._common import FIVE_MEGABYTES, VERBOSE_CONSOLE_FORMAT, VERBOSE_FORMAT, ExecValues
|
16
|
+
from .._console_junk import ConsoleBuffering, ConsoleFormatter, ConsoleHandler
|
17
|
+
from ._base_logger import BaseLogger
|
18
|
+
|
19
|
+
# endregion Imports
|
20
|
+
|
21
|
+
|
22
|
+
class ConsoleLogger(Logger, BaseLogger):
|
23
|
+
"""
|
24
|
+
A comprehensive console logger that combines Python's logging framework with Rich console styling.
|
25
|
+
|
26
|
+
This logger provides styled console output with configurable file logging, queue handling,
|
27
|
+
buffering, and interactive input capabilities. It dynamically creates logging methods
|
28
|
+
(info, error, debug, etc.) that forward to Rich's styled console printing.
|
29
|
+
|
30
|
+
Features:
|
31
|
+
- Rich styled console output with themes
|
32
|
+
- Optional file logging with rotation
|
33
|
+
- Queue-based async logging
|
34
|
+
- Message buffering capabilities
|
35
|
+
- Interactive prompt integration
|
36
|
+
- Exception tracebacks with local variables
|
37
|
+
|
38
|
+
Example:
|
39
|
+
logger = ConsoleLogger.get_instance(init=True, verbose=True, name="MyLogger", level=DEBUG)
|
40
|
+
logger.info("This is styled info")
|
41
|
+
logger.error("This is styled error")
|
42
|
+
logger.success("This is styled success")
|
43
|
+
"""
|
44
|
+
|
45
|
+
# region Setup
|
46
|
+
def __init__(
|
47
|
+
self,
|
48
|
+
theme: Theme | None = None,
|
49
|
+
name: str = "ConsoleLogger",
|
50
|
+
level: int = DEBUG,
|
51
|
+
disabled: bool = True,
|
52
|
+
console: bool = True,
|
53
|
+
file: bool = False,
|
54
|
+
queue_handler: bool = False,
|
55
|
+
buffering: bool = False,
|
56
|
+
*args,
|
57
|
+
**kwargs,
|
58
|
+
) -> None:
|
59
|
+
Logger.__init__(self, name=name, level=level)
|
60
|
+
BaseLogger.__init__(
|
61
|
+
self,
|
62
|
+
output_handler=self._console_output,
|
63
|
+
theme=theme,
|
64
|
+
style_disabled=kwargs.get("style_disabled", False),
|
65
|
+
logger_mode=kwargs.get("logger_mode", True),
|
66
|
+
level=level,
|
67
|
+
)
|
68
|
+
self.name = name
|
69
|
+
self.level = level
|
70
|
+
self.setLevel(level)
|
71
|
+
self.session = None
|
72
|
+
self.disabled = disabled
|
73
|
+
self._handlers: list[Handler] = []
|
74
|
+
self.logger_mode: bool = kwargs.pop("logger_mode", True)
|
75
|
+
if self.logger_mode:
|
76
|
+
self.disabled = False
|
77
|
+
self._handle_enable_booleans(
|
78
|
+
file=file,
|
79
|
+
console=console,
|
80
|
+
buffering=buffering,
|
81
|
+
queue_handler=queue_handler,
|
82
|
+
**kwargs,
|
83
|
+
)
|
84
|
+
|
85
|
+
def _handle_enable_booleans(self, file, console, buffering, queue_handler, **kwargs) -> None:
|
86
|
+
"""Configure logging handlers based on initialization parameters."""
|
87
|
+
if console or buffering:
|
88
|
+
self.console_handler: ConsoleHandler = ConsoleHandler(self.print, self.output_buffer)
|
89
|
+
self.console_handler.setFormatter(ConsoleFormatter(fmt=VERBOSE_CONSOLE_FORMAT, datefmt=DATE_TIME_FORMAT))
|
90
|
+
self.console_handler.setLevel(self.level)
|
91
|
+
if console:
|
92
|
+
self._handlers.append(self.console_handler)
|
93
|
+
if buffering:
|
94
|
+
self.buffer_handler: ConsoleBuffering = ConsoleBuffering(console_handler=self.console_handler)
|
95
|
+
self.addHandler(self.buffer_handler)
|
96
|
+
if file:
|
97
|
+
self.file_handler: RotatingFileHandler = RotatingFileHandler(
|
98
|
+
filename=kwargs.get("file_path", "console.log"),
|
99
|
+
maxBytes=kwargs.get("max_bytes", FIVE_MEGABYTES),
|
100
|
+
backupCount=kwargs.get("backup_count", 5),
|
101
|
+
)
|
102
|
+
self.file_handler.setFormatter(Formatter(fmt=VERBOSE_FORMAT, datefmt=DATE_TIME_FORMAT))
|
103
|
+
self.file_handler.setLevel(self.level)
|
104
|
+
self._handlers.append(self.file_handler)
|
105
|
+
if queue_handler:
|
106
|
+
self.queue = Queue()
|
107
|
+
self.queue_handler = QueueHandler(self.queue)
|
108
|
+
self.addHandler(self.queue_handler)
|
109
|
+
self.listener = QueueListener(self.queue, *self._handlers)
|
110
|
+
self.listener.start()
|
111
|
+
else:
|
112
|
+
for handler in self._handlers:
|
113
|
+
self.addHandler(handler)
|
114
|
+
|
115
|
+
def stop_queue_listener(self) -> None:
|
116
|
+
"""Stop the queue listener if it exists and clean up resources."""
|
117
|
+
if hasattr(self, "listener"):
|
118
|
+
self.verbose("ConsoleLogger: QueueListener stopped and cleaned up.")
|
119
|
+
self.listener.stop()
|
120
|
+
del self.listener
|
121
|
+
del self.queue
|
122
|
+
del self.queue_handler
|
123
|
+
|
124
|
+
def trigger_buffer_flush(self) -> Text:
|
125
|
+
"""Flush buffered messages to console output."""
|
126
|
+
if hasattr(self, "buffer_handler"):
|
127
|
+
return self.buffer_handler.flush_to_output()
|
128
|
+
return Text("No buffering handler available.", style="bold red")
|
129
|
+
|
130
|
+
def set_base_level(self, level: int) -> None:
|
131
|
+
"""Set the base logging level for the console logger."""
|
132
|
+
super().set_base_level(level)
|
133
|
+
self.setLevel(level)
|
134
|
+
if hasattr(self, "console_handler"):
|
135
|
+
self.console_handler.setLevel(level)
|
136
|
+
if hasattr(self, "buffer_handler"):
|
137
|
+
self.buffer_handler.setLevel(level)
|
138
|
+
if hasattr(self, "queue_handler"):
|
139
|
+
self.queue_handler.setLevel(level)
|
140
|
+
|
141
|
+
def _console_output(self, msg: object, extra, *args, **kwargs) -> None:
|
142
|
+
"""Console-specific output handler that integrates with logging module."""
|
143
|
+
if not self.logger_mode:
|
144
|
+
self.print(msg, *args, **kwargs)
|
145
|
+
else:
|
146
|
+
kwargs.pop("style", None)
|
147
|
+
self.log(
|
148
|
+
level=extra.get("log_level", DEBUG),
|
149
|
+
msg=msg,
|
150
|
+
extra=extra,
|
151
|
+
*args,
|
152
|
+
**kwargs,
|
153
|
+
)
|
154
|
+
|
155
|
+
# endregion Setup
|
156
|
+
|
157
|
+
# region Utility Methods
|
158
|
+
|
159
|
+
async def input(self, msg: str, style="info", **kwargs) -> str:
|
160
|
+
"""Display a styled prompt and return user input asynchronously."""
|
161
|
+
if not self.session:
|
162
|
+
self.session = PromptSession(**kwargs)
|
163
|
+
self.print(msg, style=style)
|
164
|
+
return await self.session.prompt_async()
|
165
|
+
|
166
|
+
def output_buffer(
|
167
|
+
self, msg: object, end="\n", exc_info=None, exec_values: ExecValues | None = None, *args, **kwargs
|
168
|
+
) -> str:
|
169
|
+
"""Capture console output to a string buffer without printing to terminal."""
|
170
|
+
if exc_info and exec_values:
|
171
|
+
exception: Traceback = self._get_exception(manual=True, exec_values=exec_values)
|
172
|
+
self.console.print(exception, end=end)
|
173
|
+
self.console.print(msg, end="", style=kwargs.get("style", "info"))
|
174
|
+
output = self.console_buffer.getvalue()
|
175
|
+
self._reset_buffer()
|
176
|
+
return output
|
177
|
+
|
178
|
+
# endregion Utility Methods
|
179
|
+
|
180
|
+
# region Enhanced Print Methods
|
181
|
+
|
182
|
+
def print(self, msg: object, end="\n", exc_info=None, extra: dict | None = None, *args, **kwargs) -> None | str:
|
183
|
+
"""
|
184
|
+
Print styled messages with enhanced exception handling and JSON support.
|
185
|
+
|
186
|
+
Extends the base print method with proper exception tracebacks and
|
187
|
+
integrated JSON printing for structured data output.
|
188
|
+
"""
|
189
|
+
if exc_info is not None:
|
190
|
+
try:
|
191
|
+
self._print(self._get_exception(), end=end, width=100, show_locals=True, **kwargs)
|
192
|
+
except ValueError:
|
193
|
+
...
|
194
|
+
|
195
|
+
self._print(msg, end=end, *args, **kwargs)
|
196
|
+
|
197
|
+
if extra:
|
198
|
+
self._print(msg=extra, end=end, json=True, indent=4)
|
199
|
+
|
200
|
+
@cached_property
|
201
|
+
def stack_level(self) -> int:
|
202
|
+
"""Cached property to retrieve the current stack level."""
|
203
|
+
return self.stack_tracker.record_end()
|
204
|
+
|
205
|
+
@override
|
206
|
+
def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False, stacklevel=None):
|
207
|
+
"""
|
208
|
+
Custom logging implementation with enhanced exception handling.
|
209
|
+
|
210
|
+
Overrides the standard logging._log method to provide better exception
|
211
|
+
value extraction for Rich traceback integration while respecting log levels.
|
212
|
+
"""
|
213
|
+
stacklevel = stacklevel or self.stack_level
|
214
|
+
try:
|
215
|
+
fn, lno, func, sinfo = self.findCaller(stack_info, stacklevel)
|
216
|
+
except ValueError:
|
217
|
+
fn, lno, func, sinfo = "(unknown file)", 0, "(unknown function)", None
|
218
|
+
|
219
|
+
final_extra = extra or {}
|
220
|
+
|
221
|
+
if exc_info is not None:
|
222
|
+
exec_values = self._extract_exception_values(exc_info)
|
223
|
+
if exec_values:
|
224
|
+
final_extra = {**final_extra, "exec_values": exec_values}
|
225
|
+
|
226
|
+
record = self.makeRecord(
|
227
|
+
name=self.name,
|
228
|
+
level=level,
|
229
|
+
fn=fn,
|
230
|
+
lno=lno,
|
231
|
+
msg=msg,
|
232
|
+
args=args,
|
233
|
+
exc_info=None,
|
234
|
+
func=func,
|
235
|
+
extra=final_extra,
|
236
|
+
sinfo=sinfo,
|
237
|
+
)
|
238
|
+
|
239
|
+
self.handle(record)
|
240
|
+
|
241
|
+
def exit(self) -> None:
|
242
|
+
"""Clean up resources including queue listeners and console buffers."""
|
243
|
+
if hasattr(self, "queue_handler"):
|
244
|
+
self.queue_handler.flush()
|
245
|
+
self.stop_queue_listener()
|
246
|
+
|
247
|
+
self.console_buffer.close()
|
248
|
+
|
249
|
+
# endregion Enhanced Print Methods
|
@@ -0,0 +1,64 @@
|
|
1
|
+
from logging import DEBUG, Logger
|
2
|
+
from logging.handlers import QueueHandler, RotatingFileHandler
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from rich.text import Text
|
6
|
+
from rich.theme import Theme
|
7
|
+
|
8
|
+
from .._console_junk import ConsoleBuffering, ConsoleHandler
|
9
|
+
from ._base_logger import BaseLogger
|
10
|
+
from ._sub_logger import SubConsoleLogger
|
11
|
+
|
12
|
+
class ConsoleLogger(Logger, BaseLogger):
|
13
|
+
|
14
|
+
name: str
|
15
|
+
level: int
|
16
|
+
file: bool
|
17
|
+
queue_handler: QueueHandler
|
18
|
+
buffer_handler: ConsoleBuffering
|
19
|
+
console_handler: ConsoleHandler
|
20
|
+
file_handler: RotatingFileHandler
|
21
|
+
sub_logger: dict[str, "SubConsoleLogger[ConsoleLogger]"]
|
22
|
+
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
theme: Theme | None = None,
|
26
|
+
name: str = "ConsoleLogger",
|
27
|
+
level: int = DEBUG,
|
28
|
+
disabled: bool = True,
|
29
|
+
queue_handler: bool = False,
|
30
|
+
buffering: bool = False,
|
31
|
+
file: bool = False,
|
32
|
+
console: bool = True,
|
33
|
+
style_disabled: bool = False,
|
34
|
+
logger_mode: bool = True,
|
35
|
+
*args,
|
36
|
+
**kwargs,
|
37
|
+
) -> None:
|
38
|
+
"""
|
39
|
+
A general purpose console logger that that by default is a wrapper around both Rich and Python's logging module.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
theme (Theme | None): The theme for the logger.
|
43
|
+
name (str): The name of the logger.
|
44
|
+
level (int): The logging level.
|
45
|
+
disabled (bool): Whether the logger is disabled.
|
46
|
+
queue_handler (bool): Whether to use a queue handler.
|
47
|
+
buffering (bool): Whether to use buffering.
|
48
|
+
file (bool): Whether to log to a file.
|
49
|
+
style_disabled (bool): Whether to disable styling.
|
50
|
+
logger_mode (bool): Whether to enable logger mode.
|
51
|
+
"""
|
52
|
+
# fmt: off
|
53
|
+
def debug(self, msg: object, *args, **kwargs: Any) -> None: ...
|
54
|
+
def info(self, msg: object, *args, **kwargs: Any) -> None: ...
|
55
|
+
def warning(self, msg: object, *args, **kwargs: Any) -> None: ...
|
56
|
+
def error(self, msg: object, *args, **kwargs: Any) -> None: ...
|
57
|
+
def success(self, msg: str, *args, **kwargs: Any) -> None: ...
|
58
|
+
def failure(self, msg: str, *args, **kwargs: Any) -> None: ...
|
59
|
+
def verbose(self, msg: str, *args, **kwargs: Any) -> None: ...
|
60
|
+
def set_base_level(self, level: int) -> None: ...
|
61
|
+
def input(self, msg: str, style: str = "info") -> str: ...
|
62
|
+
def trigger_buffer_flush(self) -> str | Text: ...
|
63
|
+
def print(self, msg: object, end: str="\n", exc_info=None, extra: dict | None = None, *args, **kwargs) -> None | str: ...
|
64
|
+
def exit(self) -> None: ...
|
@@ -0,0 +1,141 @@
|
|
1
|
+
from logging import DEBUG
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Any, override
|
4
|
+
|
5
|
+
from rich.theme import Theme
|
6
|
+
|
7
|
+
from bear_utils.constants.date_related import DATE_TIME_FORMAT
|
8
|
+
|
9
|
+
from .._common import FIVE_MEGABYTES
|
10
|
+
from .._styles import LoggerExtraInfo
|
11
|
+
from ._console_logger import ConsoleLogger
|
12
|
+
from ._sub_logger import SubConsoleLogger
|
13
|
+
|
14
|
+
|
15
|
+
class FileLogger(ConsoleLogger):
|
16
|
+
"""
|
17
|
+
A file-based logger that writes styled log messages to files.
|
18
|
+
|
19
|
+
Combines Python's logging framework with Rich console styling, but outputs
|
20
|
+
to files instead of console. Supports file rotation, custom formatting,
|
21
|
+
and maintains the same interface as other loggers.
|
22
|
+
|
23
|
+
Features:
|
24
|
+
- File logging with rotation
|
25
|
+
- Rich-style method generation (info, error, debug, etc.)
|
26
|
+
- Consistent print() interface
|
27
|
+
- Exception tracebacks in file format
|
28
|
+
- JSON logging support
|
29
|
+
|
30
|
+
Example:
|
31
|
+
logger = FileLogger.get_instance(
|
32
|
+
init=True,
|
33
|
+
name="FileLogger",
|
34
|
+
file_path="app.log",
|
35
|
+
max_bytes=10*1024*1024,
|
36
|
+
backup_count=5
|
37
|
+
)
|
38
|
+
logger.info("This goes to the file")
|
39
|
+
logger.error("This error is logged to file")
|
40
|
+
"""
|
41
|
+
|
42
|
+
def __init__(
|
43
|
+
self,
|
44
|
+
theme: Theme | None = None,
|
45
|
+
name: str = "FileLogger",
|
46
|
+
level: int = DEBUG,
|
47
|
+
file_path: str = "app.log",
|
48
|
+
max_bytes: int = FIVE_MEGABYTES,
|
49
|
+
backup_count: int = 5,
|
50
|
+
*args,
|
51
|
+
**kwargs,
|
52
|
+
) -> None:
|
53
|
+
self.file_path = Path(file_path) # TODO: Add more options for filename patterns (timestamps, process IDs, etc.)
|
54
|
+
self.max_bytes = max_bytes
|
55
|
+
self.backup_count = backup_count
|
56
|
+
ConsoleLogger.__init__(
|
57
|
+
self,
|
58
|
+
name=name,
|
59
|
+
level=level,
|
60
|
+
file=True,
|
61
|
+
console=False,
|
62
|
+
queue_handler=kwargs.pop("queue_handler", False),
|
63
|
+
file_path=self.file_path,
|
64
|
+
max_bytes=self.max_bytes,
|
65
|
+
backup_count=self.backup_count,
|
66
|
+
theme=theme,
|
67
|
+
style_disabled=True,
|
68
|
+
logger_mode=True,
|
69
|
+
**kwargs,
|
70
|
+
)
|
71
|
+
|
72
|
+
@override
|
73
|
+
def get_sub_logger(self, namespace: str, **kwargs: Any) -> SubConsoleLogger:
|
74
|
+
return SubConsoleLogger(self, namespace, **kwargs)
|
75
|
+
|
76
|
+
@override
|
77
|
+
def replacement_method(self, msg: object, *args, **kwargs) -> None:
|
78
|
+
"""Handle logging method calls with proper file logging integration."""
|
79
|
+
extra: LoggerExtraInfo = kwargs.pop("injected_extra")
|
80
|
+
if kwargs.get("extra"):
|
81
|
+
extra.update(kwargs.pop("extra"))
|
82
|
+
if extra.get("namespace"):
|
83
|
+
msg = f"<{extra.get('namespace')}> {msg}"
|
84
|
+
if self.stack_tracker.not_set:
|
85
|
+
self.stack_tracker.record_start()
|
86
|
+
|
87
|
+
self.log(
|
88
|
+
level=extra.get("log_level", DEBUG),
|
89
|
+
msg=msg,
|
90
|
+
extra=extra,
|
91
|
+
*args,
|
92
|
+
**kwargs,
|
93
|
+
)
|
94
|
+
|
95
|
+
def print(self, msg: object, end: str = "\n", exc_info=None, extra: dict | None = None, *args, **kwargs) -> None:
|
96
|
+
"""
|
97
|
+
Print a message to the file with proper formatting.
|
98
|
+
|
99
|
+
Maintains the same interface as other loggers but writes to file instead of console.
|
100
|
+
"""
|
101
|
+
try:
|
102
|
+
# For file logging, we want to use the logging system rather than direct file writing
|
103
|
+
# This ensures proper formatting, rotation, etc.
|
104
|
+
if exc_info is not None:
|
105
|
+
# Log with exception info
|
106
|
+
self.error(f"{msg}", exc_info=exc_info, extra=extra)
|
107
|
+
else:
|
108
|
+
# Regular info log
|
109
|
+
self.info(f"{msg}", extra=extra)
|
110
|
+
|
111
|
+
if extra:
|
112
|
+
# Log extra data as JSON-like format
|
113
|
+
self.info(f"Extra data: {extra}")
|
114
|
+
except Exception as e:
|
115
|
+
print(f"FileLogger: Failed to write to log file. Message: {msg}, Error: {e}")
|
116
|
+
|
117
|
+
def get_file_size(self) -> int:
|
118
|
+
"""Get current size of the log file in bytes."""
|
119
|
+
if self.file_path.exists():
|
120
|
+
return self.file_path.stat().st_size
|
121
|
+
return 0
|
122
|
+
|
123
|
+
def get_file_path(self) -> Path:
|
124
|
+
"""Get the current log file path."""
|
125
|
+
return self.file_path
|
126
|
+
|
127
|
+
def rotate_file(self) -> None:
|
128
|
+
"""Manually trigger file rotation."""
|
129
|
+
if hasattr(self.file_handler, "doRollover"):
|
130
|
+
self.file_handler.doRollover()
|
131
|
+
|
132
|
+
@override
|
133
|
+
def exit(self) -> None:
|
134
|
+
"""Clean up file resources."""
|
135
|
+
if hasattr(self, "file_handler"):
|
136
|
+
self.file_handler.flush()
|
137
|
+
self.file_handler.close()
|
138
|
+
self.removeHandler(self.file_handler)
|
139
|
+
|
140
|
+
# Call parent exit
|
141
|
+
super().exit()
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import threading
|
2
|
+
from logging import addLevelName
|
3
|
+
from typing import Literal
|
4
|
+
|
5
|
+
ERROR: Literal[40] = 40
|
6
|
+
WARNING: Literal[30] = 30
|
7
|
+
WARN: Literal[30] = WARNING
|
8
|
+
INFO: Literal[20] = 20
|
9
|
+
DEBUG: Literal[10] = 10
|
10
|
+
NOTSET: Literal[0] = 0
|
11
|
+
|
12
|
+
_levelToName = {
|
13
|
+
ERROR: "ERROR",
|
14
|
+
WARNING: "WARNING",
|
15
|
+
INFO: "INFO",
|
16
|
+
DEBUG: "DEBUG",
|
17
|
+
NOTSET: "NOTSET",
|
18
|
+
}
|
19
|
+
_nameToLevel = {
|
20
|
+
"ERROR": ERROR,
|
21
|
+
"WARN": WARNING,
|
22
|
+
"WARNING": WARNING,
|
23
|
+
"INFO": INFO,
|
24
|
+
"DEBUG": DEBUG,
|
25
|
+
"NOTSET": NOTSET,
|
26
|
+
}
|
27
|
+
|
28
|
+
_lock = threading.RLock()
|
29
|
+
|
30
|
+
|
31
|
+
def lvl_exists(level: int | str) -> bool:
|
32
|
+
"""Check if a logging level already exists."""
|
33
|
+
with _lock:
|
34
|
+
level = check_level(level, fail=False)
|
35
|
+
return level in _levelToName
|
36
|
+
|
37
|
+
|
38
|
+
def add_level_name(level: int, name: str) -> None:
|
39
|
+
"""Add a custom logging level name."""
|
40
|
+
with _lock:
|
41
|
+
if level in _levelToName:
|
42
|
+
raise ValueError(f"Level {level} already exists with name {_levelToName[level]}")
|
43
|
+
_levelToName[level] = name.upper()
|
44
|
+
_nameToLevel[name.upper()] = level
|
45
|
+
addLevelName(level, name)
|
46
|
+
|
47
|
+
|
48
|
+
def check_level(level: int | str, fail: bool = True) -> int:
|
49
|
+
"""Validate and normalize logging level to integer."""
|
50
|
+
if isinstance(level, str) and level.upper() in _nameToLevel:
|
51
|
+
return _nameToLevel[level.upper()]
|
52
|
+
if isinstance(level, int) and level in _levelToName:
|
53
|
+
return level
|
54
|
+
if fail:
|
55
|
+
if not isinstance(level, (int, str)):
|
56
|
+
raise TypeError(f"Level must be int or str, got {type(level).__name__}: {level!r}")
|
57
|
+
raise ValueError(f"Invalid logging level: {level!r}. Valid levels are: {list(_nameToLevel.keys())}")
|
58
|
+
return 999 # Return a high value to indicate invalid level
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from logging import Logger
|
2
|
+
|
3
|
+
from ._base_logger import BaseLogger
|
4
|
+
|
5
|
+
|
6
|
+
class ExtraBaseLogger(Logger, BaseLogger):
|
7
|
+
"""
|
8
|
+
Base logger class that extends the standard logging.Logger and BaseLogger.
|
9
|
+
This class is intended to be used as a base for custom loggers that require
|
10
|
+
additional functionality or attributes.
|
11
|
+
"""
|
12
|
+
|
13
|
+
def __init__(self, name, level=0, **kwargs):
|
14
|
+
"""
|
15
|
+
Initialize the ExtraBaseLogger with a name and level.
|
16
|
+
"""
|
17
|
+
super().__init__(name, level)
|
18
|
+
BaseLogger.__init__(self, **kwargs)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
"""
|
2
|
+
Logger adapter module that provides an alternative to SubLogger using standard library tools.
|
3
|
+
|
4
|
+
This implementation shows how to use LoggerAdapter and contextvars to maintain
|
5
|
+
all the functionality of the existing SubLogger while reducing complexity.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from functools import partial
|
9
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
10
|
+
|
11
|
+
from rich.text import Text
|
12
|
+
|
13
|
+
from .._styles import LOGGER_METHODS, LoggerExtraInfo
|
14
|
+
from ._level_sin import check_level
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from ._base_logger import BaseLogger
|
18
|
+
|
19
|
+
T = TypeVar("T", bound="BaseLogger")
|
20
|
+
|
21
|
+
|
22
|
+
class SubConsoleLogger(Generic[T]):
|
23
|
+
def __init__(self, logger: T, namespace: str, **kwargs) -> None:
|
24
|
+
"""
|
25
|
+
Initialize the SubConsoleLogger with a logger and a namespace.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
logger: The underlying logger to wrap with a namespace.
|
29
|
+
namespace: The namespace to prefix log messages with
|
30
|
+
"""
|
31
|
+
self.logger: T = logger
|
32
|
+
self._level: int = kwargs.get("level", None) or logger._level
|
33
|
+
self.logger_mode = logger.logger_mode
|
34
|
+
self.namespace: str = namespace
|
35
|
+
self._setup(self.namespace)
|
36
|
+
|
37
|
+
def print(
|
38
|
+
self,
|
39
|
+
msg: object,
|
40
|
+
end: str = "\n",
|
41
|
+
exc_info: Any = None,
|
42
|
+
extra: dict | None = None,
|
43
|
+
*args,
|
44
|
+
**kwargs,
|
45
|
+
) -> None | str:
|
46
|
+
"""
|
47
|
+
Print a message to the console with the specified formatting.
|
48
|
+
|
49
|
+
This method allows for printing messages with additional context and formatting.
|
50
|
+
"""
|
51
|
+
return self.logger.print(msg, end=end, exc_info=exc_info, extra=extra, **kwargs)
|
52
|
+
|
53
|
+
def set_sub_level(self, level: int) -> None:
|
54
|
+
"""
|
55
|
+
Set the logging level for the logger.
|
56
|
+
|
57
|
+
This method allows changing the logging level dynamically.
|
58
|
+
"""
|
59
|
+
self._level = level
|
60
|
+
|
61
|
+
def filter_by_level(self, level: int | str) -> bool:
|
62
|
+
"""
|
63
|
+
Filter method to determine if a message should be logged based on its level.
|
64
|
+
|
65
|
+
This method checks if the provided level is greater than or equal to the logger's level.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
level (int | str): The logging level of the message.
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
bool: True if the message should be logged, False otherwise.
|
72
|
+
"""
|
73
|
+
level = check_level(level)
|
74
|
+
bool_check = level >= self._level
|
75
|
+
return bool_check
|
76
|
+
|
77
|
+
def trigger_buffer_flush(self) -> str | Text:
|
78
|
+
"""
|
79
|
+
Flush buffered messages to console output.
|
80
|
+
|
81
|
+
This method is used to ensure that any buffered log messages are printed
|
82
|
+
to the console, similar to the SubLogger's buffer flush functionality.
|
83
|
+
"""
|
84
|
+
return self.logger.trigger_buffer_flush()
|
85
|
+
|
86
|
+
def _setup(self, name: str) -> None:
|
87
|
+
"""
|
88
|
+
Get an attribute from the logger.
|
89
|
+
|
90
|
+
This allows for accessing logger methods directly.
|
91
|
+
"""
|
92
|
+
filter_func = self.filter_by_level
|
93
|
+
for style_name, og_extra in LOGGER_METHODS.items():
|
94
|
+
extra: LoggerExtraInfo = og_extra.copy()
|
95
|
+
|
96
|
+
extra["namespace"] = name
|
97
|
+
|
98
|
+
setattr(
|
99
|
+
self,
|
100
|
+
style_name,
|
101
|
+
partial(
|
102
|
+
self.logger.replacement_method,
|
103
|
+
injected_extra=extra,
|
104
|
+
filter_func=filter_func,
|
105
|
+
),
|
106
|
+
)
|
107
|
+
|
108
|
+
def __repr__(self) -> str:
|
109
|
+
"""String representation of the adapter."""
|
110
|
+
return f"ConsoleAdapter(namespace={self.namespace}"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
"""
|
2
|
+
Logger adapter module that provides an alternative to SubLogger using standard library tools.
|
3
|
+
|
4
|
+
This implementation shows how to use LoggerAdapter and contextvars to maintain
|
5
|
+
all the functionality of the existing SubLogger while reducing complexity.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Generic, TypeVar
|
9
|
+
|
10
|
+
from ._base_logger import BaseLogger
|
11
|
+
|
12
|
+
T = TypeVar("T", bound=BaseLogger)
|
13
|
+
|
14
|
+
class SubConsoleLogger(Generic[T]):
|
15
|
+
"""
|
16
|
+
Enhanced logger adapter that supports style-based logging and namespace prefixing.
|
17
|
+
|
18
|
+
This class provides an alternative to the SubLogger implementation while
|
19
|
+
maintaining compatibility with the existing ConsoleLogger.
|
20
|
+
"""
|
21
|
+
|
22
|
+
logger: T
|
23
|
+
_level: int
|
24
|
+
namespace: str
|
25
|
+
extra: dict[str, Any] | None
|
26
|
+
log_level: int
|
27
|
+
# fmt: off
|
28
|
+
def __init__(self, logger: T, namespace: str, **kwargs) -> None: ...
|
29
|
+
def set_sub_level(self, level: int) -> None: ...
|
30
|
+
def success(self, msg: object, *args, **kwargs) -> None: ...
|
31
|
+
def failure(self, msg: object, *args, **kwargs) -> None: ...
|
32
|
+
def verbose(self, msg: object, *args, **kwargs) -> None: ...
|
33
|
+
def info(self, msg: object, *args, **kwargs) -> None: ...
|
34
|
+
def debug(self, msg: object, *args, **kwargs) -> None: ...
|
35
|
+
def warning(self, msg: object, *args, **kwargs) -> None: ...
|
36
|
+
def error(self, msg: object, *args, **kwargs) -> None: ...
|
37
|
+
def print(self, msg: object, end: str = "\n", exc_info: Any = None, extra: dict | None = None, *args, **kwargs: Any) -> None | str: ...
|
38
|
+
def __repr__(self) -> str: ...
|