gllm-core-binary 0.4.4__py3-none-manylinux_2_31_x86_64.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.
- gllm_core/__init__.py +1 -0
- gllm_core/__init__.pyi +0 -0
- gllm_core/adapters/__init__.py +5 -0
- gllm_core/adapters/__init__.pyi +3 -0
- gllm_core/adapters/tool/__init__.py +6 -0
- gllm_core/adapters/tool/__init__.pyi +4 -0
- gllm_core/adapters/tool/google_adk.py +91 -0
- gllm_core/adapters/tool/google_adk.pyi +23 -0
- gllm_core/adapters/tool/langchain.py +130 -0
- gllm_core/adapters/tool/langchain.pyi +31 -0
- gllm_core/constants.py +55 -0
- gllm_core/constants.pyi +36 -0
- gllm_core/event/__init__.py +6 -0
- gllm_core/event/__init__.pyi +4 -0
- gllm_core/event/event_emitter.py +211 -0
- gllm_core/event/event_emitter.pyi +155 -0
- gllm_core/event/handler/__init__.py +7 -0
- gllm_core/event/handler/__init__.pyi +5 -0
- gllm_core/event/handler/console_event_handler.py +48 -0
- gllm_core/event/handler/console_event_handler.pyi +32 -0
- gllm_core/event/handler/event_handler.py +89 -0
- gllm_core/event/handler/event_handler.pyi +51 -0
- gllm_core/event/handler/print_event_handler.py +130 -0
- gllm_core/event/handler/print_event_handler.pyi +33 -0
- gllm_core/event/handler/stream_event_handler.py +85 -0
- gllm_core/event/handler/stream_event_handler.pyi +62 -0
- gllm_core/event/hook/__init__.py +5 -0
- gllm_core/event/hook/__init__.pyi +3 -0
- gllm_core/event/hook/event_hook.py +30 -0
- gllm_core/event/hook/event_hook.pyi +18 -0
- gllm_core/event/hook/json_stringify_event_hook.py +32 -0
- gllm_core/event/hook/json_stringify_event_hook.pyi +16 -0
- gllm_core/event/messenger.py +133 -0
- gllm_core/event/messenger.pyi +66 -0
- gllm_core/schema/__init__.py +8 -0
- gllm_core/schema/__init__.pyi +6 -0
- gllm_core/schema/chunk.py +148 -0
- gllm_core/schema/chunk.pyi +66 -0
- gllm_core/schema/component.py +546 -0
- gllm_core/schema/component.pyi +205 -0
- gllm_core/schema/event.py +50 -0
- gllm_core/schema/event.pyi +33 -0
- gllm_core/schema/schema_generator.py +150 -0
- gllm_core/schema/schema_generator.pyi +35 -0
- gllm_core/schema/tool.py +418 -0
- gllm_core/schema/tool.pyi +198 -0
- gllm_core/utils/__init__.py +32 -0
- gllm_core/utils/__init__.pyi +13 -0
- gllm_core/utils/analyzer.py +256 -0
- gllm_core/utils/analyzer.pyi +123 -0
- gllm_core/utils/binary_handler_factory.py +99 -0
- gllm_core/utils/binary_handler_factory.pyi +62 -0
- gllm_core/utils/chunk_metadata_merger.py +102 -0
- gllm_core/utils/chunk_metadata_merger.pyi +41 -0
- gllm_core/utils/concurrency.py +184 -0
- gllm_core/utils/concurrency.pyi +94 -0
- gllm_core/utils/event_formatter.py +69 -0
- gllm_core/utils/event_formatter.pyi +30 -0
- gllm_core/utils/google_sheets.py +115 -0
- gllm_core/utils/google_sheets.pyi +18 -0
- gllm_core/utils/imports.py +91 -0
- gllm_core/utils/imports.pyi +42 -0
- gllm_core/utils/logger_manager.py +339 -0
- gllm_core/utils/logger_manager.pyi +176 -0
- gllm_core/utils/main_method_resolver.py +185 -0
- gllm_core/utils/main_method_resolver.pyi +54 -0
- gllm_core/utils/merger_method.py +130 -0
- gllm_core/utils/merger_method.pyi +49 -0
- gllm_core/utils/retry.py +258 -0
- gllm_core/utils/retry.pyi +41 -0
- gllm_core/utils/similarity.py +29 -0
- gllm_core/utils/similarity.pyi +10 -0
- gllm_core/utils/validation.py +26 -0
- gllm_core/utils/validation.pyi +12 -0
- gllm_core_binary-0.4.4.dist-info/METADATA +177 -0
- gllm_core_binary-0.4.4.dist-info/RECORD +78 -0
- gllm_core_binary-0.4.4.dist-info/WHEEL +5 -0
- gllm_core_binary-0.4.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""Defines a logger manager to manage logging configuration across the Gen AI application components.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Dimitrij Ray (dimitrij.ray@gdplabs.id)
|
|
5
|
+
Henry Wicaksono (henry.wicaksono@gdplabs.id)
|
|
6
|
+
Hermes Vincentius Gani (hermes.v.gani@gdplabs.id)
|
|
7
|
+
|
|
8
|
+
References:
|
|
9
|
+
NONE
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import threading
|
|
15
|
+
|
|
16
|
+
from pythonjsonlogger.core import LogRecord
|
|
17
|
+
from pythonjsonlogger.json import JsonFormatter
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.highlighter import NullHighlighter
|
|
20
|
+
from rich.logging import RichHandler
|
|
21
|
+
|
|
22
|
+
from gllm_core.constants import LogMode
|
|
23
|
+
|
|
24
|
+
DEFAULT_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
|
|
25
|
+
TEXT_COLOR_MAP = {
|
|
26
|
+
logging.DEBUG: "green",
|
|
27
|
+
logging.INFO: "blue",
|
|
28
|
+
logging.WARNING: "yellow",
|
|
29
|
+
logging.ERROR: "red",
|
|
30
|
+
logging.CRITICAL: "bold black on red",
|
|
31
|
+
}
|
|
32
|
+
LOG_FORMAT_KEY = "LOG_FORMAT"
|
|
33
|
+
RICH_CLOSE_TAG = "[/"
|
|
34
|
+
JSON_LOG_FIELDS = ["timestamp", "name", "level", "message"]
|
|
35
|
+
JSON_ERROR_FIELDS_MAP = {
|
|
36
|
+
"exc_info": "message",
|
|
37
|
+
"stack_info": "stacktrace",
|
|
38
|
+
"error_code": "code",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TextRichHandler(RichHandler):
|
|
43
|
+
"""Custom RichHandler that applies specific colors and log format."""
|
|
44
|
+
|
|
45
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
46
|
+
"""Emits a log record with custom coloring.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
record (logging.LogRecord): The log record to emit.
|
|
50
|
+
"""
|
|
51
|
+
color = TEXT_COLOR_MAP.get(record.levelno, "white")
|
|
52
|
+
|
|
53
|
+
name, msg = record.name, record.getMessage()
|
|
54
|
+
msg = msg.replace(RICH_CLOSE_TAG, rf"\{RICH_CLOSE_TAG}")
|
|
55
|
+
|
|
56
|
+
record.msg = f"[{color}][{name}] {msg}[/]"
|
|
57
|
+
record.args = None
|
|
58
|
+
|
|
59
|
+
super().emit(record)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class SimpleRichHandler(logging.StreamHandler):
|
|
63
|
+
"""Custom StreamHandler that utilizes Rich only to apply colors, without columns or Rich formatting.
|
|
64
|
+
|
|
65
|
+
Attributes:
|
|
66
|
+
console (Console): The Rich Console object to use for printing.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
70
|
+
"""Initializes a new instance of the SimpleRichHandler class."""
|
|
71
|
+
super().__init__(*args, **kwargs)
|
|
72
|
+
self.console = Console(file=self.stream)
|
|
73
|
+
|
|
74
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
75
|
+
"""Emits a log record with simple Rich coloring.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
record (logging.LogRecord): The log record to emit.
|
|
79
|
+
"""
|
|
80
|
+
color = TEXT_COLOR_MAP.get(record.levelno, "white")
|
|
81
|
+
msg = self.format(record)
|
|
82
|
+
msg = msg.replace(RICH_CLOSE_TAG, rf"\{RICH_CLOSE_TAG}")
|
|
83
|
+
|
|
84
|
+
self.console.print(f"[{color}]{msg}[/]", markup=True, highlight=False)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class AppJSONFormatter(JsonFormatter):
|
|
88
|
+
"""JSON formatter that groups error-related fields under an 'error' key.
|
|
89
|
+
|
|
90
|
+
This formatter renames the following fields when present:
|
|
91
|
+
1. exc_info -> error.message
|
|
92
|
+
2. stack_info -> error.stacktrace
|
|
93
|
+
3. error_code -> error.code
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def process_log_record(self, log_record: LogRecord) -> LogRecord:
|
|
97
|
+
"""Process log record to group and rename error-related fields.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
log_record (LogRecord): The original log record.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
LogRecord: The processed log record.
|
|
104
|
+
"""
|
|
105
|
+
record = super().process_log_record(log_record)
|
|
106
|
+
logged_record = {key: value for key, value in record.items() if key in JSON_LOG_FIELDS}
|
|
107
|
+
error_payload: dict[str, str] = dict.fromkeys(JSON_ERROR_FIELDS_MAP.values(), "")
|
|
108
|
+
|
|
109
|
+
for key, new_key in JSON_ERROR_FIELDS_MAP.items():
|
|
110
|
+
if value := record.get(key):
|
|
111
|
+
error_payload[new_key] = value
|
|
112
|
+
|
|
113
|
+
if any(error_payload.values()):
|
|
114
|
+
logged_record["error"] = error_payload
|
|
115
|
+
|
|
116
|
+
return logged_record
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
LOG_FORMAT_HANDLER_MAP = {
|
|
120
|
+
LogMode.TEXT: TextRichHandler(
|
|
121
|
+
markup=True,
|
|
122
|
+
omit_repeated_times=False,
|
|
123
|
+
highlighter=NullHighlighter(),
|
|
124
|
+
),
|
|
125
|
+
LogMode.SIMPLE: SimpleRichHandler(),
|
|
126
|
+
LogMode.JSON: logging.StreamHandler(),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
LOG_FORMAT_MAP = {
|
|
130
|
+
LogMode.TEXT: "%(message)s",
|
|
131
|
+
LogMode.SIMPLE: "[%(asctime)s.%(msecs)03d %(name)s %(levelname)s] %(message)s",
|
|
132
|
+
LogMode.JSON: "{asctime} {name} {levelname} {message}",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class LoggerManager:
|
|
137
|
+
"""A singleton class to manage logging configuration.
|
|
138
|
+
|
|
139
|
+
This class ensures that the root logger is initialized only once and is used across the application.
|
|
140
|
+
|
|
141
|
+
Basic usage:
|
|
142
|
+
The `LoggerManager` can be used to get a logger instance as follows:
|
|
143
|
+
```python
|
|
144
|
+
logger = LoggerManager().get_logger()
|
|
145
|
+
logger.info("This is an info message")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Set logging configuration:
|
|
149
|
+
The `LoggerManager` also provides capabilities to set the logging configuration:
|
|
150
|
+
```python
|
|
151
|
+
manager = LoggerManager()
|
|
152
|
+
manager.set_level(logging.DEBUG)
|
|
153
|
+
manager.set_log_format(custom_log_format)
|
|
154
|
+
manager.set_date_format(custom_date_format)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Add custom handlers:
|
|
158
|
+
The `LoggerManager` also provides capabilities to add custom handlers to the root logger:
|
|
159
|
+
```python
|
|
160
|
+
manager = LoggerManager()
|
|
161
|
+
handler = logging.FileHandler("app.log")
|
|
162
|
+
manager.add_handler(handler)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Extra error information:
|
|
166
|
+
When logging errors, extra error information can be added as follows:
|
|
167
|
+
```python
|
|
168
|
+
logger.error("I am dead!", extra={"error_code": "ERR_CONN_REFUSED"})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Logging modes:
|
|
172
|
+
The `LoggerManager` supports three logging modes:
|
|
173
|
+
|
|
174
|
+
1. Text: Logs in a human-readable format with RichHandler column-based formatting.
|
|
175
|
+
Used when the `LOG_FORMAT` environment variable is set to "text".
|
|
176
|
+
Output example:
|
|
177
|
+
```log
|
|
178
|
+
2025-10-08T09:26:16 DEBUG [LoggerName] This is a debug message.
|
|
179
|
+
2025-10-08T09:26:17 INFO [LoggerName] This is an info message.
|
|
180
|
+
2025-10-08T09:26:18 WARNING [LoggerName] This is a warning message.
|
|
181
|
+
2025-10-08T09:26:19 ERROR [LoggerName] This is an error message.
|
|
182
|
+
2025-10-08T09:26:20 CRITICAL [LoggerName] This is a critical message.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
2. Simple: Logs in a human-readable format with Rich colors but without columns-based formatting.
|
|
186
|
+
Used when the `LOG_FORMAT` environment variable is set to "simple".
|
|
187
|
+
Output example:
|
|
188
|
+
```log
|
|
189
|
+
[2025-10-08T09:26:16.123 LoggerName DEBUG] This is a debug message.
|
|
190
|
+
[2025-10-08T09:26:17.456 LoggerName INFO] This is an info message.
|
|
191
|
+
[2025-10-08T09:26:18.789 LoggerName WARNING] This is a warning message.
|
|
192
|
+
[2025-10-08T09:26:19.012 LoggerName ERROR] This is an error message.
|
|
193
|
+
[2025-10-08T09:26:20.345 LoggerName CRITICAL] This is a critical message.
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
3. JSON: Logs in a JSON format, recommended for easy parsing due to the structured nature of the log records.
|
|
197
|
+
Used when the `LOG_FORMAT` environment variable is set to "json".
|
|
198
|
+
Output example:
|
|
199
|
+
```log
|
|
200
|
+
{"timestamp": "2025-10-08T11:23:43+0700", "name": "LoggerName", "level": "DEBUG", "message": "..."}
|
|
201
|
+
{"timestamp": "2025-10-08T11:23:44+0700", "name": "LoggerName", "level": "INFO", "message": "..."}
|
|
202
|
+
{"timestamp": "2025-10-08T11:23:45+0700", "name": "LoggerName", "level": "WARNING", "message": "..."}
|
|
203
|
+
{"timestamp": "2025-10-08T11:23:46+0700", "name": "LoggerName", "level": "ERROR", "message": "..."}
|
|
204
|
+
{"timestamp": "2025-10-08T11:23:47+0700", "name": "LoggerName", "level": "CRITICAL", "message": "..."}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
When the `LOG_FORMAT` environment is not set, the `LoggerManager` defaults to "text" mode.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
_instance = None
|
|
211
|
+
_root_logger = None
|
|
212
|
+
_handlers = None
|
|
213
|
+
_formatter = None
|
|
214
|
+
_lock = threading.Lock()
|
|
215
|
+
|
|
216
|
+
def __new__(cls) -> "LoggerManager":
|
|
217
|
+
"""Initialize the singleton instance.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
LoggerManager: The singleton instance.
|
|
221
|
+
"""
|
|
222
|
+
with cls._lock:
|
|
223
|
+
if cls._instance is None:
|
|
224
|
+
cls._instance = super().__new__(cls)
|
|
225
|
+
cls._initialize_root_logger()
|
|
226
|
+
return cls._instance
|
|
227
|
+
|
|
228
|
+
# TODO: Refactor determining log mode via parameter instead of environment variable
|
|
229
|
+
@classmethod
|
|
230
|
+
def _initialize_root_logger(cls) -> None:
|
|
231
|
+
"""Initialize the root logger with default configuration."""
|
|
232
|
+
log_mode = os.getenv(LOG_FORMAT_KEY, LogMode.TEXT).lower()
|
|
233
|
+
|
|
234
|
+
cls._log_mode = log_mode if log_mode in LOG_FORMAT_HANDLER_MAP else LogMode.TEXT
|
|
235
|
+
cls._root_logger = logging.getLogger()
|
|
236
|
+
cls._root_logger.handlers.clear()
|
|
237
|
+
cls._formatter = cls._build_formatter()
|
|
238
|
+
|
|
239
|
+
default_handler = LOG_FORMAT_HANDLER_MAP[cls._log_mode]
|
|
240
|
+
default_handler.setFormatter(cls._formatter)
|
|
241
|
+
cls._handlers = [default_handler]
|
|
242
|
+
cls._root_logger.addHandler(default_handler)
|
|
243
|
+
cls._root_logger.setLevel(logging.INFO)
|
|
244
|
+
cls._root_logger.propagate = False
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def _build_formatter(cls, fmt: str | None = None, datefmt: str = DEFAULT_DATE_FORMAT) -> logging.Formatter:
|
|
248
|
+
"""Create a formatter based on current mode (JSON, TEXT, or SIMPLE).
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
fmt (str | None, optional): The format string. Defaults to None,
|
|
252
|
+
in which case the default log format is used.
|
|
253
|
+
datefmt (str, optional): The date format string. Defaults to DEFAULT_DATE_FORMAT.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
logging.Formatter: The formatter.
|
|
257
|
+
"""
|
|
258
|
+
fmt = fmt or LOG_FORMAT_MAP[cls._log_mode]
|
|
259
|
+
|
|
260
|
+
if not cls._log_mode == LogMode.JSON:
|
|
261
|
+
return logging.Formatter(fmt, datefmt)
|
|
262
|
+
|
|
263
|
+
return AppJSONFormatter(
|
|
264
|
+
fmt=fmt,
|
|
265
|
+
datefmt=datefmt,
|
|
266
|
+
style="{",
|
|
267
|
+
rename_fields={"asctime": "timestamp", "levelname": "level"},
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
def get_logger(self, name: str | None = None) -> logging.Logger:
|
|
271
|
+
"""Get a logger instance.
|
|
272
|
+
|
|
273
|
+
This method returns a logger instance that is a child of the root logger. If name is not provided,
|
|
274
|
+
the root logger will be returned instead.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
name (str | None, optional): The name of the child logger. If None, the root logger will be returned.
|
|
278
|
+
Defaults to None.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
logging.Logger: Configured logger instance.
|
|
282
|
+
"""
|
|
283
|
+
if name:
|
|
284
|
+
child = self._root_logger.getChild(name)
|
|
285
|
+
child.propagate = False
|
|
286
|
+
if not child.handlers:
|
|
287
|
+
for handler in self._handlers:
|
|
288
|
+
child.addHandler(handler)
|
|
289
|
+
return child
|
|
290
|
+
return self._root_logger
|
|
291
|
+
|
|
292
|
+
def set_level(self, level: int) -> None:
|
|
293
|
+
"""Set logging level for all loggers in the hierarchy.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
level (int): The logging level to set (e.g., logging.INFO, logging.DEBUG).
|
|
297
|
+
"""
|
|
298
|
+
self._root_logger.setLevel(level)
|
|
299
|
+
|
|
300
|
+
def set_log_format(self, log_format: str) -> None:
|
|
301
|
+
"""Set logging format for all loggers in the hierarchy.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
log_format (str): The log format to set.
|
|
305
|
+
"""
|
|
306
|
+
self._update_formatter(fmt=log_format)
|
|
307
|
+
|
|
308
|
+
def set_date_format(self, date_format: str) -> None:
|
|
309
|
+
"""Set date format for all loggers in the hierarchy.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
date_format (str): The date format to set.
|
|
313
|
+
"""
|
|
314
|
+
self._update_formatter(datefmt=date_format)
|
|
315
|
+
|
|
316
|
+
def add_handler(self, handler: logging.Handler) -> None:
|
|
317
|
+
"""Add a custom handler to the root logger.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
handler (logging.Handler): The handler to add to the root logger.
|
|
321
|
+
"""
|
|
322
|
+
handler.setFormatter(self._formatter)
|
|
323
|
+
self._root_logger.addHandler(handler)
|
|
324
|
+
self._handlers.append(handler)
|
|
325
|
+
|
|
326
|
+
def _update_formatter(self, fmt: str | None = None, datefmt: str | None = None) -> None:
|
|
327
|
+
"""Update formatter and apply to all handlers.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
fmt (str | None, optional): The format string. Defaults to None.
|
|
331
|
+
datefmt (str | None, optional): The date format string. Defaults to None.
|
|
332
|
+
"""
|
|
333
|
+
current_fmt_default = LOG_FORMAT_MAP[self._log_mode]
|
|
334
|
+
current_fmt = fmt if fmt is not None else getattr(self._formatter, "_fmt", current_fmt_default)
|
|
335
|
+
current_datefmt = datefmt if datefmt is not None else getattr(self._formatter, "datefmt", DEFAULT_DATE_FORMAT)
|
|
336
|
+
|
|
337
|
+
self._formatter = self._build_formatter(current_fmt, current_datefmt)
|
|
338
|
+
for handler in self._handlers:
|
|
339
|
+
handler.setFormatter(self._formatter)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from _typeshed import Incomplete
|
|
3
|
+
from gllm_core.constants import LogMode as LogMode
|
|
4
|
+
from pythonjsonlogger.core import LogRecord
|
|
5
|
+
from pythonjsonlogger.json import JsonFormatter
|
|
6
|
+
from rich.logging import RichHandler
|
|
7
|
+
|
|
8
|
+
DEFAULT_DATE_FORMAT: str
|
|
9
|
+
TEXT_COLOR_MAP: Incomplete
|
|
10
|
+
LOG_FORMAT_KEY: str
|
|
11
|
+
RICH_CLOSE_TAG: str
|
|
12
|
+
JSON_LOG_FIELDS: Incomplete
|
|
13
|
+
JSON_ERROR_FIELDS_MAP: Incomplete
|
|
14
|
+
|
|
15
|
+
class TextRichHandler(RichHandler):
|
|
16
|
+
"""Custom RichHandler that applies specific colors and log format."""
|
|
17
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
18
|
+
"""Emits a log record with custom coloring.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
record (logging.LogRecord): The log record to emit.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
class SimpleRichHandler(logging.StreamHandler):
|
|
25
|
+
"""Custom StreamHandler that utilizes Rich only to apply colors, without columns or Rich formatting.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
console (Console): The Rich Console object to use for printing.
|
|
29
|
+
"""
|
|
30
|
+
console: Incomplete
|
|
31
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
32
|
+
"""Initializes a new instance of the SimpleRichHandler class."""
|
|
33
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
34
|
+
"""Emits a log record with simple Rich coloring.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
record (logging.LogRecord): The log record to emit.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
class AppJSONFormatter(JsonFormatter):
|
|
41
|
+
"""JSON formatter that groups error-related fields under an 'error' key.
|
|
42
|
+
|
|
43
|
+
This formatter renames the following fields when present:
|
|
44
|
+
1. exc_info -> error.message
|
|
45
|
+
2. stack_info -> error.stacktrace
|
|
46
|
+
3. error_code -> error.code
|
|
47
|
+
"""
|
|
48
|
+
def process_log_record(self, log_record: LogRecord) -> LogRecord:
|
|
49
|
+
"""Process log record to group and rename error-related fields.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
log_record (LogRecord): The original log record.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
LogRecord: The processed log record.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
LOG_FORMAT_HANDLER_MAP: Incomplete
|
|
59
|
+
LOG_FORMAT_MAP: Incomplete
|
|
60
|
+
|
|
61
|
+
class LoggerManager:
|
|
62
|
+
'''A singleton class to manage logging configuration.
|
|
63
|
+
|
|
64
|
+
This class ensures that the root logger is initialized only once and is used across the application.
|
|
65
|
+
|
|
66
|
+
Basic usage:
|
|
67
|
+
The `LoggerManager` can be used to get a logger instance as follows:
|
|
68
|
+
```python
|
|
69
|
+
logger = LoggerManager().get_logger()
|
|
70
|
+
logger.info("This is an info message")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Set logging configuration:
|
|
74
|
+
The `LoggerManager` also provides capabilities to set the logging configuration:
|
|
75
|
+
```python
|
|
76
|
+
manager = LoggerManager()
|
|
77
|
+
manager.set_level(logging.DEBUG)
|
|
78
|
+
manager.set_log_format(custom_log_format)
|
|
79
|
+
manager.set_date_format(custom_date_format)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Add custom handlers:
|
|
83
|
+
The `LoggerManager` also provides capabilities to add custom handlers to the root logger:
|
|
84
|
+
```python
|
|
85
|
+
manager = LoggerManager()
|
|
86
|
+
handler = logging.FileHandler("app.log")
|
|
87
|
+
manager.add_handler(handler)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Extra error information:
|
|
91
|
+
When logging errors, extra error information can be added as follows:
|
|
92
|
+
```python
|
|
93
|
+
logger.error("I am dead!", extra={"error_code": "ERR_CONN_REFUSED"})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Logging modes:
|
|
97
|
+
The `LoggerManager` supports three logging modes:
|
|
98
|
+
|
|
99
|
+
1. Text: Logs in a human-readable format with RichHandler column-based formatting.
|
|
100
|
+
Used when the `LOG_FORMAT` environment variable is set to "text".
|
|
101
|
+
Output example:
|
|
102
|
+
```log
|
|
103
|
+
2025-10-08T09:26:16 DEBUG [LoggerName] This is a debug message.
|
|
104
|
+
2025-10-08T09:26:17 INFO [LoggerName] This is an info message.
|
|
105
|
+
2025-10-08T09:26:18 WARNING [LoggerName] This is a warning message.
|
|
106
|
+
2025-10-08T09:26:19 ERROR [LoggerName] This is an error message.
|
|
107
|
+
2025-10-08T09:26:20 CRITICAL [LoggerName] This is a critical message.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
2. Simple: Logs in a human-readable format with Rich colors but without columns-based formatting.
|
|
111
|
+
Used when the `LOG_FORMAT` environment variable is set to "simple".
|
|
112
|
+
Output example:
|
|
113
|
+
```log
|
|
114
|
+
[2025-10-08T09:26:16.123 LoggerName DEBUG] This is a debug message.
|
|
115
|
+
[2025-10-08T09:26:17.456 LoggerName INFO] This is an info message.
|
|
116
|
+
[2025-10-08T09:26:18.789 LoggerName WARNING] This is a warning message.
|
|
117
|
+
[2025-10-08T09:26:19.012 LoggerName ERROR] This is an error message.
|
|
118
|
+
[2025-10-08T09:26:20.345 LoggerName CRITICAL] This is a critical message.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
3. JSON: Logs in a JSON format, recommended for easy parsing due to the structured nature of the log records.
|
|
122
|
+
Used when the `LOG_FORMAT` environment variable is set to "json".
|
|
123
|
+
Output example:
|
|
124
|
+
```log
|
|
125
|
+
{"timestamp": "2025-10-08T11:23:43+0700", "name": "LoggerName", "level": "DEBUG", "message": "..."}
|
|
126
|
+
{"timestamp": "2025-10-08T11:23:44+0700", "name": "LoggerName", "level": "INFO", "message": "..."}
|
|
127
|
+
{"timestamp": "2025-10-08T11:23:45+0700", "name": "LoggerName", "level": "WARNING", "message": "..."}
|
|
128
|
+
{"timestamp": "2025-10-08T11:23:46+0700", "name": "LoggerName", "level": "ERROR", "message": "..."}
|
|
129
|
+
{"timestamp": "2025-10-08T11:23:47+0700", "name": "LoggerName", "level": "CRITICAL", "message": "..."}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
When the `LOG_FORMAT` environment is not set, the `LoggerManager` defaults to "text" mode.
|
|
133
|
+
'''
|
|
134
|
+
def __new__(cls) -> LoggerManager:
|
|
135
|
+
"""Initialize the singleton instance.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
LoggerManager: The singleton instance.
|
|
139
|
+
"""
|
|
140
|
+
def get_logger(self, name: str | None = None) -> logging.Logger:
|
|
141
|
+
"""Get a logger instance.
|
|
142
|
+
|
|
143
|
+
This method returns a logger instance that is a child of the root logger. If name is not provided,
|
|
144
|
+
the root logger will be returned instead.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
name (str | None, optional): The name of the child logger. If None, the root logger will be returned.
|
|
148
|
+
Defaults to None.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
logging.Logger: Configured logger instance.
|
|
152
|
+
"""
|
|
153
|
+
def set_level(self, level: int) -> None:
|
|
154
|
+
"""Set logging level for all loggers in the hierarchy.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
level (int): The logging level to set (e.g., logging.INFO, logging.DEBUG).
|
|
158
|
+
"""
|
|
159
|
+
def set_log_format(self, log_format: str) -> None:
|
|
160
|
+
"""Set logging format for all loggers in the hierarchy.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
log_format (str): The log format to set.
|
|
164
|
+
"""
|
|
165
|
+
def set_date_format(self, date_format: str) -> None:
|
|
166
|
+
"""Set date format for all loggers in the hierarchy.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
date_format (str): The date format to set.
|
|
170
|
+
"""
|
|
171
|
+
def add_handler(self, handler: logging.Handler) -> None:
|
|
172
|
+
"""Add a custom handler to the root logger.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
handler (logging.Handler): The handler to add to the root logger.
|
|
176
|
+
"""
|