hammad-python 0.0.30__py3-none-any.whl → 0.0.31__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.
- ham/__init__.py +10 -0
- {hammad_python-0.0.30.dist-info → hammad_python-0.0.31.dist-info}/METADATA +6 -32
- hammad_python-0.0.31.dist-info/RECORD +6 -0
- hammad/__init__.py +0 -84
- hammad/_internal.py +0 -256
- hammad/_main.py +0 -226
- hammad/cache/__init__.py +0 -40
- hammad/cache/base_cache.py +0 -181
- hammad/cache/cache.py +0 -169
- hammad/cache/decorators.py +0 -261
- hammad/cache/file_cache.py +0 -80
- hammad/cache/ttl_cache.py +0 -74
- hammad/cli/__init__.py +0 -33
- hammad/cli/animations.py +0 -573
- hammad/cli/plugins.py +0 -867
- hammad/cli/styles/__init__.py +0 -55
- hammad/cli/styles/settings.py +0 -139
- hammad/cli/styles/types.py +0 -358
- hammad/cli/styles/utils.py +0 -634
- hammad/data/__init__.py +0 -90
- hammad/data/collections/__init__.py +0 -49
- hammad/data/collections/collection.py +0 -326
- hammad/data/collections/indexes/__init__.py +0 -37
- hammad/data/collections/indexes/qdrant/__init__.py +0 -1
- hammad/data/collections/indexes/qdrant/index.py +0 -723
- hammad/data/collections/indexes/qdrant/settings.py +0 -94
- hammad/data/collections/indexes/qdrant/utils.py +0 -210
- hammad/data/collections/indexes/tantivy/__init__.py +0 -1
- hammad/data/collections/indexes/tantivy/index.py +0 -426
- hammad/data/collections/indexes/tantivy/settings.py +0 -40
- hammad/data/collections/indexes/tantivy/utils.py +0 -176
- hammad/data/configurations/__init__.py +0 -35
- hammad/data/configurations/configuration.py +0 -564
- hammad/data/models/__init__.py +0 -50
- hammad/data/models/extensions/__init__.py +0 -4
- hammad/data/models/extensions/pydantic/__init__.py +0 -42
- hammad/data/models/extensions/pydantic/converters.py +0 -759
- hammad/data/models/fields.py +0 -546
- hammad/data/models/model.py +0 -1078
- hammad/data/models/utils.py +0 -280
- hammad/data/sql/__init__.py +0 -24
- hammad/data/sql/database.py +0 -576
- hammad/data/sql/types.py +0 -127
- hammad/data/types/__init__.py +0 -75
- hammad/data/types/file.py +0 -431
- hammad/data/types/multimodal/__init__.py +0 -36
- hammad/data/types/multimodal/audio.py +0 -200
- hammad/data/types/multimodal/image.py +0 -182
- hammad/data/types/text.py +0 -1308
- hammad/formatting/__init__.py +0 -33
- hammad/formatting/json/__init__.py +0 -27
- hammad/formatting/json/converters.py +0 -158
- hammad/formatting/text/__init__.py +0 -63
- hammad/formatting/text/converters.py +0 -723
- hammad/formatting/text/markdown.py +0 -131
- hammad/formatting/yaml/__init__.py +0 -26
- hammad/formatting/yaml/converters.py +0 -5
- hammad/genai/__init__.py +0 -217
- hammad/genai/a2a/__init__.py +0 -32
- hammad/genai/a2a/workers.py +0 -552
- hammad/genai/agents/__init__.py +0 -59
- hammad/genai/agents/agent.py +0 -1973
- hammad/genai/agents/run.py +0 -1024
- hammad/genai/agents/types/__init__.py +0 -42
- hammad/genai/agents/types/agent_context.py +0 -13
- hammad/genai/agents/types/agent_event.py +0 -128
- hammad/genai/agents/types/agent_hooks.py +0 -220
- hammad/genai/agents/types/agent_messages.py +0 -31
- hammad/genai/agents/types/agent_response.py +0 -125
- hammad/genai/agents/types/agent_stream.py +0 -327
- hammad/genai/graphs/__init__.py +0 -125
- hammad/genai/graphs/_utils.py +0 -190
- hammad/genai/graphs/base.py +0 -1828
- hammad/genai/graphs/plugins.py +0 -316
- hammad/genai/graphs/types.py +0 -638
- hammad/genai/models/__init__.py +0 -1
- hammad/genai/models/embeddings/__init__.py +0 -43
- hammad/genai/models/embeddings/model.py +0 -226
- hammad/genai/models/embeddings/run.py +0 -163
- hammad/genai/models/embeddings/types/__init__.py +0 -37
- hammad/genai/models/embeddings/types/embedding_model_name.py +0 -75
- hammad/genai/models/embeddings/types/embedding_model_response.py +0 -76
- hammad/genai/models/embeddings/types/embedding_model_run_params.py +0 -66
- hammad/genai/models/embeddings/types/embedding_model_settings.py +0 -47
- hammad/genai/models/language/__init__.py +0 -57
- hammad/genai/models/language/model.py +0 -1098
- hammad/genai/models/language/run.py +0 -878
- hammad/genai/models/language/types/__init__.py +0 -40
- hammad/genai/models/language/types/language_model_instructor_mode.py +0 -47
- hammad/genai/models/language/types/language_model_messages.py +0 -28
- hammad/genai/models/language/types/language_model_name.py +0 -239
- hammad/genai/models/language/types/language_model_request.py +0 -127
- hammad/genai/models/language/types/language_model_response.py +0 -217
- hammad/genai/models/language/types/language_model_response_chunk.py +0 -56
- hammad/genai/models/language/types/language_model_settings.py +0 -89
- hammad/genai/models/language/types/language_model_stream.py +0 -600
- hammad/genai/models/language/utils/__init__.py +0 -28
- hammad/genai/models/language/utils/requests.py +0 -421
- hammad/genai/models/language/utils/structured_outputs.py +0 -135
- hammad/genai/models/model_provider.py +0 -4
- hammad/genai/models/multimodal.py +0 -47
- hammad/genai/models/reranking.py +0 -26
- hammad/genai/types/__init__.py +0 -1
- hammad/genai/types/base.py +0 -215
- hammad/genai/types/history.py +0 -290
- hammad/genai/types/tools.py +0 -507
- hammad/logging/__init__.py +0 -35
- hammad/logging/decorators.py +0 -834
- hammad/logging/logger.py +0 -1018
- hammad/mcp/__init__.py +0 -53
- hammad/mcp/client/__init__.py +0 -35
- hammad/mcp/client/client.py +0 -624
- hammad/mcp/client/client_service.py +0 -400
- hammad/mcp/client/settings.py +0 -178
- hammad/mcp/servers/__init__.py +0 -26
- hammad/mcp/servers/launcher.py +0 -1161
- hammad/runtime/__init__.py +0 -32
- hammad/runtime/decorators.py +0 -142
- hammad/runtime/run.py +0 -299
- hammad/service/__init__.py +0 -49
- hammad/service/create.py +0 -527
- hammad/service/decorators.py +0 -283
- hammad/types.py +0 -288
- hammad/typing/__init__.py +0 -435
- hammad/web/__init__.py +0 -43
- hammad/web/http/__init__.py +0 -1
- hammad/web/http/client.py +0 -944
- hammad/web/models.py +0 -275
- hammad/web/openapi/__init__.py +0 -1
- hammad/web/openapi/client.py +0 -740
- hammad/web/search/__init__.py +0 -1
- hammad/web/search/client.py +0 -1023
- hammad/web/utils.py +0 -472
- hammad_python-0.0.30.dist-info/RECORD +0 -135
- {hammad → ham}/py.typed +0 -0
- {hammad_python-0.0.30.dist-info → hammad_python-0.0.31.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.30.dist-info → hammad_python-0.0.31.dist-info}/licenses/LICENSE +0 -0
hammad/logging/logger.py
DELETED
@@ -1,1018 +0,0 @@
|
|
1
|
-
"""hammad.logging.logger"""
|
2
|
-
|
3
|
-
import logging as _logging
|
4
|
-
import inspect
|
5
|
-
from pathlib import Path
|
6
|
-
from dataclasses import dataclass, field
|
7
|
-
from typing import (
|
8
|
-
Literal,
|
9
|
-
TypeAlias,
|
10
|
-
Dict,
|
11
|
-
Optional,
|
12
|
-
Any,
|
13
|
-
Union,
|
14
|
-
List,
|
15
|
-
Callable,
|
16
|
-
Iterator,
|
17
|
-
)
|
18
|
-
from typing_extensions import TypedDict
|
19
|
-
from contextlib import contextmanager
|
20
|
-
|
21
|
-
from rich import get_console as get_rich_console
|
22
|
-
from rich.logging import RichHandler
|
23
|
-
from rich.progress import (
|
24
|
-
Progress,
|
25
|
-
TaskID,
|
26
|
-
SpinnerColumn,
|
27
|
-
TextColumn,
|
28
|
-
BarColumn,
|
29
|
-
TimeRemainingColumn,
|
30
|
-
)
|
31
|
-
from rich.spinner import Spinner
|
32
|
-
from rich.live import Live
|
33
|
-
|
34
|
-
from ..cli.styles.types import (
|
35
|
-
CLIStyleType,
|
36
|
-
)
|
37
|
-
from ..cli.styles.settings import CLIStyleRenderableSettings, CLIStyleBackgroundSettings
|
38
|
-
from ..cli.animations import (
|
39
|
-
animate_spinning,
|
40
|
-
)
|
41
|
-
|
42
|
-
__all__ = (
|
43
|
-
"Logger",
|
44
|
-
"create_logger",
|
45
|
-
"create_logger_level",
|
46
|
-
"LoggerConfig",
|
47
|
-
"FileConfig",
|
48
|
-
)
|
49
|
-
|
50
|
-
|
51
|
-
# -----------------------------------------------------------------------------
|
52
|
-
# Types
|
53
|
-
# -----------------------------------------------------------------------------
|
54
|
-
|
55
|
-
|
56
|
-
LoggerLevelName: TypeAlias = Literal["debug", "info", "warning", "error", "critical"]
|
57
|
-
"""Literal type helper for logging levels."""
|
58
|
-
|
59
|
-
|
60
|
-
class LoggerLevelSettings(TypedDict, total=False):
|
61
|
-
"""Configuration dictionary for the display style of a
|
62
|
-
single logging level."""
|
63
|
-
|
64
|
-
title: CLIStyleType | CLIStyleRenderableSettings
|
65
|
-
"""Either a string tag or style settings for the title output
|
66
|
-
of the messages of this level. This includes module name
|
67
|
-
and level name."""
|
68
|
-
|
69
|
-
message: CLIStyleType | CLIStyleRenderableSettings
|
70
|
-
"""Either a string tag or style settings for the message output
|
71
|
-
of the messages of this level. This includes the message itself."""
|
72
|
-
|
73
|
-
background: CLIStyleType | CLIStyleBackgroundSettings
|
74
|
-
"""Either a string tag or style settings for the background output
|
75
|
-
of the messages of this level. This includes the message itself."""
|
76
|
-
|
77
|
-
|
78
|
-
class FileConfig(TypedDict, total=False):
|
79
|
-
"""Configuration for file logging."""
|
80
|
-
|
81
|
-
path: Union[str, Path]
|
82
|
-
"""Path to the log file."""
|
83
|
-
|
84
|
-
mode: Literal["a", "w"]
|
85
|
-
"""File mode - 'a' for append, 'w' for write (overwrites)."""
|
86
|
-
|
87
|
-
max_bytes: int
|
88
|
-
"""Maximum size in bytes before rotation (0 for no rotation)."""
|
89
|
-
|
90
|
-
backup_count: int
|
91
|
-
"""Number of backup files to keep during rotation."""
|
92
|
-
|
93
|
-
encoding: str
|
94
|
-
"""File encoding (defaults to 'utf-8')."""
|
95
|
-
|
96
|
-
delay: bool
|
97
|
-
"""Whether to delay file opening until first write."""
|
98
|
-
|
99
|
-
create_dirs: bool
|
100
|
-
"""Whether to create parent directories if they don't exist."""
|
101
|
-
|
102
|
-
|
103
|
-
class LoggerConfig(TypedDict, total=False):
|
104
|
-
"""Complete configuration for Logger initialization."""
|
105
|
-
|
106
|
-
name: str
|
107
|
-
"""Logger name."""
|
108
|
-
|
109
|
-
level: Union[str, int]
|
110
|
-
"""Logging level."""
|
111
|
-
|
112
|
-
rich: bool
|
113
|
-
"""Whether to use rich formatting."""
|
114
|
-
|
115
|
-
display_all: bool
|
116
|
-
"""Whether to display all log levels."""
|
117
|
-
|
118
|
-
level_styles: Dict[str, LoggerLevelSettings]
|
119
|
-
"""Custom level styles."""
|
120
|
-
|
121
|
-
file: Union[str, Path, FileConfig]
|
122
|
-
"""File logging configuration."""
|
123
|
-
|
124
|
-
files: List[Union[str, Path, FileConfig]]
|
125
|
-
"""Multiple file destinations."""
|
126
|
-
|
127
|
-
format: str
|
128
|
-
"""Custom log format string."""
|
129
|
-
|
130
|
-
date_format: str
|
131
|
-
"""Date format for timestamps."""
|
132
|
-
|
133
|
-
json_logs: bool
|
134
|
-
"""Whether to output structured JSON logs."""
|
135
|
-
|
136
|
-
console: bool
|
137
|
-
"""Whether to log to console (default True)."""
|
138
|
-
|
139
|
-
handlers: List[_logging.Handler]
|
140
|
-
"""Additional custom handlers."""
|
141
|
-
|
142
|
-
|
143
|
-
# -----------------------------------------------------------------------------
|
144
|
-
# Default Level Styles
|
145
|
-
# -----------------------------------------------------------------------------
|
146
|
-
|
147
|
-
DEFAULT_LEVEL_STYLES: Dict[str, LoggerLevelSettings] = {
|
148
|
-
"critical": {
|
149
|
-
"message": "red bold",
|
150
|
-
},
|
151
|
-
"error": {
|
152
|
-
"message": "red italic",
|
153
|
-
},
|
154
|
-
"warning": {
|
155
|
-
"message": "yellow italic",
|
156
|
-
},
|
157
|
-
"info": {
|
158
|
-
"message": "white",
|
159
|
-
},
|
160
|
-
"debug": {
|
161
|
-
"message": "white italic dim",
|
162
|
-
},
|
163
|
-
}
|
164
|
-
|
165
|
-
|
166
|
-
# -----------------------------------------------------------------------------
|
167
|
-
# Logging Filter
|
168
|
-
# -----------------------------------------------------------------------------
|
169
|
-
|
170
|
-
|
171
|
-
class RichLoggerFilter(_logging.Filter):
|
172
|
-
"""Filter for applying rich styling to log messages based on level."""
|
173
|
-
|
174
|
-
def __init__(self, level_styles: Dict[str, LoggerLevelSettings]):
|
175
|
-
super().__init__()
|
176
|
-
self.level_styles = level_styles
|
177
|
-
|
178
|
-
def filter(self, record: _logging.LogRecord) -> bool:
|
179
|
-
# Get the level name
|
180
|
-
level_name = record.levelname.lower()
|
181
|
-
|
182
|
-
# Check if we have custom styling for this level
|
183
|
-
if level_name in self.level_styles:
|
184
|
-
style_config = self.level_styles[level_name]
|
185
|
-
|
186
|
-
record._hammad_style_config = style_config
|
187
|
-
|
188
|
-
return True
|
189
|
-
|
190
|
-
|
191
|
-
# -----------------------------------------------------------------------------
|
192
|
-
# Custom Rich Formatter
|
193
|
-
# -----------------------------------------------------------------------------
|
194
|
-
|
195
|
-
|
196
|
-
class RichLoggerFormatter(_logging.Formatter):
|
197
|
-
"""Custom formatter that applies rich styling."""
|
198
|
-
|
199
|
-
def __init__(self, *args, **kwargs):
|
200
|
-
super().__init__(*args, **kwargs)
|
201
|
-
self.console = get_rich_console()
|
202
|
-
|
203
|
-
def formatMessage(self, record: _logging.LogRecord) -> str:
|
204
|
-
"""Override formatMessage to apply styling to different parts."""
|
205
|
-
# Check if we have style configuration
|
206
|
-
if hasattr(record, "_hammad_style_config"):
|
207
|
-
style_config = record._hammad_style_config
|
208
|
-
|
209
|
-
# Handle title styling (logger name)
|
210
|
-
title_style = style_config.get("title", None)
|
211
|
-
if title_style:
|
212
|
-
if isinstance(title_style, str):
|
213
|
-
# It's a color/style string tag
|
214
|
-
record.name = f"[{title_style}]{record.name}[/{title_style}]"
|
215
|
-
elif isinstance(title_style, dict):
|
216
|
-
# It's a CLIStyleRenderableSettings dict
|
217
|
-
style_str = self._build_renderable_style_string(title_style)
|
218
|
-
if style_str:
|
219
|
-
record.name = f"[{style_str}]{record.name}[/{style_str}]"
|
220
|
-
|
221
|
-
# Handle message styling
|
222
|
-
message_style = style_config.get("message", None)
|
223
|
-
if message_style:
|
224
|
-
if isinstance(message_style, str):
|
225
|
-
# It's a color/style string tag
|
226
|
-
record.message = (
|
227
|
-
f"[{message_style}]{record.getMessage()}[/{message_style}]"
|
228
|
-
)
|
229
|
-
elif isinstance(message_style, dict):
|
230
|
-
# It's a CLIStyleRenderableSettings dict
|
231
|
-
style_str = self._build_renderable_style_string(message_style)
|
232
|
-
if style_str:
|
233
|
-
record.message = (
|
234
|
-
f"[{style_str}]{record.getMessage()}[/{style_str}]"
|
235
|
-
)
|
236
|
-
else:
|
237
|
-
record.message = record.getMessage()
|
238
|
-
else:
|
239
|
-
record.message = record.getMessage()
|
240
|
-
else:
|
241
|
-
record.message = record.getMessage()
|
242
|
-
|
243
|
-
# Now format with the styled values
|
244
|
-
formatted = self._style._fmt.format(**record.__dict__)
|
245
|
-
return formatted if formatted != "None" else ""
|
246
|
-
|
247
|
-
def _build_renderable_style_string(self, style_dict: dict) -> str:
|
248
|
-
"""Build a rich markup style string from a CLIStyleRenderableSettings dictionary."""
|
249
|
-
style_parts = []
|
250
|
-
|
251
|
-
# Handle all the style attributes from CLIStyleRenderableSettings
|
252
|
-
for attr in [
|
253
|
-
"bold",
|
254
|
-
"italic",
|
255
|
-
"dim",
|
256
|
-
"underline",
|
257
|
-
"strike",
|
258
|
-
"blink",
|
259
|
-
"blink2",
|
260
|
-
"reverse",
|
261
|
-
"conceal",
|
262
|
-
"underline2",
|
263
|
-
"frame",
|
264
|
-
"encircle",
|
265
|
-
"overline",
|
266
|
-
]:
|
267
|
-
if style_dict.get(attr):
|
268
|
-
style_parts.append(attr)
|
269
|
-
|
270
|
-
return " ".join(style_parts) if style_parts else ""
|
271
|
-
|
272
|
-
|
273
|
-
# -----------------------------------------------------------------------------
|
274
|
-
# Logger
|
275
|
-
# -----------------------------------------------------------------------------
|
276
|
-
|
277
|
-
|
278
|
-
@dataclass
|
279
|
-
class Logger:
|
280
|
-
"""Flexible logger with rich styling and custom level support."""
|
281
|
-
|
282
|
-
_logger: _logging.Logger = field(init=False)
|
283
|
-
"""The underlying logging.Logger instance."""
|
284
|
-
|
285
|
-
_level_styles: Dict[str, LoggerLevelSettings] = field(init=False)
|
286
|
-
"""Custom level styles."""
|
287
|
-
|
288
|
-
_custom_levels: Dict[str, int] = field(init=False)
|
289
|
-
"""Custom logging levels."""
|
290
|
-
|
291
|
-
_user_level: str = field(init=False)
|
292
|
-
"""User-specified logging level."""
|
293
|
-
|
294
|
-
def __init__(
|
295
|
-
self,
|
296
|
-
name: Optional[str] = None,
|
297
|
-
level: Optional[Union[LoggerLevelName, int]] = None,
|
298
|
-
rich: bool = True,
|
299
|
-
display_all: bool = False,
|
300
|
-
level_styles: Optional[Dict[str, LoggerLevelSettings]] = None,
|
301
|
-
file: Optional[Union[str, Path, FileConfig]] = None,
|
302
|
-
files: Optional[List[Union[str, Path, FileConfig]]] = None,
|
303
|
-
format: Optional[str] = None,
|
304
|
-
date_format: Optional[str] = None,
|
305
|
-
json_logs: bool = False,
|
306
|
-
console: bool = True,
|
307
|
-
handlers: Optional[List[_logging.Handler]] = None,
|
308
|
-
) -> None:
|
309
|
-
"""
|
310
|
-
Initialize a new Logger instance.
|
311
|
-
|
312
|
-
Args:
|
313
|
-
name: Name for the logger. If None, defaults to "hammad"
|
314
|
-
level: Logging level. If None, defaults to "debug" if display_all else "warning"
|
315
|
-
rich: Whether to use rich formatting for output
|
316
|
-
display_all: If True, sets effective level to debug to show all messages
|
317
|
-
level_styles: Custom level styles to override defaults
|
318
|
-
file: Single file configuration for logging
|
319
|
-
files: Multiple file configurations for logging
|
320
|
-
format: Custom log format string
|
321
|
-
date_format: Date format for timestamps
|
322
|
-
json_logs: Whether to output structured JSON logs
|
323
|
-
console: Whether to log to console (default True)
|
324
|
-
handlers: Additional custom handlers to add
|
325
|
-
"""
|
326
|
-
logger_name = name or "hammad"
|
327
|
-
|
328
|
-
# Initialize custom levels dict
|
329
|
-
self._custom_levels = {}
|
330
|
-
|
331
|
-
# Initialize level styles with defaults
|
332
|
-
self._level_styles = DEFAULT_LEVEL_STYLES.copy()
|
333
|
-
if level_styles:
|
334
|
-
self._level_styles.update(level_styles)
|
335
|
-
|
336
|
-
# Handle integer levels by converting to string names
|
337
|
-
if isinstance(level, int):
|
338
|
-
# Map standard logging levels to their names
|
339
|
-
int_to_name = {
|
340
|
-
_logging.DEBUG: "debug",
|
341
|
-
_logging.INFO: "info",
|
342
|
-
_logging.WARNING: "warning",
|
343
|
-
_logging.ERROR: "error",
|
344
|
-
_logging.CRITICAL: "critical",
|
345
|
-
}
|
346
|
-
level = int_to_name.get(level, "warning")
|
347
|
-
|
348
|
-
self._user_level = level or "warning"
|
349
|
-
|
350
|
-
if display_all:
|
351
|
-
effective_level = "debug"
|
352
|
-
else:
|
353
|
-
effective_level = self._user_level
|
354
|
-
|
355
|
-
# Standard level mapping
|
356
|
-
level_map = {
|
357
|
-
"debug": _logging.DEBUG,
|
358
|
-
"info": _logging.INFO,
|
359
|
-
"warning": _logging.WARNING,
|
360
|
-
"error": _logging.ERROR,
|
361
|
-
"critical": _logging.CRITICAL,
|
362
|
-
}
|
363
|
-
|
364
|
-
# Check if it's a custom level
|
365
|
-
if effective_level.lower() in self._custom_levels:
|
366
|
-
log_level = self._custom_levels[effective_level.lower()]
|
367
|
-
else:
|
368
|
-
log_level = level_map.get(effective_level.lower(), _logging.WARNING)
|
369
|
-
|
370
|
-
# Create logger
|
371
|
-
self._logger = _logging.getLogger(logger_name)
|
372
|
-
|
373
|
-
# Store configuration
|
374
|
-
self._file_config = file
|
375
|
-
self._files_config = files or []
|
376
|
-
self._format = format
|
377
|
-
self._date_format = date_format
|
378
|
-
self._json_logs = json_logs
|
379
|
-
self._console_enabled = console
|
380
|
-
self._rich_enabled = rich
|
381
|
-
|
382
|
-
# Clear any existing handlers
|
383
|
-
if self._logger.hasHandlers():
|
384
|
-
self._logger.handlers.clear()
|
385
|
-
|
386
|
-
# Setup handlers
|
387
|
-
self._setup_handlers(log_level)
|
388
|
-
|
389
|
-
# Add custom handlers if provided
|
390
|
-
if handlers:
|
391
|
-
for handler in handlers:
|
392
|
-
self._logger.addHandler(handler)
|
393
|
-
|
394
|
-
self._logger.setLevel(log_level)
|
395
|
-
self._logger.propagate = False
|
396
|
-
|
397
|
-
def _setup_handlers(self, log_level: int) -> None:
|
398
|
-
"""Setup all handlers for the logger."""
|
399
|
-
# Console handler
|
400
|
-
if self._console_enabled:
|
401
|
-
if self._rich_enabled:
|
402
|
-
self._setup_rich_handler(log_level)
|
403
|
-
else:
|
404
|
-
self._setup_standard_handler(log_level)
|
405
|
-
|
406
|
-
# File handlers
|
407
|
-
if self._file_config:
|
408
|
-
self._setup_file_handler(self._file_config, log_level)
|
409
|
-
|
410
|
-
for file_config in self._files_config:
|
411
|
-
self._setup_file_handler(file_config, log_level)
|
412
|
-
|
413
|
-
def _setup_rich_handler(self, log_level: int) -> None:
|
414
|
-
"""Setup rich handler for the logger."""
|
415
|
-
console = get_rich_console()
|
416
|
-
|
417
|
-
handler = RichHandler(
|
418
|
-
level=log_level,
|
419
|
-
console=console,
|
420
|
-
rich_tracebacks=True,
|
421
|
-
show_time=self._date_format is not None,
|
422
|
-
show_path=False,
|
423
|
-
markup=True,
|
424
|
-
)
|
425
|
-
|
426
|
-
format_str = self._format or "| [bold]✼ {name}[/bold] - {message}"
|
427
|
-
formatter = RichLoggerFormatter(format_str, style="{")
|
428
|
-
|
429
|
-
if self._date_format:
|
430
|
-
formatter.datefmt = self._date_format
|
431
|
-
|
432
|
-
handler.setFormatter(formatter)
|
433
|
-
|
434
|
-
# Add our custom filter
|
435
|
-
handler.addFilter(RichLoggerFilter(self._level_styles))
|
436
|
-
|
437
|
-
self._logger.addHandler(handler)
|
438
|
-
|
439
|
-
def _setup_standard_handler(self, log_level: int) -> None:
|
440
|
-
"""Setup standard handler for the logger."""
|
441
|
-
handler = _logging.StreamHandler()
|
442
|
-
|
443
|
-
format_str = self._format or "✼ {name} - {levelname} - {message}"
|
444
|
-
if self._json_logs:
|
445
|
-
formatter = self._create_json_formatter()
|
446
|
-
else:
|
447
|
-
formatter = _logging.Formatter(format_str, style="{")
|
448
|
-
if self._date_format:
|
449
|
-
formatter.datefmt = self._date_format
|
450
|
-
|
451
|
-
handler.setFormatter(formatter)
|
452
|
-
handler.setLevel(log_level)
|
453
|
-
|
454
|
-
self._logger.addHandler(handler)
|
455
|
-
|
456
|
-
def _setup_file_handler(
|
457
|
-
self, file_config: Union[str, Path, FileConfig], log_level: int
|
458
|
-
) -> None:
|
459
|
-
"""Setup file handler for the logger."""
|
460
|
-
import logging.handlers
|
461
|
-
|
462
|
-
# Parse file configuration
|
463
|
-
if isinstance(file_config, (str, Path)):
|
464
|
-
config: FileConfig = {"path": file_config}
|
465
|
-
else:
|
466
|
-
config = file_config.copy()
|
467
|
-
|
468
|
-
file_path = Path(config["path"])
|
469
|
-
|
470
|
-
# Create directories if needed
|
471
|
-
if config.get("create_dirs", True):
|
472
|
-
file_path.parent.mkdir(parents=True, exist_ok=True)
|
473
|
-
|
474
|
-
# Determine handler type
|
475
|
-
max_bytes = config.get("max_bytes", 0)
|
476
|
-
backup_count = config.get("backup_count", 0)
|
477
|
-
|
478
|
-
if max_bytes > 0:
|
479
|
-
# Rotating file handler
|
480
|
-
handler = logging.handlers.RotatingFileHandler(
|
481
|
-
filename=str(file_path),
|
482
|
-
mode=config.get("mode", "a"),
|
483
|
-
maxBytes=max_bytes,
|
484
|
-
backupCount=backup_count,
|
485
|
-
encoding=config.get("encoding", "utf-8"),
|
486
|
-
delay=config.get("delay", False),
|
487
|
-
)
|
488
|
-
else:
|
489
|
-
# Regular file handler
|
490
|
-
handler = _logging.FileHandler(
|
491
|
-
filename=str(file_path),
|
492
|
-
mode=config.get("mode", "a"),
|
493
|
-
encoding=config.get("encoding", "utf-8"),
|
494
|
-
delay=config.get("delay", False),
|
495
|
-
)
|
496
|
-
|
497
|
-
# Set formatter
|
498
|
-
if self._json_logs:
|
499
|
-
formatter = self._create_json_formatter()
|
500
|
-
else:
|
501
|
-
format_str = self._format or "[{asctime}] {name} - {levelname} - {message}"
|
502
|
-
formatter = _logging.Formatter(format_str, style="{")
|
503
|
-
if self._date_format:
|
504
|
-
formatter.datefmt = self._date_format
|
505
|
-
|
506
|
-
handler.setFormatter(formatter)
|
507
|
-
handler.setLevel(log_level)
|
508
|
-
|
509
|
-
self._logger.addHandler(handler)
|
510
|
-
|
511
|
-
def _create_json_formatter(self) -> _logging.Formatter:
|
512
|
-
"""Create a JSON formatter for structured logging."""
|
513
|
-
import json
|
514
|
-
import datetime
|
515
|
-
|
516
|
-
class JSONFormatter(_logging.Formatter):
|
517
|
-
def format(self, record):
|
518
|
-
log_entry = {
|
519
|
-
"timestamp": datetime.datetime.fromtimestamp(
|
520
|
-
record.created
|
521
|
-
).isoformat(),
|
522
|
-
"level": record.levelname,
|
523
|
-
"logger": record.name,
|
524
|
-
"message": record.getMessage(),
|
525
|
-
"module": record.module,
|
526
|
-
"function": record.funcName,
|
527
|
-
"line": record.lineno,
|
528
|
-
}
|
529
|
-
|
530
|
-
if record.exc_info:
|
531
|
-
log_entry["exception"] = self.formatException(record.exc_info)
|
532
|
-
|
533
|
-
return json.dumps(log_entry)
|
534
|
-
|
535
|
-
return JSONFormatter()
|
536
|
-
|
537
|
-
def setLevel(
|
538
|
-
self,
|
539
|
-
level: Union[LoggerLevelName, int],
|
540
|
-
) -> None:
|
541
|
-
"""Set the logging level."""
|
542
|
-
# Handle integer levels by converting to string names
|
543
|
-
if isinstance(level, int):
|
544
|
-
# Map standard logging levels to their names
|
545
|
-
int_to_name = {
|
546
|
-
_logging.DEBUG: "debug",
|
547
|
-
_logging.INFO: "info",
|
548
|
-
_logging.WARNING: "warning",
|
549
|
-
_logging.ERROR: "error",
|
550
|
-
_logging.CRITICAL: "critical",
|
551
|
-
}
|
552
|
-
level_str = int_to_name.get(level, "warning")
|
553
|
-
else:
|
554
|
-
level_str = level
|
555
|
-
|
556
|
-
self._user_level = level_str
|
557
|
-
|
558
|
-
# Standard level mapping
|
559
|
-
level_map = {
|
560
|
-
"debug": _logging.DEBUG,
|
561
|
-
"info": _logging.INFO,
|
562
|
-
"warning": _logging.WARNING,
|
563
|
-
"error": _logging.ERROR,
|
564
|
-
"critical": _logging.CRITICAL,
|
565
|
-
}
|
566
|
-
|
567
|
-
# Check custom levels first
|
568
|
-
if level_str.lower() in self._custom_levels:
|
569
|
-
log_level = self._custom_levels[level_str.lower()]
|
570
|
-
else:
|
571
|
-
log_level = level_map.get(level_str.lower(), _logging.WARNING)
|
572
|
-
|
573
|
-
# Set the integer level on the logger and handlers
|
574
|
-
self._logger.setLevel(log_level)
|
575
|
-
for handler in self._logger.handlers:
|
576
|
-
handler.setLevel(log_level)
|
577
|
-
|
578
|
-
def add_level(
|
579
|
-
self, name: str, value: int, style: Optional[LoggerLevelSettings] = None
|
580
|
-
) -> None:
|
581
|
-
"""
|
582
|
-
Add a custom logging level.
|
583
|
-
|
584
|
-
Args:
|
585
|
-
name: Name of the custom level
|
586
|
-
value: Numeric value for the level (should be unique)
|
587
|
-
style: Optional style settings for the level
|
588
|
-
"""
|
589
|
-
# Add to Python's logging module
|
590
|
-
_logging.addLevelName(value, name.upper())
|
591
|
-
|
592
|
-
# Store in our custom levels
|
593
|
-
self._custom_levels[name.lower()] = value
|
594
|
-
|
595
|
-
# Add style if provided
|
596
|
-
if style:
|
597
|
-
self._level_styles[name.lower()] = style
|
598
|
-
|
599
|
-
# Update filters if using rich handler
|
600
|
-
for handler in self._logger.handlers:
|
601
|
-
if isinstance(handler, RichHandler):
|
602
|
-
# Remove old filter and add new one with updated styles
|
603
|
-
for f in handler.filters[:]:
|
604
|
-
if isinstance(f, RichLoggerFilter):
|
605
|
-
handler.removeFilter(f)
|
606
|
-
handler.addFilter(RichLoggerFilter(self._level_styles))
|
607
|
-
|
608
|
-
@property
|
609
|
-
def level(self) -> str:
|
610
|
-
"""Get the current logging level."""
|
611
|
-
return self._user_level
|
612
|
-
|
613
|
-
@level.setter
|
614
|
-
def level(self, value: Union[str, int]) -> None:
|
615
|
-
"""Set the logging level."""
|
616
|
-
# Handle integer levels by converting to string names
|
617
|
-
if isinstance(value, int):
|
618
|
-
# Map standard logging levels to their names
|
619
|
-
int_to_name = {
|
620
|
-
_logging.DEBUG: "debug",
|
621
|
-
_logging.INFO: "info",
|
622
|
-
_logging.WARNING: "warning",
|
623
|
-
_logging.ERROR: "error",
|
624
|
-
_logging.CRITICAL: "critical",
|
625
|
-
}
|
626
|
-
value_str = int_to_name.get(value, "warning")
|
627
|
-
else:
|
628
|
-
value_str = value
|
629
|
-
|
630
|
-
self._user_level = value_str
|
631
|
-
|
632
|
-
# Standard level mapping
|
633
|
-
level_map = {
|
634
|
-
"debug": _logging.DEBUG,
|
635
|
-
"info": _logging.INFO,
|
636
|
-
"warning": _logging.WARNING,
|
637
|
-
"error": _logging.ERROR,
|
638
|
-
"critical": _logging.CRITICAL,
|
639
|
-
}
|
640
|
-
|
641
|
-
# Check custom levels
|
642
|
-
if value_str.lower() in self._custom_levels:
|
643
|
-
log_level = self._custom_levels[value_str.lower()]
|
644
|
-
else:
|
645
|
-
log_level = level_map.get(value_str.lower(), _logging.WARNING)
|
646
|
-
|
647
|
-
# Update logger level
|
648
|
-
self._logger.setLevel(log_level)
|
649
|
-
|
650
|
-
# Update handler levels
|
651
|
-
for handler in self._logger.handlers:
|
652
|
-
handler.setLevel(log_level)
|
653
|
-
|
654
|
-
# Convenience methods for standard logging levels
|
655
|
-
def debug(self, message: str, *args: Any, **kwargs: Any) -> None:
|
656
|
-
"""Log a debug message."""
|
657
|
-
self._logger.debug(message, *args, **kwargs)
|
658
|
-
|
659
|
-
def info(self, message: str, *args: Any, **kwargs: Any) -> None:
|
660
|
-
"""Log an info message."""
|
661
|
-
self._logger.info(message, *args, **kwargs)
|
662
|
-
|
663
|
-
def warning(self, message: str, *args: Any, **kwargs: Any) -> None:
|
664
|
-
"""Log a warning message."""
|
665
|
-
self._logger.warning(message, *args, **kwargs)
|
666
|
-
|
667
|
-
def error(self, message: str, *args: Any, **kwargs: Any) -> None:
|
668
|
-
"""Log an error message."""
|
669
|
-
self._logger.error(message, *args, **kwargs)
|
670
|
-
|
671
|
-
def critical(self, message: str, *args: Any, **kwargs: Any) -> None:
|
672
|
-
"""Log a critical message."""
|
673
|
-
self._logger.critical(message, *args, **kwargs)
|
674
|
-
|
675
|
-
def log(
|
676
|
-
self, level: Union[str, int], message: str, *args: Any, **kwargs: Any
|
677
|
-
) -> None:
|
678
|
-
"""
|
679
|
-
Log a message at the specified level.
|
680
|
-
|
681
|
-
Args:
|
682
|
-
level: The level to log at (can be standard or custom)
|
683
|
-
message: The message to log
|
684
|
-
*args: Additional positional arguments for the logger
|
685
|
-
**kwargs: Additional keyword arguments for the logger
|
686
|
-
"""
|
687
|
-
# Standard level mapping
|
688
|
-
level_map = {
|
689
|
-
"debug": _logging.DEBUG,
|
690
|
-
"info": _logging.INFO,
|
691
|
-
"warning": _logging.WARNING,
|
692
|
-
"error": _logging.ERROR,
|
693
|
-
"critical": _logging.CRITICAL,
|
694
|
-
}
|
695
|
-
|
696
|
-
# Handle integer levels
|
697
|
-
if isinstance(level, int):
|
698
|
-
# Use the integer level directly
|
699
|
-
log_level = level
|
700
|
-
else:
|
701
|
-
# Check custom levels first
|
702
|
-
if level.lower() in self._custom_levels:
|
703
|
-
log_level = self._custom_levels[level.lower()]
|
704
|
-
else:
|
705
|
-
log_level = level_map.get(level.lower(), _logging.WARNING)
|
706
|
-
|
707
|
-
self._logger.log(log_level, message, *args, **kwargs)
|
708
|
-
|
709
|
-
@property
|
710
|
-
def name(self) -> str:
|
711
|
-
"""Get the logger name."""
|
712
|
-
return self._logger.name
|
713
|
-
|
714
|
-
@property
|
715
|
-
def handlers(self) -> list[_logging.Handler]:
|
716
|
-
"""Get the logger handlers."""
|
717
|
-
return self._logger.handlers
|
718
|
-
|
719
|
-
def get_logger(self) -> _logging.Logger:
|
720
|
-
"""Get the underlying logging.Logger instance."""
|
721
|
-
return self._logger
|
722
|
-
|
723
|
-
@contextmanager
|
724
|
-
def track(
|
725
|
-
self,
|
726
|
-
description: str = "Processing...",
|
727
|
-
total: Optional[int] = None,
|
728
|
-
spinner: Optional[str] = None,
|
729
|
-
show_progress: bool = True,
|
730
|
-
show_time: bool = True,
|
731
|
-
transient: bool = False,
|
732
|
-
) -> Iterator[Union[TaskID, Callable[[str], None]]]:
|
733
|
-
"""Context manager for tracking progress with rich progress bar or spinner.
|
734
|
-
|
735
|
-
Args:
|
736
|
-
description: Description of the task being tracked
|
737
|
-
total: Total number of steps (if None, uses spinner instead of progress bar)
|
738
|
-
spinner: Spinner style to use (if total is None)
|
739
|
-
show_progress: Whether to show progress percentage
|
740
|
-
show_time: Whether to show time remaining
|
741
|
-
transient: Whether to remove the progress display when done
|
742
|
-
|
743
|
-
Yields:
|
744
|
-
TaskID for progress updates or callable for spinner text updates
|
745
|
-
|
746
|
-
Examples:
|
747
|
-
# Progress bar
|
748
|
-
with logger.track("Processing files", total=100) as task:
|
749
|
-
for i in range(100):
|
750
|
-
# do work
|
751
|
-
task.advance(1)
|
752
|
-
|
753
|
-
# Spinner
|
754
|
-
with logger.track("Loading data") as update:
|
755
|
-
# do work
|
756
|
-
update("Still loading...")
|
757
|
-
"""
|
758
|
-
console = get_rich_console()
|
759
|
-
|
760
|
-
if total is not None:
|
761
|
-
# Use progress bar
|
762
|
-
columns = [SpinnerColumn(), TextColumn("{task.description}")]
|
763
|
-
if show_progress:
|
764
|
-
columns.extend(
|
765
|
-
[BarColumn(), "[progress.percentage]{task.percentage:>3.0f}%"]
|
766
|
-
)
|
767
|
-
if show_time:
|
768
|
-
columns.append(TimeRemainingColumn())
|
769
|
-
|
770
|
-
with Progress(*columns, console=console, transient=transient) as progress:
|
771
|
-
task_id = progress.add_task(description, total=total)
|
772
|
-
|
773
|
-
class TaskWrapper:
|
774
|
-
def __init__(self, progress_obj, task_id):
|
775
|
-
self.progress = progress_obj
|
776
|
-
self.task_id = task_id
|
777
|
-
|
778
|
-
def advance(self, advance: int = 1) -> None:
|
779
|
-
self.progress.advance(self.task_id, advance)
|
780
|
-
|
781
|
-
def update(self, **kwargs) -> None:
|
782
|
-
self.progress.update(self.task_id, **kwargs)
|
783
|
-
|
784
|
-
yield TaskWrapper(progress, task_id)
|
785
|
-
else:
|
786
|
-
# Use spinner
|
787
|
-
spinner_obj = Spinner(spinner or "dots", text=description)
|
788
|
-
|
789
|
-
with Live(spinner_obj, console=console, transient=transient) as live:
|
790
|
-
|
791
|
-
def update_text(new_text: str) -> None:
|
792
|
-
spinner_obj.text = new_text
|
793
|
-
live.refresh()
|
794
|
-
|
795
|
-
yield update_text
|
796
|
-
|
797
|
-
def trace_function(self, *args, **kwargs):
|
798
|
-
"""Apply function tracing decorator. Imports from decorators module."""
|
799
|
-
from .decorators import trace_function as _trace_function
|
800
|
-
|
801
|
-
return _trace_function(logger=self, *args, **kwargs)
|
802
|
-
|
803
|
-
def trace_cls(self, *args, **kwargs):
|
804
|
-
"""Apply class tracing decorator. Imports from decorators module."""
|
805
|
-
from .decorators import trace_cls as _trace_cls
|
806
|
-
|
807
|
-
return _trace_cls(logger=self, *args, **kwargs)
|
808
|
-
|
809
|
-
def trace(self, *args, **kwargs):
|
810
|
-
"""Apply universal tracing decorator. Imports from decorators module."""
|
811
|
-
from .decorators import trace as _trace
|
812
|
-
|
813
|
-
return _trace(logger=self, *args, **kwargs)
|
814
|
-
|
815
|
-
def animate_spinning(
|
816
|
-
self,
|
817
|
-
text: str,
|
818
|
-
duration: Optional[float] = None,
|
819
|
-
frames: Optional[List[str]] = None,
|
820
|
-
speed: float = 0.1,
|
821
|
-
level: LoggerLevelName = "info",
|
822
|
-
) -> None:
|
823
|
-
"""Display spinning animation with logging.
|
824
|
-
|
825
|
-
Args:
|
826
|
-
text: Text to display with spinner
|
827
|
-
duration: Duration to run animation (defaults to 2.0)
|
828
|
-
frames: Custom spinner frames
|
829
|
-
speed: Speed of animation
|
830
|
-
level: Log level to use
|
831
|
-
"""
|
832
|
-
self.log(level, f"Starting: {text}")
|
833
|
-
animate_spinning(
|
834
|
-
text,
|
835
|
-
duration=duration,
|
836
|
-
frames=frames,
|
837
|
-
speed=speed,
|
838
|
-
)
|
839
|
-
self.log(level, f"Completed: {text}")
|
840
|
-
|
841
|
-
def add_file(
|
842
|
-
self,
|
843
|
-
file_config: Union[str, Path, FileConfig],
|
844
|
-
level: Optional[Union[str, int]] = None,
|
845
|
-
) -> None:
|
846
|
-
"""Add a new file handler to the logger.
|
847
|
-
|
848
|
-
Args:
|
849
|
-
file_config: File configuration
|
850
|
-
level: Optional level for this handler (uses logger level if None)
|
851
|
-
"""
|
852
|
-
handler_level = level or self._logger.level
|
853
|
-
if isinstance(handler_level, str):
|
854
|
-
level_map = {
|
855
|
-
"debug": _logging.DEBUG,
|
856
|
-
"info": _logging.INFO,
|
857
|
-
"warning": _logging.WARNING,
|
858
|
-
"error": _logging.ERROR,
|
859
|
-
"critical": _logging.CRITICAL,
|
860
|
-
}
|
861
|
-
handler_level = level_map.get(handler_level.lower(), _logging.WARNING)
|
862
|
-
|
863
|
-
self._setup_file_handler(file_config, handler_level)
|
864
|
-
|
865
|
-
def remove_handlers(self, handler_types: Optional[List[str]] = None) -> None:
|
866
|
-
"""Remove handlers from the logger.
|
867
|
-
|
868
|
-
Args:
|
869
|
-
handler_types: List of handler type names to remove.
|
870
|
-
If None, removes all handlers.
|
871
|
-
Options: ['file', 'console', 'rich', 'rotating']
|
872
|
-
"""
|
873
|
-
if handler_types is None:
|
874
|
-
self._logger.handlers.clear()
|
875
|
-
return
|
876
|
-
|
877
|
-
handlers_to_remove = []
|
878
|
-
for handler in self._logger.handlers:
|
879
|
-
handler_type = type(handler).__name__.lower()
|
880
|
-
|
881
|
-
if any(ht in handler_type for ht in handler_types):
|
882
|
-
handlers_to_remove.append(handler)
|
883
|
-
|
884
|
-
for handler in handlers_to_remove:
|
885
|
-
self._logger.removeHandler(handler)
|
886
|
-
|
887
|
-
def get_file_paths(self) -> List[Path]:
|
888
|
-
"""Get all file paths being logged to."""
|
889
|
-
file_paths = []
|
890
|
-
|
891
|
-
for handler in self._logger.handlers:
|
892
|
-
if hasattr(handler, "baseFilename"):
|
893
|
-
file_paths.append(Path(handler.baseFilename))
|
894
|
-
|
895
|
-
return file_paths
|
896
|
-
|
897
|
-
def flush(self) -> None:
|
898
|
-
"""Flush all handlers."""
|
899
|
-
for handler in self._logger.handlers:
|
900
|
-
handler.flush()
|
901
|
-
|
902
|
-
def close(self) -> None:
|
903
|
-
"""Close all handlers and cleanup resources."""
|
904
|
-
for handler in self._logger.handlers[:]:
|
905
|
-
handler.close()
|
906
|
-
self._logger.removeHandler(handler)
|
907
|
-
|
908
|
-
|
909
|
-
# -----------------------------------------------------------------------------
|
910
|
-
# Factory
|
911
|
-
# -----------------------------------------------------------------------------
|
912
|
-
|
913
|
-
|
914
|
-
def create_logger_level(
|
915
|
-
name: str,
|
916
|
-
level: int,
|
917
|
-
color: Optional[str] = None,
|
918
|
-
style: Optional[str] = None,
|
919
|
-
) -> None:
|
920
|
-
"""
|
921
|
-
Create a custom logging level.
|
922
|
-
|
923
|
-
Args:
|
924
|
-
name: The name of the logging level (e.g., "TRACE", "SUCCESS")
|
925
|
-
level: The numeric level value (should be between existing levels)
|
926
|
-
color: Optional color for rich formatting (e.g., "green", "blue")
|
927
|
-
style: Optional style for rich formatting (e.g., "bold", "italic")
|
928
|
-
"""
|
929
|
-
# Convert name to uppercase for consistency
|
930
|
-
level_name = name.upper()
|
931
|
-
|
932
|
-
# Add the level to the logging module
|
933
|
-
_logging.addLevelName(level, level_name)
|
934
|
-
|
935
|
-
# Create a method on the Logger class for this level
|
936
|
-
def log_method(self, message, *args, **kwargs):
|
937
|
-
if self.isEnabledFor(level):
|
938
|
-
self._log(level, message, args, **kwargs)
|
939
|
-
|
940
|
-
# Add the method to the standard logging.Logger class
|
941
|
-
setattr(_logging.Logger, name.lower(), log_method)
|
942
|
-
|
943
|
-
# Store level info for potential rich formatting
|
944
|
-
if hasattr(_logging, "_custom_level_info"):
|
945
|
-
_logging._custom_level_info[level] = {
|
946
|
-
"name": level_name,
|
947
|
-
"color": color,
|
948
|
-
"style": style,
|
949
|
-
}
|
950
|
-
else:
|
951
|
-
_logging._custom_level_info = {
|
952
|
-
level: {"name": level_name, "color": color, "style": style}
|
953
|
-
}
|
954
|
-
|
955
|
-
|
956
|
-
def create_logger(
|
957
|
-
name: Optional[str] = None,
|
958
|
-
level: Optional[Union[LoggerLevelName, int]] = None,
|
959
|
-
rich: bool = True,
|
960
|
-
display_all: bool = False,
|
961
|
-
levels: Optional[Dict[LoggerLevelName, LoggerLevelSettings]] = None,
|
962
|
-
file: Optional[Union[str, Path, FileConfig]] = None,
|
963
|
-
files: Optional[List[Union[str, Path, FileConfig]]] = None,
|
964
|
-
format: Optional[str] = None,
|
965
|
-
date_format: Optional[str] = None,
|
966
|
-
json_logs: bool = False,
|
967
|
-
console: bool = True,
|
968
|
-
handlers: Optional[List[_logging.Handler]] = None,
|
969
|
-
) -> Logger:
|
970
|
-
"""
|
971
|
-
Get a logger instance.
|
972
|
-
|
973
|
-
Args:
|
974
|
-
name: Name for the logger. If None, uses caller's function name
|
975
|
-
level: Logging level. If None, defaults to "debug" if display_all else "warning"
|
976
|
-
rich: Whether to use rich formatting for output
|
977
|
-
display_all: If True, sets effective level to debug to show all messages
|
978
|
-
levels: Custom level styles to override defaults
|
979
|
-
file: Single file configuration for logging
|
980
|
-
files: Multiple file configurations for logging
|
981
|
-
format: Custom log format string
|
982
|
-
date_format: Date format for timestamps
|
983
|
-
json_logs: Whether to output structured JSON logs
|
984
|
-
console: Whether to log to console (default True)
|
985
|
-
handlers: Additional custom handlers to add
|
986
|
-
|
987
|
-
Returns:
|
988
|
-
A Logger instance with the specified configuration.
|
989
|
-
"""
|
990
|
-
if name is None:
|
991
|
-
frame = inspect.currentframe()
|
992
|
-
if frame and frame.f_back:
|
993
|
-
name = frame.f_back.f_code.co_name
|
994
|
-
else:
|
995
|
-
name = "logger"
|
996
|
-
|
997
|
-
return Logger(
|
998
|
-
name=name,
|
999
|
-
level=level,
|
1000
|
-
rich=rich,
|
1001
|
-
display_all=display_all,
|
1002
|
-
level_styles=levels,
|
1003
|
-
file=file,
|
1004
|
-
files=files,
|
1005
|
-
format=format,
|
1006
|
-
date_format=date_format,
|
1007
|
-
json_logs=json_logs,
|
1008
|
-
console=console,
|
1009
|
-
handlers=handlers,
|
1010
|
-
)
|
1011
|
-
|
1012
|
-
|
1013
|
-
# internal logger and helper
|
1014
|
-
_logger = Logger("hammad", level="warning")
|
1015
|
-
|
1016
|
-
|
1017
|
-
def _get_internal_logger(name: str) -> Logger:
|
1018
|
-
return Logger(name=name, level="warning")
|