cxppython 0.0.1__tar.gz → 0.0.2__tar.gz
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.
- {cxppython-0.0.1 → cxppython-0.0.2}/PKG-INFO +8 -1
- cxppython-0.0.2/README.md +8 -0
- cxppython-0.0.2/cxppython/__init__.py +3 -0
- {cxppython-0.0.1 → cxppython-0.0.2}/cxppython/main.py +2 -1
- cxppython-0.0.2/cxppython/utils/__init__.py +0 -0
- cxppython-0.0.2/cxppython/utils/btlogging/__init__.py +11 -0
- cxppython-0.0.2/cxppython/utils/btlogging/console.py +73 -0
- cxppython-0.0.2/cxppython/utils/btlogging/defines.py +11 -0
- cxppython-0.0.2/cxppython/utils/btlogging/format.py +214 -0
- cxppython-0.0.2/cxppython/utils/btlogging/helpers.py +71 -0
- cxppython-0.0.2/cxppython/utils/btlogging/loggingmachine.py +664 -0
- cxppython-0.0.2/cxppython/utils/database/__init__.py +2 -0
- cxppython-0.0.2/cxppython/utils/database/mysql.py +103 -0
- cxppython-0.0.2/cxppython/utils/easy_imports.py +37 -0
- {cxppython-0.0.1 → cxppython-0.0.2}/cxppython.egg-info/PKG-INFO +8 -1
- cxppython-0.0.2/cxppython.egg-info/SOURCES.txt +20 -0
- {cxppython-0.0.1 → cxppython-0.0.2}/setup.py +2 -2
- cxppython-0.0.1/README.md +0 -1
- cxppython-0.0.1/cxppython/__init__.py +0 -1
- cxppython-0.0.1/cxppython.egg-info/SOURCES.txt +0 -10
- {cxppython-0.0.1 → cxppython-0.0.2}/LICENSE +0 -0
- {cxppython-0.0.1 → cxppython-0.0.2}/cxppython.egg-info/dependency_links.txt +0 -0
- {cxppython-0.0.1 → cxppython-0.0.2}/cxppython.egg-info/requires.txt +0 -0
- {cxppython-0.0.1 → cxppython-0.0.2}/cxppython.egg-info/top_level.txt +0 -0
- {cxppython-0.0.1 → cxppython-0.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: cxppython
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: A python utils package
|
|
5
5
|
Home-page: https://github.com/yourusername/my_package
|
|
6
6
|
Author: cxp
|
|
@@ -25,3 +25,10 @@ Dynamic: requires-python
|
|
|
25
25
|
Dynamic: summary
|
|
26
26
|
|
|
27
27
|
# cxppython
|
|
28
|
+
### install build
|
|
29
|
+
- python -m pip install --upgrade pip setuptools wheel twine
|
|
30
|
+
-
|
|
31
|
+
### build
|
|
32
|
+
- python setup.py sdist bdist_wheel
|
|
33
|
+
### upload
|
|
34
|
+
- twine upload dist/*
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
btlogging sub-package standardized logging for Bittensor.
|
|
3
|
+
|
|
4
|
+
This module provides logging functionality for the Bittensor package. It includes custom loggers, handlers, and
|
|
5
|
+
formatters to ensure consistent logging throughout the project.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .loggingmachine import LoggingMachine
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logging = LoggingMachine(LoggingMachine.config())
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BittensorConsole class gives the ability to log messages to the terminal without changing Bittensor logging level.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
from bittensor import logging
|
|
6
|
+
|
|
7
|
+
# will be logged
|
|
8
|
+
logging.console.info("info message")
|
|
9
|
+
logging.console.error("error message")
|
|
10
|
+
logging.console.success("success message")
|
|
11
|
+
logging.console.warning("warning message")
|
|
12
|
+
logging.console.critical("critical message")
|
|
13
|
+
|
|
14
|
+
# will not be logged
|
|
15
|
+
logging.info("test info")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from functools import wraps
|
|
19
|
+
from typing import Callable, TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from .helpers import all_loggers
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from .loggingmachine import LoggingMachine
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _print_wrapper(func: "Callable"):
|
|
28
|
+
@wraps(func)
|
|
29
|
+
def wrapper(self: "BittensorConsole", *args, **kwargs):
|
|
30
|
+
"""A wrapper function to temporarily set the logger level to debug."""
|
|
31
|
+
old_logger_level = self.logger.get_level()
|
|
32
|
+
self.logger.set_console()
|
|
33
|
+
func(self, *args, **kwargs)
|
|
34
|
+
|
|
35
|
+
for logger in all_loggers():
|
|
36
|
+
logger.setLevel(old_logger_level)
|
|
37
|
+
|
|
38
|
+
return wrapper
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BittensorConsole:
|
|
42
|
+
def __init__(self, logger: "LoggingMachine"):
|
|
43
|
+
self.logger = logger
|
|
44
|
+
|
|
45
|
+
@_print_wrapper
|
|
46
|
+
def debug(self, message: str):
|
|
47
|
+
"""Logs a DEBUG message to the console."""
|
|
48
|
+
self.logger.debug(message)
|
|
49
|
+
|
|
50
|
+
@_print_wrapper
|
|
51
|
+
def info(self, message: str):
|
|
52
|
+
"""Logs a INFO message to the console."""
|
|
53
|
+
self.logger.info(message)
|
|
54
|
+
|
|
55
|
+
@_print_wrapper
|
|
56
|
+
def success(self, message: str):
|
|
57
|
+
"""Logs a SUCCESS message to the console."""
|
|
58
|
+
self.logger.success(message)
|
|
59
|
+
|
|
60
|
+
@_print_wrapper
|
|
61
|
+
def warning(self, message: str):
|
|
62
|
+
"""Logs a WARNING message to the console."""
|
|
63
|
+
self.logger.warning(message)
|
|
64
|
+
|
|
65
|
+
@_print_wrapper
|
|
66
|
+
def error(self, message: str):
|
|
67
|
+
"""Logs a ERROR message to the console."""
|
|
68
|
+
self.logger.error(message)
|
|
69
|
+
|
|
70
|
+
@_print_wrapper
|
|
71
|
+
def critical(self, message: str):
|
|
72
|
+
"""Logs a CRITICAL message to the console."""
|
|
73
|
+
self.logger.critical(message)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Btlogging constant definition module."""
|
|
2
|
+
|
|
3
|
+
BASE_LOG_FORMAT = "%(asctime)s | %(levelname)s | %(message)s"
|
|
4
|
+
TRACE_LOG_FORMAT = (
|
|
5
|
+
f"%(asctime)s | %(levelname)s | %(name)s:%(filename)s:%(lineno)s | %(message)s"
|
|
6
|
+
)
|
|
7
|
+
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
8
|
+
BITTENSOR_LOGGER_NAME = "cxppy"
|
|
9
|
+
DEFAULT_LOG_FILE_NAME = "cxppy.log"
|
|
10
|
+
DEFAULT_MAX_ROTATING_LOG_FILE_SIZE = 25 * 1024 * 1024
|
|
11
|
+
DEFAULT_LOG_BACKUP_COUNT = 10
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
btlogging.format module
|
|
3
|
+
|
|
4
|
+
This module defines custom logging formatters for the Bittensor project.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from colorama import init, Fore, Back, Style
|
|
11
|
+
|
|
12
|
+
init(wrap=False)
|
|
13
|
+
|
|
14
|
+
TRACE_LEVEL_NUM: int = 5
|
|
15
|
+
SUCCESS_LEVEL_NUM: int = 21
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _trace(self, message: str, *args, **kws):
|
|
19
|
+
if self.isEnabledFor(TRACE_LEVEL_NUM):
|
|
20
|
+
self._log(TRACE_LEVEL_NUM, message, args, **kws)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _success(self, message: str, *args, **kws):
|
|
24
|
+
if self.isEnabledFor(SUCCESS_LEVEL_NUM):
|
|
25
|
+
self._log(SUCCESS_LEVEL_NUM, message, args, **kws)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
logging.SUCCESS = SUCCESS_LEVEL_NUM
|
|
29
|
+
logging.addLevelName(SUCCESS_LEVEL_NUM, "SUCCESS")
|
|
30
|
+
logging.Logger.success = _success
|
|
31
|
+
|
|
32
|
+
logging.TRACE = TRACE_LEVEL_NUM
|
|
33
|
+
logging.addLevelName(TRACE_LEVEL_NUM, "TRACE")
|
|
34
|
+
logging.Logger.trace = _trace
|
|
35
|
+
|
|
36
|
+
emoji_map: dict[str, str] = {
|
|
37
|
+
":white_heavy_check_mark:": "✅",
|
|
38
|
+
":cross_mark:": "❌",
|
|
39
|
+
":satellite:": "🛰️",
|
|
40
|
+
":warning:": "⚠️",
|
|
41
|
+
":arrow_right:": "➡️",
|
|
42
|
+
":hourglass:": "⏳",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
color_map: dict[str, str] = {
|
|
47
|
+
"[red]": Fore.RED,
|
|
48
|
+
"[/red]": Style.RESET_ALL,
|
|
49
|
+
"[blue]": Fore.BLUE,
|
|
50
|
+
"[/blue]": Style.RESET_ALL,
|
|
51
|
+
"[green]": Fore.GREEN,
|
|
52
|
+
"[/green]": Style.RESET_ALL,
|
|
53
|
+
"[magenta]": Fore.MAGENTA,
|
|
54
|
+
"[/magenta]": Style.RESET_ALL,
|
|
55
|
+
"[yellow]": Fore.YELLOW,
|
|
56
|
+
"[/yellow]": Style.RESET_ALL,
|
|
57
|
+
"[orange]": Fore.YELLOW,
|
|
58
|
+
"[/orange]": Style.RESET_ALL,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
log_level_color_prefix: dict[int, str] = {
|
|
63
|
+
logging.NOTSET: Fore.RESET,
|
|
64
|
+
logging.TRACE: Fore.MAGENTA,
|
|
65
|
+
logging.DEBUG: Fore.BLUE,
|
|
66
|
+
logging.INFO: Fore.WHITE,
|
|
67
|
+
logging.SUCCESS: Fore.GREEN,
|
|
68
|
+
logging.WARNING: Fore.YELLOW,
|
|
69
|
+
logging.ERROR: Fore.RED,
|
|
70
|
+
logging.CRITICAL: Back.RED,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
LOG_FORMATS: dict[int, str] = {
|
|
75
|
+
level: f"{Fore.BLUE}%(asctime)s{Fore.RESET} | {Style.BRIGHT}{color}%(levelname)s\033[0m | %(message)s"
|
|
76
|
+
for level, color in log_level_color_prefix.items()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
LOG_TRACE_FORMATS: dict[int, str] = {
|
|
80
|
+
level: f"{Fore.BLUE}%(asctime)s{Fore.RESET}"
|
|
81
|
+
f" | {Style.BRIGHT}{color}%(levelname)s{Fore.RESET}{Back.RESET}{Style.RESET_ALL}"
|
|
82
|
+
f" | %(name)s:%(filename)s:%(lineno)s"
|
|
83
|
+
f" | %(message)s"
|
|
84
|
+
for level, color in log_level_color_prefix.items()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
DEFAULT_LOG_FORMAT: str = (
|
|
88
|
+
f"{Fore.BLUE}%(asctime)s{Fore.RESET} | "
|
|
89
|
+
f"{Style.BRIGHT}{Fore.WHITE}%(levelname)s{Style.RESET_ALL} | "
|
|
90
|
+
f"%(name)s:%(filename)s:%(lineno)s | %(message)s"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
DEFAULT_TRACE_FORMAT: str = (
|
|
94
|
+
f"{Fore.BLUE}%(asctime)s{Fore.RESET} | "
|
|
95
|
+
f"{Style.BRIGHT}{Fore.WHITE}%(levelname)s{Style.RESET_ALL} | "
|
|
96
|
+
f"%(name)s:%(filename)s:%(lineno)s | %(message)s"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class BtStreamFormatter(logging.Formatter):
|
|
101
|
+
"""
|
|
102
|
+
A custom logging formatter for the Bittensor project that overrides the time formatting to include milliseconds,
|
|
103
|
+
centers the level name, and applies custom log formats, emojis, and colors.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
def __init__(self, *args, **kwargs):
|
|
107
|
+
super().__init__(*args, **kwargs)
|
|
108
|
+
self.trace = False
|
|
109
|
+
|
|
110
|
+
def formatTime(self, record, datefmt: Optional[str] = None) -> str:
|
|
111
|
+
"""
|
|
112
|
+
Override formatTime to add milliseconds.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
record (logging.LogRecord): The log record.
|
|
116
|
+
datefmt (Optional[str]): The date format string.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
s (str): The formatted time string with milliseconds.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
created = self.converter(record.created)
|
|
123
|
+
if datefmt:
|
|
124
|
+
s = time.strftime(datefmt, created)
|
|
125
|
+
else:
|
|
126
|
+
s = time.strftime("%Y-%m-%d %H:%M:%S", created)
|
|
127
|
+
s += f".{int(record.msecs):03d}"
|
|
128
|
+
return s
|
|
129
|
+
|
|
130
|
+
def format(self, record: "logging.LogRecord") -> str:
|
|
131
|
+
"""
|
|
132
|
+
Override format to apply custom formatting including emojis and colors.
|
|
133
|
+
|
|
134
|
+
This method saves the original format, applies custom formatting based on the log level and trace flag, replaces
|
|
135
|
+
text with emojis and colors, and then returns the formatted log record.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
record (logging.LogRecord): The log record.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
result (str): The formatted log record.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
format_orig = self._style._fmt
|
|
145
|
+
record.levelname = f"{record.levelname:^16}"
|
|
146
|
+
|
|
147
|
+
if record.levelno not in LOG_FORMATS:
|
|
148
|
+
self._style._fmt = (
|
|
149
|
+
DEFAULT_TRACE_FORMAT if self.trace else DEFAULT_LOG_FORMAT
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
if self.trace is True:
|
|
153
|
+
self._style._fmt = LOG_TRACE_FORMATS[record.levelno]
|
|
154
|
+
else:
|
|
155
|
+
self._style._fmt = LOG_FORMATS[record.levelno]
|
|
156
|
+
|
|
157
|
+
for text, emoji in emoji_map.items():
|
|
158
|
+
record.msg = record.msg.replace(text, emoji)
|
|
159
|
+
# Apply color specifiers
|
|
160
|
+
for text, color in color_map.items():
|
|
161
|
+
record.msg = record.msg.replace(text, color)
|
|
162
|
+
|
|
163
|
+
result = super().format(record)
|
|
164
|
+
self._style._fmt = format_orig
|
|
165
|
+
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
def set_trace(self, state: bool = True):
|
|
169
|
+
"""Change formatter state."""
|
|
170
|
+
self.trace = state
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class BtFileFormatter(logging.Formatter):
|
|
174
|
+
"""
|
|
175
|
+
BtFileFormatter
|
|
176
|
+
|
|
177
|
+
A custom logging formatter for the Bittensor project that overrides the time formatting to include milliseconds and
|
|
178
|
+
centers the level name.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def formatTime(
|
|
182
|
+
self, record: "logging.LogRecord", datefmt: Optional[str] = None
|
|
183
|
+
) -> str:
|
|
184
|
+
"""
|
|
185
|
+
Override formatTime to add milliseconds.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
record (logging.LogRecord): The log record.
|
|
189
|
+
datefmt (Optional[str]): The date format string.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
s (str): The formatted time string with milliseconds.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
created = self.converter(record.created)
|
|
196
|
+
if datefmt:
|
|
197
|
+
s = time.strftime(datefmt, created)
|
|
198
|
+
else:
|
|
199
|
+
s = time.strftime("%Y-%m-%d %H:%M:%S", created)
|
|
200
|
+
s += f".{int(record.msecs):03d}"
|
|
201
|
+
return s
|
|
202
|
+
|
|
203
|
+
def format(self, record: "logging.LogRecord") -> str:
|
|
204
|
+
"""
|
|
205
|
+
Override format to center the level name.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
record (logging.LogRecord): The log record.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
formatted record (str): The formatted log record.
|
|
212
|
+
"""
|
|
213
|
+
record.levelname = f"{record.levelname:^10}"
|
|
214
|
+
return super().format(record)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
btlogging.helpers module provides helper functions for the Bittensor logging system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Generator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def all_loggers() -> Generator["logging.Logger", None, None]:
|
|
10
|
+
"""Generator that yields all logger instances in the application.
|
|
11
|
+
|
|
12
|
+
Iterates through the logging root manager's logger dictionary and yields all active `Logger` instances. It skips
|
|
13
|
+
placeholders and other types that are not instances of `Logger`.
|
|
14
|
+
|
|
15
|
+
Yields:
|
|
16
|
+
logger (logging.Logger): An active logger instance.
|
|
17
|
+
"""
|
|
18
|
+
for logger in logging.root.manager.loggerDict.values():
|
|
19
|
+
if isinstance(logger, logging.PlaceHolder):
|
|
20
|
+
continue
|
|
21
|
+
# In some versions of Python, the values in loggerDict might be
|
|
22
|
+
# LoggerAdapter instances instead of Logger instances.
|
|
23
|
+
# We check for Logger instances specifically.
|
|
24
|
+
if isinstance(logger, logging.Logger):
|
|
25
|
+
yield logger
|
|
26
|
+
else:
|
|
27
|
+
# If it's not a Logger instance, it could be a LoggerAdapter or
|
|
28
|
+
# another form that doesn't directly offer logging methods.
|
|
29
|
+
# This branch can be extended to handle such cases as needed.
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def all_logger_names() -> Generator[str, None, None]:
|
|
34
|
+
"""
|
|
35
|
+
Generate the names of all active loggers.
|
|
36
|
+
|
|
37
|
+
This function iterates through the logging root manager's logger dictionary and yields the names of all active
|
|
38
|
+
`Logger` instances. It skips placeholders and other types that are not instances of `Logger`.
|
|
39
|
+
|
|
40
|
+
Yields:
|
|
41
|
+
name (str): The name of an active logger.
|
|
42
|
+
"""
|
|
43
|
+
for name, logger in logging.root.manager.loggerDict.items():
|
|
44
|
+
if isinstance(logger, logging.PlaceHolder):
|
|
45
|
+
continue
|
|
46
|
+
# In some versions of Python, the values in loggerDict might be
|
|
47
|
+
# LoggerAdapter instances instead of Logger instances.
|
|
48
|
+
# We check for Logger instances specifically.
|
|
49
|
+
if isinstance(logger, logging.Logger):
|
|
50
|
+
yield name
|
|
51
|
+
else:
|
|
52
|
+
# If it's not a Logger instance, it could be a LoggerAdapter or
|
|
53
|
+
# another form that doesn't directly offer logging methods.
|
|
54
|
+
# This branch can be extended to handle such cases as needed.
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_max_logger_name_length() -> int:
|
|
59
|
+
"""
|
|
60
|
+
Calculate and return the length of the longest logger name.
|
|
61
|
+
|
|
62
|
+
This function iterates through all active logger names and determines the length of the longest name.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
max_length (int): The length of the longest logger name.
|
|
66
|
+
"""
|
|
67
|
+
max_length = 0
|
|
68
|
+
for name in all_logger_names():
|
|
69
|
+
if len(name) > max_length:
|
|
70
|
+
max_length = len(name)
|
|
71
|
+
return max_length
|
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module provides a logging framework for Bittensor, managing both Bittensor-specific and third-party logging states.
|
|
3
|
+
It leverages the StateMachine from the statemachine package to transition between different logging states such as
|
|
4
|
+
Default, Debug, Trace, and Disabled.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import atexit
|
|
9
|
+
import logging as stdlogging
|
|
10
|
+
import multiprocessing as mp
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from logging import Logger
|
|
14
|
+
from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler
|
|
15
|
+
from typing import NamedTuple
|
|
16
|
+
|
|
17
|
+
from statemachine import State, StateMachine
|
|
18
|
+
|
|
19
|
+
from cxppython.core.config import Config
|
|
20
|
+
from cxppython.utils.btlogging.console import BittensorConsole
|
|
21
|
+
from .defines import (
|
|
22
|
+
BITTENSOR_LOGGER_NAME,
|
|
23
|
+
DATE_FORMAT,
|
|
24
|
+
DEFAULT_LOG_BACKUP_COUNT,
|
|
25
|
+
DEFAULT_LOG_FILE_NAME,
|
|
26
|
+
DEFAULT_MAX_ROTATING_LOG_FILE_SIZE,
|
|
27
|
+
TRACE_LOG_FORMAT,
|
|
28
|
+
)
|
|
29
|
+
from .format import BtFileFormatter, BtStreamFormatter
|
|
30
|
+
from .helpers import all_loggers
|
|
31
|
+
|
|
32
|
+
# https://github.com/python/cpython/issues/97941
|
|
33
|
+
CUSTOM_LOGGER_METHOD_STACK_LEVEL = 2 if sys.version_info >= (3, 11) else 1
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _concat_message(msg="", prefix="", suffix=""):
|
|
37
|
+
"""Concatenates a message with optional prefix and suffix."""
|
|
38
|
+
message_parts = [
|
|
39
|
+
str(component).strip()
|
|
40
|
+
for component in [prefix, msg, suffix]
|
|
41
|
+
if component is not None and str(component).strip()
|
|
42
|
+
]
|
|
43
|
+
formatted_message = " - ".join(message_parts)
|
|
44
|
+
return formatted_message
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class LoggingConfig(NamedTuple):
|
|
48
|
+
"""Named tuple to hold the logging configuration."""
|
|
49
|
+
|
|
50
|
+
debug: bool
|
|
51
|
+
trace: bool
|
|
52
|
+
info: bool
|
|
53
|
+
record_log: bool
|
|
54
|
+
logging_dir: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class LoggingMachine(StateMachine, Logger):
|
|
58
|
+
"""Handles logger states for bittensor and 3rd party libraries."""
|
|
59
|
+
|
|
60
|
+
Default = State(initial=True)
|
|
61
|
+
Debug = State()
|
|
62
|
+
Trace = State()
|
|
63
|
+
Disabled = State()
|
|
64
|
+
Warning = State()
|
|
65
|
+
Info = State()
|
|
66
|
+
|
|
67
|
+
enable_default = (
|
|
68
|
+
Debug.to(Default)
|
|
69
|
+
| Trace.to(Default)
|
|
70
|
+
| Disabled.to(Default)
|
|
71
|
+
| Default.to(Default)
|
|
72
|
+
| Warning.to(Default)
|
|
73
|
+
| Info.to(Default)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
enable_console = (
|
|
77
|
+
Default.to(Debug)
|
|
78
|
+
| Trace.to(Debug)
|
|
79
|
+
| Disabled.to(Debug)
|
|
80
|
+
| Debug.to(Debug)
|
|
81
|
+
| Warning.to(Debug)
|
|
82
|
+
| Info.to(Debug)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
enable_info = (
|
|
86
|
+
Default.to(Info)
|
|
87
|
+
| Debug.to(Info)
|
|
88
|
+
| Trace.to(Info)
|
|
89
|
+
| Disabled.to(Info)
|
|
90
|
+
| Warning.to(Info)
|
|
91
|
+
| Info.to(Info)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
enable_trace = (
|
|
95
|
+
Default.to(Trace)
|
|
96
|
+
| Debug.to(Trace)
|
|
97
|
+
| Disabled.to(Trace)
|
|
98
|
+
| Trace.to(Trace)
|
|
99
|
+
| Warning.to(Trace)
|
|
100
|
+
| Info.to(Trace)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
enable_debug = (
|
|
104
|
+
Default.to(Debug)
|
|
105
|
+
| Trace.to(Debug)
|
|
106
|
+
| Disabled.to(Debug)
|
|
107
|
+
| Debug.to(Debug)
|
|
108
|
+
| Warning.to(Debug)
|
|
109
|
+
| Info.to(Debug)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
enable_warning = (
|
|
113
|
+
Default.to(Warning)
|
|
114
|
+
| Trace.to(Warning)
|
|
115
|
+
| Disabled.to(Warning)
|
|
116
|
+
| Debug.to(Warning)
|
|
117
|
+
| Warning.to(Warning)
|
|
118
|
+
| Info.to(Warning)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
disable_trace = Trace.to(Default)
|
|
122
|
+
|
|
123
|
+
disable_debug = Debug.to(Default)
|
|
124
|
+
|
|
125
|
+
disable_warning = Warning.to(Default)
|
|
126
|
+
|
|
127
|
+
disable_info = Info.to(Default)
|
|
128
|
+
|
|
129
|
+
disable_logging = (
|
|
130
|
+
Trace.to(Disabled)
|
|
131
|
+
| Debug.to(Disabled)
|
|
132
|
+
| Default.to(Disabled)
|
|
133
|
+
| Disabled.to(Disabled)
|
|
134
|
+
| Info.to(Disabled)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def __init__(self, config: "Config", name: str = BITTENSOR_LOGGER_NAME):
|
|
138
|
+
# basics
|
|
139
|
+
super(LoggingMachine, self).__init__()
|
|
140
|
+
self._queue = mp.Queue(-1)
|
|
141
|
+
self._primary_loggers = {name}
|
|
142
|
+
self._config = self._extract_logging_config(config)
|
|
143
|
+
|
|
144
|
+
# Formatters
|
|
145
|
+
#
|
|
146
|
+
# In the future, this may be expanded to a dictionary mapping handler
|
|
147
|
+
# types to their respective formatters.
|
|
148
|
+
self._stream_formatter = BtStreamFormatter()
|
|
149
|
+
self._file_formatter = BtFileFormatter(TRACE_LOG_FORMAT, DATE_FORMAT)
|
|
150
|
+
|
|
151
|
+
# start with handlers for the QueueListener.
|
|
152
|
+
#
|
|
153
|
+
# In the future, we may want to add options to introduce other handlers
|
|
154
|
+
# for things like log aggregation by external services.
|
|
155
|
+
self._handlers = self._configure_handlers(self._config)
|
|
156
|
+
|
|
157
|
+
# configure and start the queue listener
|
|
158
|
+
self._listener = self._create_and_start_listener(self._handlers)
|
|
159
|
+
|
|
160
|
+
# set up all the loggers
|
|
161
|
+
self._logger = self._initialize_bt_logger(name)
|
|
162
|
+
self.disable_third_party_loggers()
|
|
163
|
+
self._enable_initial_state(self._config)
|
|
164
|
+
self.console = BittensorConsole(self)
|
|
165
|
+
|
|
166
|
+
def _enable_initial_state(self, config):
|
|
167
|
+
"""Set correct state action on initializing"""
|
|
168
|
+
if config.trace:
|
|
169
|
+
self.enable_trace()
|
|
170
|
+
elif config.debug:
|
|
171
|
+
self.enable_debug()
|
|
172
|
+
elif config.info:
|
|
173
|
+
self.enable_info()
|
|
174
|
+
else:
|
|
175
|
+
self.enable_default()
|
|
176
|
+
|
|
177
|
+
def _extract_logging_config(self, config: "Config") -> dict:
|
|
178
|
+
"""Extract btlogging's config from bittensor config
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
config (bittensor.core.config.Config): Bittensor config instance.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
(dict): btlogging's config from Bittensor config or Bittensor config.
|
|
185
|
+
"""
|
|
186
|
+
# This is to handle nature of DefaultMunch
|
|
187
|
+
if getattr(config, "logging", None):
|
|
188
|
+
return config.logging
|
|
189
|
+
else:
|
|
190
|
+
return config
|
|
191
|
+
|
|
192
|
+
def _configure_handlers(self, config) -> list[stdlogging.Handler]:
|
|
193
|
+
handlers = list()
|
|
194
|
+
|
|
195
|
+
# stream handler, a given
|
|
196
|
+
stream_handler = stdlogging.StreamHandler(sys.stdout)
|
|
197
|
+
stream_handler.setFormatter(self._stream_formatter)
|
|
198
|
+
handlers.append(stream_handler)
|
|
199
|
+
|
|
200
|
+
# file handler, maybe
|
|
201
|
+
if config.record_log and config.logging_dir:
|
|
202
|
+
logfile = os.path.abspath(
|
|
203
|
+
os.path.join(config.logging_dir, DEFAULT_LOG_FILE_NAME)
|
|
204
|
+
)
|
|
205
|
+
file_handler = self._create_file_handler(logfile)
|
|
206
|
+
handlers.append(file_handler)
|
|
207
|
+
return handlers
|
|
208
|
+
|
|
209
|
+
def get_config(self):
|
|
210
|
+
return self._config
|
|
211
|
+
|
|
212
|
+
def set_config(self, config: "Config"):
|
|
213
|
+
"""Set config after initialization, if desired.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
config (bittensor.core.config.Config): Bittensor config instance.
|
|
217
|
+
"""
|
|
218
|
+
self._config = self._extract_logging_config(config)
|
|
219
|
+
if self._config.logging_dir and self._config.record_log:
|
|
220
|
+
expanded_dir = os.path.expanduser(config.logging_dir)
|
|
221
|
+
logfile = os.path.abspath(os.path.join(expanded_dir, DEFAULT_LOG_FILE_NAME))
|
|
222
|
+
self._enable_file_logging(logfile)
|
|
223
|
+
if self._config.trace:
|
|
224
|
+
self.enable_trace()
|
|
225
|
+
elif self._config.debug:
|
|
226
|
+
self.enable_debug()
|
|
227
|
+
elif self._config.info:
|
|
228
|
+
self.enable_info()
|
|
229
|
+
|
|
230
|
+
def _create_and_start_listener(self, handlers):
|
|
231
|
+
"""
|
|
232
|
+
A listener to receive and publish log records.
|
|
233
|
+
|
|
234
|
+
This listener receives records from a queue populated by the main bittensor logger, as well as 3rd party loggers
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
listener = QueueListener(self._queue, *handlers, respect_handler_level=True)
|
|
238
|
+
listener.start()
|
|
239
|
+
atexit.register(listener.stop)
|
|
240
|
+
return listener
|
|
241
|
+
|
|
242
|
+
def get_queue(self):
|
|
243
|
+
"""
|
|
244
|
+
Get the queue the QueueListener is publishing from.
|
|
245
|
+
|
|
246
|
+
To set up logging in a separate process, a QueueHandler must be added to all the desired loggers.
|
|
247
|
+
"""
|
|
248
|
+
return self._queue
|
|
249
|
+
|
|
250
|
+
def _initialize_bt_logger(self, name: str):
|
|
251
|
+
"""
|
|
252
|
+
Initialize logging for bittensor.
|
|
253
|
+
|
|
254
|
+
Since the initial state is Default, logging level for the module logger is INFO, and all third-party loggers are
|
|
255
|
+
silenced. Subsequent state transitions will handle all logger outputs.
|
|
256
|
+
"""
|
|
257
|
+
logger = stdlogging.getLogger(name)
|
|
258
|
+
queue_handler = QueueHandler(self._queue)
|
|
259
|
+
logger.addHandler(queue_handler)
|
|
260
|
+
return logger
|
|
261
|
+
|
|
262
|
+
def _deinitialize_bt_logger(self, name: str):
|
|
263
|
+
"""Find the logger by name and remove the queue handler associated with it."""
|
|
264
|
+
logger = stdlogging.getLogger(name)
|
|
265
|
+
for handler in list(logger.handlers):
|
|
266
|
+
if isinstance(handler, QueueHandler):
|
|
267
|
+
logger.removeHandler(handler)
|
|
268
|
+
return logger
|
|
269
|
+
|
|
270
|
+
def _create_file_handler(self, logfile: str):
|
|
271
|
+
file_handler = RotatingFileHandler(
|
|
272
|
+
logfile,
|
|
273
|
+
maxBytes=DEFAULT_MAX_ROTATING_LOG_FILE_SIZE,
|
|
274
|
+
backupCount=DEFAULT_LOG_BACKUP_COUNT,
|
|
275
|
+
)
|
|
276
|
+
file_handler.setFormatter(self._file_formatter)
|
|
277
|
+
file_handler.setLevel(stdlogging.TRACE)
|
|
278
|
+
return file_handler
|
|
279
|
+
|
|
280
|
+
def register_primary_logger(self, name: str):
|
|
281
|
+
"""
|
|
282
|
+
Register a logger as primary logger
|
|
283
|
+
|
|
284
|
+
This adds a logger to the _primary_loggers set to ensure
|
|
285
|
+
it doesn't get disabled when disabling third-party loggers.
|
|
286
|
+
A queue handler is also associated with it.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
name (str): the name for primary logger.
|
|
290
|
+
"""
|
|
291
|
+
self._primary_loggers.add(name)
|
|
292
|
+
self._initialize_bt_logger(name)
|
|
293
|
+
|
|
294
|
+
def deregister_primary_logger(self, name: str):
|
|
295
|
+
"""
|
|
296
|
+
De-registers a primary logger
|
|
297
|
+
|
|
298
|
+
This function removes the logger from the _primary_loggers
|
|
299
|
+
set and deinitializes its queue handler
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
name (str): the name of primary logger.
|
|
303
|
+
"""
|
|
304
|
+
self._primary_loggers.remove(name)
|
|
305
|
+
self._deinitialize_bt_logger(name)
|
|
306
|
+
|
|
307
|
+
def enable_third_party_loggers(self):
|
|
308
|
+
"""Enables logging for third-party loggers by adding a queue handler to each."""
|
|
309
|
+
for logger in all_loggers():
|
|
310
|
+
if logger.name in self._primary_loggers:
|
|
311
|
+
continue
|
|
312
|
+
queue_handler = QueueHandler(self._queue)
|
|
313
|
+
logger.addHandler(queue_handler)
|
|
314
|
+
logger.setLevel(self._logger.level)
|
|
315
|
+
|
|
316
|
+
def disable_third_party_loggers(self):
|
|
317
|
+
"""Disables logging for third-party loggers by removing all their handlers."""
|
|
318
|
+
# remove all handlers
|
|
319
|
+
for logger in all_loggers():
|
|
320
|
+
if logger.name in self._primary_loggers:
|
|
321
|
+
continue
|
|
322
|
+
for handler in logger.handlers:
|
|
323
|
+
logger.removeHandler(handler)
|
|
324
|
+
|
|
325
|
+
def _enable_file_logging(self, logfile: str):
|
|
326
|
+
# preserve idempotency; do not create extra filehandlers
|
|
327
|
+
# if one already exists
|
|
328
|
+
if any(
|
|
329
|
+
[isinstance(handler, RotatingFileHandler) for handler in self._handlers]
|
|
330
|
+
):
|
|
331
|
+
return
|
|
332
|
+
file_handler = self._create_file_handler(logfile)
|
|
333
|
+
self._handlers.append(file_handler)
|
|
334
|
+
self._listener.handlers = tuple(self._handlers)
|
|
335
|
+
|
|
336
|
+
# state transitions
|
|
337
|
+
def before_transition(self, event, state):
|
|
338
|
+
"""Stops listener after transition."""
|
|
339
|
+
self._listener.stop()
|
|
340
|
+
|
|
341
|
+
def after_transition(self, event, state):
|
|
342
|
+
"""Starts listener after transition."""
|
|
343
|
+
self._listener.start()
|
|
344
|
+
|
|
345
|
+
# Default Logging
|
|
346
|
+
def before_enable_default(self):
|
|
347
|
+
"""Logs status before enable Default."""
|
|
348
|
+
self._logger.info("Enabling default logging (Warning level)")
|
|
349
|
+
self._logger.setLevel(stdlogging.WARNING)
|
|
350
|
+
for logger in all_loggers():
|
|
351
|
+
if logger.name in self._primary_loggers:
|
|
352
|
+
continue
|
|
353
|
+
logger.setLevel(stdlogging.CRITICAL)
|
|
354
|
+
|
|
355
|
+
def after_enable_default(self):
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
# Warning
|
|
359
|
+
def before_enable_warning(self):
|
|
360
|
+
"""Logs status before enable Warning."""
|
|
361
|
+
self._logger.info("Enabling warning.")
|
|
362
|
+
self._stream_formatter.set_trace(True)
|
|
363
|
+
for logger in all_loggers():
|
|
364
|
+
logger.setLevel(stdlogging.WARNING)
|
|
365
|
+
|
|
366
|
+
def after_enable_warning(self):
|
|
367
|
+
"""Logs status after enable Warning."""
|
|
368
|
+
self._logger.info("Warning enabled.")
|
|
369
|
+
|
|
370
|
+
# Info
|
|
371
|
+
def before_enable_info(self):
|
|
372
|
+
"""Logs status before enable info."""
|
|
373
|
+
self._logger.info("Enabling info logging.")
|
|
374
|
+
self._logger.setLevel(stdlogging.INFO)
|
|
375
|
+
for logger in all_loggers():
|
|
376
|
+
if logger.name in self._primary_loggers:
|
|
377
|
+
continue
|
|
378
|
+
logger.setLevel(stdlogging.INFO)
|
|
379
|
+
|
|
380
|
+
def after_enable_info(self):
|
|
381
|
+
"""Logs status after enable info."""
|
|
382
|
+
self._logger.info("Info enabled.")
|
|
383
|
+
|
|
384
|
+
# Trace
|
|
385
|
+
def before_enable_trace(self):
|
|
386
|
+
"""Logs status before enable Trace."""
|
|
387
|
+
self._logger.info("Enabling trace.")
|
|
388
|
+
self._stream_formatter.set_trace(True)
|
|
389
|
+
for logger in all_loggers():
|
|
390
|
+
logger.setLevel(stdlogging.TRACE)
|
|
391
|
+
|
|
392
|
+
def after_enable_trace(self):
|
|
393
|
+
"""Logs status after enable Trace."""
|
|
394
|
+
self._logger.info("Trace enabled.")
|
|
395
|
+
|
|
396
|
+
def before_disable_trace(self):
|
|
397
|
+
"""Logs status before disable Trace."""
|
|
398
|
+
self._logger.info("Disabling trace.")
|
|
399
|
+
self._stream_formatter.set_trace(False)
|
|
400
|
+
self.enable_default()
|
|
401
|
+
|
|
402
|
+
def after_disable_trace(self):
|
|
403
|
+
"""Logs status after disable Trace."""
|
|
404
|
+
self._logger.info("Trace disabled.")
|
|
405
|
+
|
|
406
|
+
# Debug
|
|
407
|
+
def before_enable_debug(self):
|
|
408
|
+
"""Logs status before enable Debug."""
|
|
409
|
+
self._logger.info("Enabling debug.")
|
|
410
|
+
self._stream_formatter.set_trace(True)
|
|
411
|
+
for logger in all_loggers():
|
|
412
|
+
logger.setLevel(stdlogging.DEBUG)
|
|
413
|
+
|
|
414
|
+
def before_enable_console(self):
|
|
415
|
+
"""Logs status before enable Console."""
|
|
416
|
+
self._stream_formatter.set_trace(True)
|
|
417
|
+
for logger in all_loggers():
|
|
418
|
+
logger.setLevel(stdlogging.DEBUG)
|
|
419
|
+
|
|
420
|
+
def after_enable_debug(self):
|
|
421
|
+
"""Logs status after enable Debug."""
|
|
422
|
+
self._logger.info("Debug enabled.")
|
|
423
|
+
|
|
424
|
+
def before_disable_debug(self):
|
|
425
|
+
"""Logs status before disable Debug."""
|
|
426
|
+
self._logger.info("Disabling debug.")
|
|
427
|
+
self._stream_formatter.set_trace(False)
|
|
428
|
+
self.enable_default()
|
|
429
|
+
|
|
430
|
+
def after_disable_debug(self):
|
|
431
|
+
"""Logs status after disable Debug."""
|
|
432
|
+
self._logger.info("Debug disabled.")
|
|
433
|
+
|
|
434
|
+
# Disable Logging
|
|
435
|
+
def before_disable_logging(self):
|
|
436
|
+
"""
|
|
437
|
+
Prepares the logging system for disabling.
|
|
438
|
+
|
|
439
|
+
This method performs the following actions:
|
|
440
|
+
1. Logs an informational message indicating that logging is being disabled.
|
|
441
|
+
2. Disables trace mode in the stream formatter.
|
|
442
|
+
3. Sets the logging level to CRITICAL for all loggers.
|
|
443
|
+
|
|
444
|
+
This ensures that only critical messages will be logged after this method is called.
|
|
445
|
+
"""
|
|
446
|
+
self._logger.info("Disabling logging.")
|
|
447
|
+
self._stream_formatter.set_trace(False)
|
|
448
|
+
|
|
449
|
+
for logger in all_loggers():
|
|
450
|
+
logger.setLevel(stdlogging.CRITICAL)
|
|
451
|
+
|
|
452
|
+
# Required API support log commands for API backwards compatibility.
|
|
453
|
+
@property
|
|
454
|
+
def __trace_on__(self) -> bool:
|
|
455
|
+
"""
|
|
456
|
+
Checks if the current state is in "Trace" mode.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
bool: True if the current state is "Trace", otherwise False.
|
|
460
|
+
"""
|
|
461
|
+
return self.current_state_value == "Trace"
|
|
462
|
+
|
|
463
|
+
def trace(self, msg="", prefix="", suffix="", *args, stacklevel=1, **kwargs):
|
|
464
|
+
"""Wraps trace message with prefix and suffix."""
|
|
465
|
+
msg = _concat_message(msg, prefix, suffix)
|
|
466
|
+
self._logger.trace(
|
|
467
|
+
msg,
|
|
468
|
+
*args,
|
|
469
|
+
**kwargs,
|
|
470
|
+
stacklevel=stacklevel + CUSTOM_LOGGER_METHOD_STACK_LEVEL,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
def debug(self, msg="", prefix="", suffix="", *args, stacklevel=1, **kwargs):
|
|
474
|
+
"""Wraps debug message with prefix and suffix."""
|
|
475
|
+
msg = _concat_message(msg, prefix, suffix)
|
|
476
|
+
self._logger.debug(msg, *args, **kwargs, stacklevel=stacklevel + 1)
|
|
477
|
+
|
|
478
|
+
def info(self, msg="", prefix="", suffix="", *args, stacklevel=1, **kwargs):
|
|
479
|
+
"""Wraps info message with prefix and suffix."""
|
|
480
|
+
msg = _concat_message(msg, prefix, suffix)
|
|
481
|
+
self._logger.info(msg, *args, **kwargs, stacklevel=stacklevel + 1)
|
|
482
|
+
|
|
483
|
+
def success(self, msg="", prefix="", suffix="", *args, stacklevel=1, **kwargs):
|
|
484
|
+
"""Wraps success message with prefix and suffix."""
|
|
485
|
+
msg = _concat_message(msg, prefix, suffix)
|
|
486
|
+
self._logger.success(
|
|
487
|
+
msg,
|
|
488
|
+
*args,
|
|
489
|
+
**kwargs,
|
|
490
|
+
stacklevel=stacklevel + CUSTOM_LOGGER_METHOD_STACK_LEVEL,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
def warning(self, msg="", prefix="", suffix="", *args, stacklevel=1, **kwargs):
|
|
494
|
+
"""Wraps warning message with prefix and suffix."""
|
|
495
|
+
msg = _concat_message(msg, prefix, suffix)
|
|
496
|
+
self._logger.warning(msg, *args, **kwargs, stacklevel=stacklevel + 1)
|
|
497
|
+
|
|
498
|
+
def error(self, msg="", prefix="", suffix="", *args, stacklevel=1, **kwargs):
|
|
499
|
+
"""Wraps error message with prefix and suffix."""
|
|
500
|
+
msg = _concat_message(msg, prefix, suffix)
|
|
501
|
+
self._logger.error(msg, *args, **kwargs, stacklevel=stacklevel + 1)
|
|
502
|
+
|
|
503
|
+
def critical(self, msg="", prefix="", suffix="", *args, stacklevel=1, **kwargs):
|
|
504
|
+
"""Wraps critical message with prefix and suffix."""
|
|
505
|
+
msg = _concat_message(msg, prefix, suffix)
|
|
506
|
+
self._logger.critical(msg, *args, **kwargs, stacklevel=stacklevel + 1)
|
|
507
|
+
|
|
508
|
+
def exception(self, msg="", prefix="", suffix="", *args, stacklevel=1, **kwargs):
|
|
509
|
+
"""Wraps exception message with prefix and suffix."""
|
|
510
|
+
msg = _concat_message(msg, prefix, suffix)
|
|
511
|
+
self._logger.exception(msg, *args, **kwargs, stacklevel=stacklevel + 1)
|
|
512
|
+
|
|
513
|
+
def on(self):
|
|
514
|
+
"""Enable default state."""
|
|
515
|
+
self._logger.info("Logging enabled.")
|
|
516
|
+
self.enable_default()
|
|
517
|
+
|
|
518
|
+
def off(self):
|
|
519
|
+
"""Disables all states."""
|
|
520
|
+
self.disable_logging()
|
|
521
|
+
|
|
522
|
+
def set_debug(self, on: bool = True):
|
|
523
|
+
"""Sets Debug state."""
|
|
524
|
+
if on and not self.current_state_value == "Debug":
|
|
525
|
+
self.enable_debug()
|
|
526
|
+
elif not on:
|
|
527
|
+
if self.current_state_value == "Debug":
|
|
528
|
+
self.disable_debug()
|
|
529
|
+
|
|
530
|
+
def set_trace(self, on: bool = True):
|
|
531
|
+
"""Sets Trace state."""
|
|
532
|
+
if on and not self.current_state_value == "Trace":
|
|
533
|
+
self.enable_trace()
|
|
534
|
+
elif not on:
|
|
535
|
+
if self.current_state_value == "Trace":
|
|
536
|
+
self.disable_trace()
|
|
537
|
+
|
|
538
|
+
def set_info(self, on: bool = True):
|
|
539
|
+
"""Sets Info state."""
|
|
540
|
+
if on and not self.current_state_value == "Info":
|
|
541
|
+
self.enable_info()
|
|
542
|
+
elif not on:
|
|
543
|
+
if self.current_state_value == "Info":
|
|
544
|
+
self.disable_info()
|
|
545
|
+
|
|
546
|
+
def set_warning(self, on: bool = True):
|
|
547
|
+
"""Sets Warning state."""
|
|
548
|
+
if on and not self.current_state_value == "Warning":
|
|
549
|
+
self.enable_warning()
|
|
550
|
+
elif not on:
|
|
551
|
+
if self.current_state_value == "Warning":
|
|
552
|
+
self.disable_warning()
|
|
553
|
+
|
|
554
|
+
def set_default(self):
|
|
555
|
+
"""Sets Default state."""
|
|
556
|
+
if not self.current_state_value == "Default":
|
|
557
|
+
self.enable_default()
|
|
558
|
+
|
|
559
|
+
def set_console(self):
|
|
560
|
+
"""Sets Console state."""
|
|
561
|
+
if not self.current_state_value == "Console":
|
|
562
|
+
self.enable_console()
|
|
563
|
+
|
|
564
|
+
def get_level(self) -> int:
|
|
565
|
+
"""Returns Logging level."""
|
|
566
|
+
return self._logger.level
|
|
567
|
+
|
|
568
|
+
def setLevel(self, level):
|
|
569
|
+
"""Set the specified level on the underlying logger."""
|
|
570
|
+
self._logger.setLevel(level)
|
|
571
|
+
|
|
572
|
+
def check_config(self, config: "Config"):
|
|
573
|
+
assert config.logging
|
|
574
|
+
|
|
575
|
+
def help(self):
|
|
576
|
+
pass
|
|
577
|
+
|
|
578
|
+
@classmethod
|
|
579
|
+
def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None):
|
|
580
|
+
"""Accept specific arguments fro parser"""
|
|
581
|
+
prefix_str = "" if prefix is None else prefix + "."
|
|
582
|
+
try:
|
|
583
|
+
default_logging_debug = os.getenv("BT_LOGGING_DEBUG") or False
|
|
584
|
+
default_logging_info = os.getenv("BT_LOGGING_INFO") or False
|
|
585
|
+
default_logging_trace = os.getenv("BT_LOGGING_TRACE") or False
|
|
586
|
+
default_logging_record_log = os.getenv("BT_LOGGING_RECORD_LOG") or False
|
|
587
|
+
default_logging_logging_dir = os.getenv(
|
|
588
|
+
"BT_LOGGING_LOGGING_DIR"
|
|
589
|
+
) or os.path.join("~", ".bittensor", "miners")
|
|
590
|
+
parser.add_argument(
|
|
591
|
+
"--" + prefix_str + "logging.debug",
|
|
592
|
+
action="store_true",
|
|
593
|
+
help="""Turn on bittensor debugging information""",
|
|
594
|
+
default=default_logging_debug,
|
|
595
|
+
)
|
|
596
|
+
parser.add_argument(
|
|
597
|
+
"--" + prefix_str + "logging.trace",
|
|
598
|
+
action="store_true",
|
|
599
|
+
help="""Turn on bittensor trace level information""",
|
|
600
|
+
default=default_logging_trace,
|
|
601
|
+
)
|
|
602
|
+
parser.add_argument(
|
|
603
|
+
"--" + prefix_str + "logging.info",
|
|
604
|
+
action="store_true",
|
|
605
|
+
help="""Turn on bittensor info level information""",
|
|
606
|
+
default=default_logging_info,
|
|
607
|
+
)
|
|
608
|
+
parser.add_argument(
|
|
609
|
+
"--" + prefix_str + "logging.record_log",
|
|
610
|
+
action="store_true",
|
|
611
|
+
help="""Turns on logging to file.""",
|
|
612
|
+
default=default_logging_record_log,
|
|
613
|
+
)
|
|
614
|
+
parser.add_argument(
|
|
615
|
+
"--" + prefix_str + "logging.logging_dir",
|
|
616
|
+
type=str,
|
|
617
|
+
help="Logging default root directory.",
|
|
618
|
+
default=default_logging_logging_dir,
|
|
619
|
+
)
|
|
620
|
+
except argparse.ArgumentError:
|
|
621
|
+
# re-parsing arguments.
|
|
622
|
+
pass
|
|
623
|
+
|
|
624
|
+
@classmethod
|
|
625
|
+
def config(cls) -> "Config":
|
|
626
|
+
"""Get config from the argument parser.
|
|
627
|
+
|
|
628
|
+
Return:
|
|
629
|
+
config (bittensor.core.config.Config): config object
|
|
630
|
+
"""
|
|
631
|
+
parser = argparse.ArgumentParser()
|
|
632
|
+
cls.add_args(parser)
|
|
633
|
+
return Config(parser)
|
|
634
|
+
|
|
635
|
+
def __call__(
|
|
636
|
+
self,
|
|
637
|
+
config: "Config" = None,
|
|
638
|
+
debug: bool = None,
|
|
639
|
+
trace: bool = None,
|
|
640
|
+
info: bool = None,
|
|
641
|
+
record_log: bool = None,
|
|
642
|
+
logging_dir: str = None,
|
|
643
|
+
):
|
|
644
|
+
if config is not None:
|
|
645
|
+
cfg = self._extract_logging_config(config)
|
|
646
|
+
if info is not None:
|
|
647
|
+
cfg.info = info
|
|
648
|
+
elif debug is not None:
|
|
649
|
+
cfg.debug = debug
|
|
650
|
+
elif trace is not None:
|
|
651
|
+
cfg.trace = trace
|
|
652
|
+
if record_log is not None:
|
|
653
|
+
cfg.record_log = record_log
|
|
654
|
+
if logging_dir is not None:
|
|
655
|
+
cfg.logging_dir = logging_dir
|
|
656
|
+
else:
|
|
657
|
+
cfg = LoggingConfig(
|
|
658
|
+
debug=debug,
|
|
659
|
+
trace=trace,
|
|
660
|
+
info=info,
|
|
661
|
+
record_log=record_log,
|
|
662
|
+
logging_dir=logging_dir,
|
|
663
|
+
)
|
|
664
|
+
self.set_config(cfg)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Iterable,List, Type
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import text,insert,create_engine
|
|
4
|
+
from sqlalchemy.exc import OperationalError
|
|
5
|
+
from sqlalchemy.orm import sessionmaker, Session
|
|
6
|
+
from sqlalchemy.ext.declarative import as_declarative
|
|
7
|
+
import cxppython as cc
|
|
8
|
+
|
|
9
|
+
@as_declarative()
|
|
10
|
+
class Base:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class MysqlDB:
|
|
14
|
+
__instance = None
|
|
15
|
+
|
|
16
|
+
def __init__(self, mysql_config):
|
|
17
|
+
if MysqlDB.__instance is not None:
|
|
18
|
+
raise Exception("This class is a singleton, use DB.create()")
|
|
19
|
+
else:
|
|
20
|
+
MysqlDB.__instance = self
|
|
21
|
+
self.engine = self.create_engine(mysql_config)
|
|
22
|
+
self.session = sessionmaker(bind=self.engine)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def instance():
|
|
26
|
+
return MysqlDB.__instance
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def session() -> Session:
|
|
30
|
+
# engine = DB.__instance.engine
|
|
31
|
+
# session = sessionmaker(bind=engine)
|
|
32
|
+
# return session()
|
|
33
|
+
session = MysqlDB.__instance.session
|
|
34
|
+
return session()
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def add(value) -> Exception | None:
|
|
38
|
+
try:
|
|
39
|
+
session = MysqlDB.session()
|
|
40
|
+
session.add(value)
|
|
41
|
+
session.commit()
|
|
42
|
+
session.close()
|
|
43
|
+
except Exception as err:
|
|
44
|
+
return err
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def bulk_save(objects: Iterable[object]) -> Exception | None:
|
|
50
|
+
try:
|
|
51
|
+
with MysqlDB.session() as session, session.begin():
|
|
52
|
+
session.bulk_save_objects(objects)
|
|
53
|
+
except Exception as err:
|
|
54
|
+
return err
|
|
55
|
+
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def create(mysql_config):
|
|
60
|
+
if MysqlDB.__instance is None:
|
|
61
|
+
MysqlDB.__instance = MysqlDB(mysql_config)
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def test_db_connection():
|
|
65
|
+
try:
|
|
66
|
+
# 尝试建立连接
|
|
67
|
+
with MysqlDB.instance().engine.connect() as connection:
|
|
68
|
+
cc.logging.success("Database connection successful!")
|
|
69
|
+
return True
|
|
70
|
+
except OperationalError as e:
|
|
71
|
+
cc.logging.error(f"Failed to connect to the database: {e}")
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
def create_engine(self, mysql_config):
|
|
75
|
+
echo = False
|
|
76
|
+
if "echo" in mysql_config:
|
|
77
|
+
echo = mysql_config["echo"]
|
|
78
|
+
return create_engine(
|
|
79
|
+
'mysql+pymysql://{user}:{password}@{host}:{port}/{database}'.format(**mysql_config),
|
|
80
|
+
pool_size=200,
|
|
81
|
+
max_overflow=0,
|
|
82
|
+
echo=echo)
|
|
83
|
+
|
|
84
|
+
def connect(self):
|
|
85
|
+
return self.engine.connect()
|
|
86
|
+
|
|
87
|
+
def batch_insert_records(session: Session,
|
|
88
|
+
model: Type[Base],
|
|
89
|
+
records: List,
|
|
90
|
+
batch_size: int = 50,
|
|
91
|
+
ignore_existing: bool = True,
|
|
92
|
+
commit_per_batch: bool = True):
|
|
93
|
+
data = records
|
|
94
|
+
for i in range(0, len(data), batch_size):
|
|
95
|
+
batch = data[i:i + batch_size]
|
|
96
|
+
stmt = insert(model).values(batch)
|
|
97
|
+
if ignore_existing:
|
|
98
|
+
stmt = stmt.prefix_with("IGNORE")
|
|
99
|
+
session.execute(stmt)
|
|
100
|
+
|
|
101
|
+
if commit_per_batch:
|
|
102
|
+
session.commit()
|
|
103
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from cxppython.utils.btlogging import logging
|
|
2
|
+
|
|
3
|
+
# Logging helpers.
|
|
4
|
+
def trace(on: bool = True):
|
|
5
|
+
"""
|
|
6
|
+
Enables or disables trace logging.
|
|
7
|
+
Args:
|
|
8
|
+
on (bool): If True, enables trace logging. If False, disables trace logging.
|
|
9
|
+
"""
|
|
10
|
+
logging.set_trace(on)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def debug(on: bool = True):
|
|
14
|
+
"""
|
|
15
|
+
Enables or disables debug logging.
|
|
16
|
+
Args:
|
|
17
|
+
on (bool): If True, enables debug logging. If False, disables debug logging.
|
|
18
|
+
"""
|
|
19
|
+
logging.set_debug(on)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def warning(on: bool = True):
|
|
23
|
+
"""
|
|
24
|
+
Enables or disables warning logging.
|
|
25
|
+
Args:
|
|
26
|
+
on (bool): If True, enables warning logging. If False, disables warning logging and sets default (WARNING) level.
|
|
27
|
+
"""
|
|
28
|
+
logging.set_warning(on)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def info(on: bool = True):
|
|
32
|
+
"""
|
|
33
|
+
Enables or disables info logging.
|
|
34
|
+
Args:
|
|
35
|
+
on (bool): If True, enables info logging. If False, disables info logging and sets default (WARNING) level.
|
|
36
|
+
"""
|
|
37
|
+
logging.set_info(on)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: cxppython
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: A python utils package
|
|
5
5
|
Home-page: https://github.com/yourusername/my_package
|
|
6
6
|
Author: cxp
|
|
@@ -25,3 +25,10 @@ Dynamic: requires-python
|
|
|
25
25
|
Dynamic: summary
|
|
26
26
|
|
|
27
27
|
# cxppython
|
|
28
|
+
### install build
|
|
29
|
+
- python -m pip install --upgrade pip setuptools wheel twine
|
|
30
|
+
-
|
|
31
|
+
### build
|
|
32
|
+
- python setup.py sdist bdist_wheel
|
|
33
|
+
### upload
|
|
34
|
+
- twine upload dist/*
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
cxppython/__init__.py
|
|
5
|
+
cxppython/main.py
|
|
6
|
+
cxppython.egg-info/PKG-INFO
|
|
7
|
+
cxppython.egg-info/SOURCES.txt
|
|
8
|
+
cxppython.egg-info/dependency_links.txt
|
|
9
|
+
cxppython.egg-info/requires.txt
|
|
10
|
+
cxppython.egg-info/top_level.txt
|
|
11
|
+
cxppython/utils/__init__.py
|
|
12
|
+
cxppython/utils/easy_imports.py
|
|
13
|
+
cxppython/utils/btlogging/__init__.py
|
|
14
|
+
cxppython/utils/btlogging/console.py
|
|
15
|
+
cxppython/utils/btlogging/defines.py
|
|
16
|
+
cxppython/utils/btlogging/format.py
|
|
17
|
+
cxppython/utils/btlogging/helpers.py
|
|
18
|
+
cxppython/utils/btlogging/loggingmachine.py
|
|
19
|
+
cxppython/utils/database/__init__.py
|
|
20
|
+
cxppython/utils/database/mysql.py
|
|
@@ -2,8 +2,8 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="cxppython", # 包名
|
|
5
|
-
version="0.0.
|
|
6
|
-
packages=find_packages(), # 自动找到所有包
|
|
5
|
+
version="0.0.2", # 版本号
|
|
6
|
+
packages=find_packages(exclude=["tests", "tests.*"]), # 自动找到所有包
|
|
7
7
|
description="A python utils package",
|
|
8
8
|
long_description=open("README.md").read(),
|
|
9
9
|
long_description_content_type="text/markdown",
|
cxppython-0.0.1/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# cxppython
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|