hello-datap-component-base 0.2.0__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.
- hello_datap_component_base/__init__.py +31 -0
- hello_datap_component_base/base.py +211 -0
- hello_datap_component_base/cli.py +276 -0
- hello_datap_component_base/config.py +169 -0
- hello_datap_component_base/discover.py +187 -0
- hello_datap_component_base/logger.py +290 -0
- hello_datap_component_base/mns_client.py +286 -0
- hello_datap_component_base/runner.py +247 -0
- hello_datap_component_base-0.2.0.dist-info/METADATA +596 -0
- hello_datap_component_base-0.2.0.dist-info/RECORD +13 -0
- hello_datap_component_base-0.2.0.dist-info/WHEEL +5 -0
- hello_datap_component_base-0.2.0.dist-info/entry_points.txt +2 -0
- hello_datap_component_base-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import inspect
|
|
3
|
+
import pkgutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Type, Optional, Tuple
|
|
6
|
+
from .base import BaseService
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def find_service_classes(
|
|
10
|
+
search_path: str = ".",
|
|
11
|
+
exclude_dirs: List[str] = None
|
|
12
|
+
) -> List[Tuple[str, Type[BaseService]]]:
|
|
13
|
+
"""
|
|
14
|
+
查找所有继承自 BaseService 的类
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
search_path: 搜索路径
|
|
18
|
+
exclude_dirs: 排除的目录
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
列表,每个元素是 (模块名, 类) 的元组
|
|
22
|
+
"""
|
|
23
|
+
if exclude_dirs is None:
|
|
24
|
+
exclude_dirs = ["__pycache__", ".git", ".pytest_cache", "venv", "env", ".venv",
|
|
25
|
+
"hello_datap_component_base.egg-info", "build", "dist"]
|
|
26
|
+
|
|
27
|
+
import sys
|
|
28
|
+
import os
|
|
29
|
+
|
|
30
|
+
# 确保搜索路径在 Python 路径中,以便能够导入模块
|
|
31
|
+
search_path_obj = Path(search_path).resolve()
|
|
32
|
+
search_path_str = str(search_path_obj)
|
|
33
|
+
|
|
34
|
+
# 确保当前工作目录和搜索路径都在 sys.path 中
|
|
35
|
+
current_dir = os.getcwd()
|
|
36
|
+
if current_dir not in sys.path:
|
|
37
|
+
sys.path.insert(0, current_dir)
|
|
38
|
+
if search_path_str not in sys.path:
|
|
39
|
+
sys.path.insert(0, search_path_str)
|
|
40
|
+
# 确保 '.' 也在路径中(相对导入)
|
|
41
|
+
if '.' not in sys.path:
|
|
42
|
+
sys.path.insert(0, '.')
|
|
43
|
+
|
|
44
|
+
service_classes = []
|
|
45
|
+
|
|
46
|
+
# 遍历目录查找 Python 文件
|
|
47
|
+
for py_file in search_path_obj.rglob("*.py"):
|
|
48
|
+
# 跳过排除的目录
|
|
49
|
+
py_file_str = str(py_file)
|
|
50
|
+
py_file_parts = py_file.parts
|
|
51
|
+
|
|
52
|
+
# 检查是否在排除的目录中
|
|
53
|
+
if any(exclude in py_file_parts for exclude in exclude_dirs):
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
# 跳过包目录(hello_datap_component_base)下的文件
|
|
57
|
+
# 但允许根目录下的其他文件(如 example_service.py)
|
|
58
|
+
if "hello_datap_component_base" in py_file_parts:
|
|
59
|
+
# 如果文件在包目录内(不是包目录本身作为文件名),跳过
|
|
60
|
+
idx = py_file_parts.index("hello_datap_component_base")
|
|
61
|
+
if idx < len(py_file_parts) - 1: # 包目录下还有子路径
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
# 计算模块路径
|
|
65
|
+
relative_path = py_file.relative_to(search_path_obj)
|
|
66
|
+
module_path = str(relative_path.with_suffix('')).replace('/', '.')
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# 动态导入模块
|
|
70
|
+
module = importlib.import_module(module_path)
|
|
71
|
+
|
|
72
|
+
# 查找模块中继承自 BaseService 的类
|
|
73
|
+
for name, obj in inspect.getmembers(module, inspect.isclass):
|
|
74
|
+
try:
|
|
75
|
+
# 检查是否是 BaseService 的子类
|
|
76
|
+
if (inspect.isclass(obj) and
|
|
77
|
+
issubclass(obj, BaseService) and
|
|
78
|
+
obj is not BaseService and
|
|
79
|
+
not inspect.isabstract(obj)):
|
|
80
|
+
|
|
81
|
+
# 检查是否实现了 process 方法
|
|
82
|
+
if hasattr(obj, 'process'):
|
|
83
|
+
service_classes.append((module_path, obj))
|
|
84
|
+
except (TypeError, AttributeError) as e:
|
|
85
|
+
# 跳过不是类的对象或无法检查的对象
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
except (ImportError, ValueError, AttributeError) as e:
|
|
89
|
+
# 对于根目录下的用户文件,输出错误信息以便调试
|
|
90
|
+
# 检查是否是搜索路径下的直接文件(不是包内的文件)
|
|
91
|
+
relative_to_search = py_file.relative_to(search_path_obj)
|
|
92
|
+
is_root_file = len(relative_to_search.parts) == 1 and py_file.name not in ["__init__.py"]
|
|
93
|
+
|
|
94
|
+
if is_root_file:
|
|
95
|
+
# 输出到stderr以便用户看到
|
|
96
|
+
import sys
|
|
97
|
+
error_msg = str(e)
|
|
98
|
+
# 检查是否是缺少模块的错误
|
|
99
|
+
if "No module named" in error_msg or "ModuleNotFoundError" in error_msg:
|
|
100
|
+
print(f"⚠️ 警告: 导入 {module_path} 失败,缺少依赖: {error_msg}", file=sys.stderr)
|
|
101
|
+
print(f" 提示: 请检查配置文件的 runtime_env.pip 是否包含所需的包", file=sys.stderr)
|
|
102
|
+
else:
|
|
103
|
+
print(f"⚠️ 警告: 导入 {module_path} 失败: {error_msg}", file=sys.stderr)
|
|
104
|
+
continue
|
|
105
|
+
except Exception as e:
|
|
106
|
+
# 对于根目录下的用户文件,输出错误信息以便调试
|
|
107
|
+
relative_to_search = py_file.relative_to(search_path_obj)
|
|
108
|
+
is_root_file = len(relative_to_search.parts) == 1 and py_file.name not in ["__init__.py"]
|
|
109
|
+
|
|
110
|
+
if is_root_file:
|
|
111
|
+
import sys
|
|
112
|
+
error_msg = str(e)
|
|
113
|
+
if "No module named" in error_msg or "ModuleNotFoundError" in error_msg:
|
|
114
|
+
print(f"⚠️ 警告: 导入 {module_path} 失败,缺少依赖: {error_msg}", file=sys.stderr)
|
|
115
|
+
print(f" 提示: 请检查配置文件的 runtime_env.pip 是否包含所需的包", file=sys.stderr)
|
|
116
|
+
else:
|
|
117
|
+
print(f"⚠️ 警告: 导入 {module_path} 时出错: {error_msg}", file=sys.stderr)
|
|
118
|
+
import traceback
|
|
119
|
+
traceback.print_exc(file=sys.stderr)
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
return service_classes
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_single_service_class(
|
|
126
|
+
search_path: str = ".",
|
|
127
|
+
class_name: Optional[str] = None
|
|
128
|
+
) -> Type[BaseService]:
|
|
129
|
+
"""
|
|
130
|
+
获取单个服务类
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
search_path: 搜索路径
|
|
134
|
+
class_name: 指定的类名(可选)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
服务类
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
ValueError: 如果找到0个或多个服务类
|
|
141
|
+
"""
|
|
142
|
+
service_classes = find_service_classes(search_path)
|
|
143
|
+
|
|
144
|
+
if not service_classes:
|
|
145
|
+
import os
|
|
146
|
+
current_dir = os.getcwd()
|
|
147
|
+
search_abs = os.path.abspath(search_path)
|
|
148
|
+
|
|
149
|
+
# 尝试列出一些可能的服务文件
|
|
150
|
+
possible_files = []
|
|
151
|
+
for py_file in Path(search_abs).rglob("*.py"):
|
|
152
|
+
if "example" in str(py_file).lower() and "service" in str(py_file).lower():
|
|
153
|
+
possible_files.append(str(py_file.relative_to(search_abs)))
|
|
154
|
+
if len(possible_files) >= 3:
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
error_msg = (
|
|
158
|
+
f"No service class found in '{search_abs}' (current directory: {current_dir}).\n"
|
|
159
|
+
f"Please ensure:\n"
|
|
160
|
+
f" 1. You are in the project root directory\n"
|
|
161
|
+
f" 2. There is a Python file containing a class that inherits from BaseService\n"
|
|
162
|
+
f" 3. The service class implements the 'process' method\n"
|
|
163
|
+
)
|
|
164
|
+
if possible_files:
|
|
165
|
+
error_msg += f"\nPossible service files found:\n"
|
|
166
|
+
for f in possible_files:
|
|
167
|
+
error_msg += f" - {f}\n"
|
|
168
|
+
error_msg += f"\nTry using --class-name to specify the service class name."
|
|
169
|
+
|
|
170
|
+
raise ValueError(error_msg)
|
|
171
|
+
|
|
172
|
+
if class_name:
|
|
173
|
+
# 查找指定类名的服务
|
|
174
|
+
for module_path, cls in service_classes:
|
|
175
|
+
if cls.__name__ == class_name:
|
|
176
|
+
return cls
|
|
177
|
+
raise ValueError(f"Service class '{class_name}' not found")
|
|
178
|
+
|
|
179
|
+
# 检查是否只有一个服务类
|
|
180
|
+
if len(service_classes) > 1:
|
|
181
|
+
class_list = [f"{module}.{cls.__name__}" for module, cls in service_classes]
|
|
182
|
+
raise ValueError(
|
|
183
|
+
f"Multiple service classes found: {class_list}. "
|
|
184
|
+
f"Please specify which one to use with --class-name."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return service_classes[0][1]
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Optional, Dict, Any
|
|
4
|
+
import json
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pythonjsonlogger import jsonlogger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ServiceLoggerAdapter(logging.LoggerAdapter):
|
|
10
|
+
"""服务日志适配器,自动在每条日志中添加服务名称和版本信息"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, logger: logging.Logger, service_name: str, version: Optional[str] = None):
|
|
13
|
+
super().__init__(logger, {})
|
|
14
|
+
self.service_name = service_name
|
|
15
|
+
self.version = version
|
|
16
|
+
|
|
17
|
+
def process(self, msg, kwargs):
|
|
18
|
+
"""处理日志消息,添加服务信息"""
|
|
19
|
+
# 确保 extra 字典存在
|
|
20
|
+
if 'extra' not in kwargs:
|
|
21
|
+
kwargs['extra'] = {}
|
|
22
|
+
|
|
23
|
+
# 添加服务名称和版本信息
|
|
24
|
+
kwargs['extra']['service'] = self.service_name
|
|
25
|
+
if self.version:
|
|
26
|
+
kwargs['extra']['version'] = self.version
|
|
27
|
+
|
|
28
|
+
return msg, kwargs
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CustomJsonFormatter(jsonlogger.JsonFormatter):
|
|
32
|
+
"""自定义 JSON 格式化器"""
|
|
33
|
+
|
|
34
|
+
def add_fields(self, log_record: Dict[str, Any], record: logging.LogRecord, message_dict: Dict[str, Any]):
|
|
35
|
+
super().add_fields(log_record, record, message_dict)
|
|
36
|
+
|
|
37
|
+
# 添加时间戳
|
|
38
|
+
if not log_record.get('timestamp'):
|
|
39
|
+
log_record['timestamp'] = datetime.utcnow().isoformat()
|
|
40
|
+
|
|
41
|
+
# 添加日志级别
|
|
42
|
+
if not log_record.get('level'):
|
|
43
|
+
log_record['level'] = record.levelname
|
|
44
|
+
|
|
45
|
+
# 添加进程信息
|
|
46
|
+
log_record['pid'] = record.process
|
|
47
|
+
log_record['process_name'] = record.processName
|
|
48
|
+
|
|
49
|
+
# 添加文件位置
|
|
50
|
+
log_record['file'] = record.filename
|
|
51
|
+
log_record['line'] = record.lineno
|
|
52
|
+
log_record['function'] = record.funcName
|
|
53
|
+
|
|
54
|
+
# 确保服务信息在 JSON 日志中(如果 extra 中有的话)
|
|
55
|
+
if 'service' in log_record:
|
|
56
|
+
log_record['service'] = log_record['service']
|
|
57
|
+
if 'version' in log_record:
|
|
58
|
+
log_record['version'] = log_record['version']
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def setup_logging(
|
|
62
|
+
level: str = "INFO",
|
|
63
|
+
json_format: bool = False,
|
|
64
|
+
service_name: str = "unknown",
|
|
65
|
+
version: Optional[str] = None
|
|
66
|
+
):
|
|
67
|
+
"""
|
|
68
|
+
设置日志配置
|
|
69
|
+
|
|
70
|
+
兼容 Ray 分布式计算框架:日志输出到 stdout/stderr,Ray 会自动收集这些日志。
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
level: 日志级别
|
|
74
|
+
json_format: 是否使用 JSON 格式
|
|
75
|
+
service_name: 服务名称
|
|
76
|
+
version: 服务版本(可选)
|
|
77
|
+
"""
|
|
78
|
+
# 检测是否在 Ray 环境中运行
|
|
79
|
+
is_ray_env = False
|
|
80
|
+
try:
|
|
81
|
+
import ray
|
|
82
|
+
is_ray_env = ray.is_initialized()
|
|
83
|
+
except ImportError:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
# 移除所有现有的处理器
|
|
87
|
+
root_logger = logging.getLogger()
|
|
88
|
+
for handler in root_logger.handlers[:]:
|
|
89
|
+
root_logger.removeHandler(handler)
|
|
90
|
+
|
|
91
|
+
# 设置日志级别
|
|
92
|
+
numeric_level = getattr(logging, level.upper(), None)
|
|
93
|
+
if not isinstance(numeric_level, int):
|
|
94
|
+
numeric_level = logging.INFO
|
|
95
|
+
|
|
96
|
+
root_logger.setLevel(numeric_level)
|
|
97
|
+
|
|
98
|
+
# 创建处理器
|
|
99
|
+
if json_format:
|
|
100
|
+
formatter = CustomJsonFormatter(
|
|
101
|
+
'%(timestamp)s %(level)s %(name)s %(message)s',
|
|
102
|
+
rename_fields={
|
|
103
|
+
'level': 'levelname',
|
|
104
|
+
'timestamp': 'asctime'
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
else:
|
|
108
|
+
# 改进日志格式,包含服务名称和版本
|
|
109
|
+
version_str = f" v{version}" if version else ""
|
|
110
|
+
formatter = logging.Formatter(
|
|
111
|
+
f'%(asctime)s - [{service_name}{version_str}] - %(levelname)s - '
|
|
112
|
+
f'%(filename)s:%(lineno)d - %(message)s',
|
|
113
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# 控制台处理器(输出到 stdout,Ray 会自动捕获)
|
|
117
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
118
|
+
console_handler.setFormatter(formatter)
|
|
119
|
+
root_logger.addHandler(console_handler)
|
|
120
|
+
|
|
121
|
+
# 错误日志输出到 stderr(Ray 也会捕获)
|
|
122
|
+
error_handler = logging.StreamHandler(sys.stderr)
|
|
123
|
+
error_handler.setFormatter(formatter)
|
|
124
|
+
error_handler.setLevel(logging.ERROR)
|
|
125
|
+
root_logger.addHandler(error_handler)
|
|
126
|
+
|
|
127
|
+
# 文件处理器(在 Ray 环境中可能不可用,优雅处理)
|
|
128
|
+
if not is_ray_env:
|
|
129
|
+
# 非 Ray 环境才尝试创建文件日志
|
|
130
|
+
try:
|
|
131
|
+
file_handler = logging.FileHandler(f"{service_name}.log", encoding='utf-8')
|
|
132
|
+
file_handler.setFormatter(formatter)
|
|
133
|
+
root_logger.addHandler(file_handler)
|
|
134
|
+
except (IOError, PermissionError):
|
|
135
|
+
pass # 无法创建日志文件,只输出到控制台
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def get_service_logger(name: str, version: Optional[str] = None) -> ServiceLoggerAdapter:
|
|
139
|
+
"""
|
|
140
|
+
获取服务专用的日志器(带服务名称和版本信息)
|
|
141
|
+
|
|
142
|
+
兼容 Ray 分布式计算框架:日志输出到 stdout/stderr,Ray 会自动收集。
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
name: 服务名称
|
|
146
|
+
version: 服务版本(可选)
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
配置好的日志适配器
|
|
150
|
+
"""
|
|
151
|
+
logger = logging.getLogger(f"service.{name}")
|
|
152
|
+
|
|
153
|
+
# 如果还没有处理器,添加一个默认的
|
|
154
|
+
if not logger.handlers:
|
|
155
|
+
# 检测是否在 Ray 环境中运行
|
|
156
|
+
is_ray_env = False
|
|
157
|
+
try:
|
|
158
|
+
import ray
|
|
159
|
+
is_ray_env = ray.is_initialized()
|
|
160
|
+
except ImportError:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
# stdout 处理器(Ray 会自动捕获)
|
|
164
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
165
|
+
# 改进日志格式,包含服务名称和版本
|
|
166
|
+
version_str = f" v{version}" if version else ""
|
|
167
|
+
formatter = logging.Formatter(
|
|
168
|
+
f'%(asctime)s - [{name}{version_str}] - %(levelname)s - %(message)s',
|
|
169
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
170
|
+
)
|
|
171
|
+
console_handler.setFormatter(formatter)
|
|
172
|
+
logger.addHandler(console_handler)
|
|
173
|
+
|
|
174
|
+
# stderr 处理器用于错误日志(Ray 也会捕获)
|
|
175
|
+
error_handler = logging.StreamHandler(sys.stderr)
|
|
176
|
+
error_handler.setFormatter(formatter)
|
|
177
|
+
error_handler.setLevel(logging.ERROR)
|
|
178
|
+
logger.addHandler(error_handler)
|
|
179
|
+
|
|
180
|
+
logger.setLevel(logging.INFO)
|
|
181
|
+
|
|
182
|
+
# 在非 Ray 环境中,尝试添加文件处理器
|
|
183
|
+
if not is_ray_env:
|
|
184
|
+
try:
|
|
185
|
+
file_handler = logging.FileHandler(f"{name}.log", encoding='utf-8')
|
|
186
|
+
file_handler.setFormatter(formatter)
|
|
187
|
+
logger.addHandler(file_handler)
|
|
188
|
+
except (IOError, PermissionError):
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
# 返回适配器,自动添加服务信息
|
|
192
|
+
return ServiceLoggerAdapter(logger, name, version)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# 全局 logger 实例,方便用户直接导入使用
|
|
196
|
+
_global_logger: Optional[ServiceLoggerAdapter] = None
|
|
197
|
+
_global_service_name: Optional[str] = None
|
|
198
|
+
_global_version: Optional[str] = None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def set_service_context(service_name: str, version: Optional[str] = None):
|
|
202
|
+
"""
|
|
203
|
+
设置全局服务上下文(服务名称和版本)
|
|
204
|
+
|
|
205
|
+
当服务初始化时,会自动调用此函数设置上下文。
|
|
206
|
+
设置后,全局 logger 会自动包含服务信息。
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
service_name: 服务名称
|
|
210
|
+
version: 服务版本(可选)
|
|
211
|
+
"""
|
|
212
|
+
global _global_logger, _global_service_name, _global_version
|
|
213
|
+
_global_service_name = service_name
|
|
214
|
+
_global_version = version
|
|
215
|
+
# 重新创建 logger 以应用新的上下文
|
|
216
|
+
_global_logger = get_service_logger(service_name, version)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def get_logger() -> ServiceLoggerAdapter:
|
|
220
|
+
"""
|
|
221
|
+
获取全局 logger 实例
|
|
222
|
+
|
|
223
|
+
如果已经设置了服务上下文(通过 set_service_context),返回带服务信息的 logger。
|
|
224
|
+
否则返回一个默认的 logger。
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
日志适配器实例
|
|
228
|
+
"""
|
|
229
|
+
global _global_logger, _global_service_name, _global_version
|
|
230
|
+
|
|
231
|
+
if _global_logger is None:
|
|
232
|
+
# 如果还没有设置服务上下文,创建一个默认的 logger
|
|
233
|
+
if _global_service_name:
|
|
234
|
+
_global_logger = get_service_logger(_global_service_name, _global_version)
|
|
235
|
+
else:
|
|
236
|
+
# 使用默认名称创建 logger
|
|
237
|
+
_global_logger = get_service_logger("unknown", None)
|
|
238
|
+
|
|
239
|
+
return _global_logger
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# 创建一个延迟加载的 logger 包装类,确保在 Ray 环境中也能正常工作
|
|
243
|
+
class _LazyLogger:
|
|
244
|
+
"""
|
|
245
|
+
延迟加载的 logger 包装类,确保在 Ray 环境中也能正常工作
|
|
246
|
+
|
|
247
|
+
这个类通过 __getattr__ 代理所有方法调用到底层的 ServiceLoggerAdapter 实例,
|
|
248
|
+
避免了模块级别全局变量在 Ray 序列化时可能出现的问题。
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
def __getattr__(self, name):
|
|
252
|
+
"""
|
|
253
|
+
延迟获取 logger 实例的属性
|
|
254
|
+
|
|
255
|
+
每次访问属性时都会获取最新的 logger 实例,确保在 Ray 环境中
|
|
256
|
+
即使模块被重新导入,也能获取到正确的 logger。
|
|
257
|
+
"""
|
|
258
|
+
logger_instance = get_logger()
|
|
259
|
+
attr = getattr(logger_instance, name)
|
|
260
|
+
# 如果属性是可调用的,返回一个包装函数以确保每次调用都使用最新的 logger
|
|
261
|
+
if callable(attr):
|
|
262
|
+
def wrapper(*args, **kwargs):
|
|
263
|
+
current_logger = get_logger()
|
|
264
|
+
method = getattr(current_logger, name)
|
|
265
|
+
return method(*args, **kwargs)
|
|
266
|
+
return wrapper
|
|
267
|
+
return attr
|
|
268
|
+
|
|
269
|
+
def __call__(self, *args, **kwargs):
|
|
270
|
+
"""如果 logger 被当作函数调用"""
|
|
271
|
+
logger_instance = get_logger()
|
|
272
|
+
return logger_instance(*args, **kwargs)
|
|
273
|
+
|
|
274
|
+
def __repr__(self):
|
|
275
|
+
"""返回 logger 的字符串表示"""
|
|
276
|
+
logger_instance = get_logger()
|
|
277
|
+
return repr(logger_instance)
|
|
278
|
+
|
|
279
|
+
def __str__(self):
|
|
280
|
+
"""返回 logger 的字符串表示"""
|
|
281
|
+
logger_instance = get_logger()
|
|
282
|
+
return str(logger_instance)
|
|
283
|
+
|
|
284
|
+
def __dir__(self):
|
|
285
|
+
"""返回 logger 的所有属性,用于 IDE 自动补全"""
|
|
286
|
+
logger_instance = get_logger()
|
|
287
|
+
return dir(logger_instance)
|
|
288
|
+
|
|
289
|
+
# 创建延迟加载的 logger 实例
|
|
290
|
+
logger = _LazyLogger()
|