已合并
修改注释 #389
修改注释 #389
已合并
zhaoxi_00622383创建于 7月22日
共 7 个文件变更+71-23
@@ -58,18 +58,18 @@ LOG_DIR=logs
58LOG_ROTATION_SIZE=20 MB58LOG_ROTATION_SIZE=20 MB
59# 日志保留天数(0=不按天数清理,默认7天)59# 日志保留天数(0=不按天数清理,默认7天)
60LOG_RETENTION_DAYS=760LOG_RETENTION_DAYS=7
61-# 日志总空间上限(字节,0=不按空间清理,默认500MB)61+# 日志总空间上限(支持 "500 MB" 格式,0=不按空间清理,默认500MB)
62-LOG_MAX_TOTAL_SIZE=52428800062+LOG_MAX_TOTAL_SIZE=500 MB
63 63 
64# ── SDK 日志(openjiuwen SDK 的日志清理配置)────────────────────────────64# ── SDK 日志(openjiuwen SDK 的日志清理配置)────────────────────────────
65# SDK 日志归档文件保留数量(轮转后最多保留 N 个 .gz,默认20)65# SDK 日志归档文件保留数量(轮转后最多保留 N 个 .gz,默认20)
66JIUWEN_LOG_BACKUP_COUNT=2066JIUWEN_LOG_BACKUP_COUNT=20
67-# SDK 日志单文件大小阈值(字节,达到即触发轮转,默认20MB)67+# SDK 日志单文件大小阈值(MB,达到即触发轮转,默认20MB)
68-JIUWEN_LOG_MAX_BYTES=2097152068+JIUWEN_LOG_MAX_BYTES=20 MB
69# SDK 日志归档保留天数(0=不按天数清理,默认7天)69# SDK 日志归档保留天数(0=不按天数清理,默认7天)
70JIUWEN_LOG_RETENTION_DAYS=770JIUWEN_LOG_RETENTION_DAYS=7
71-# SDK 日志总空间上限(字节,0=不按空间清理,默认500MB)71+# SDK 日志总空间上限(MB,0=不按空间清理,默认500MB)
72-JIUWEN_LOG_MAX_TOTAL_SIZE=52428800072+JIUWEN_LOG_MAX_TOTAL_SIZE=500 MB
73 73 
74# ── LLM(上游保留字段,供其他 Agent 参考;EDPAgent 不读此组)──────────────74# ── LLM(上游保留字段,供其他 Agent 参考;EDPAgent 不读此组)──────────────
75LLM_PROVIDER=llm_OpenAI75LLM_PROVIDER=llm_OpenAI
@@ -142,8 +142,9 @@ def _cleanup_logs(files):
142 142 
143 注意:活跃文件(无 .gz 后缀的 .log 文件)不参与删除,只删 .gz 归档文件。143 注意:活跃文件(无 .gz 后缀的 .log 文件)不参与删除,只删 .gz 归档文件。
144 """144 """
145- retention_days = int(os.getenv("LOG_RETENTION_DAYS", "7"))145+ _settings = get_settings()
146- max_total_size = int(os.getenv("LOG_MAX_TOTAL_SIZE", "524288000"))146+ retention_days = _settings.log_retention_days
147+ max_total_size = _settings.log_max_total_size
147 148 
148 if not files:149 if not files:
149 return150 return
@@ -530,8 +531,9 @@ async def lifespan(fastapi_app: FastAPI):
530 from openjiuwen.core.common.logging.log_config import configure_log_config531 from openjiuwen.core.common.logging.log_config import configure_log_config
531 from openjiuwen.core.common.logging.default.constant import DEFAULT_INNER_LOG_CONFIG532 from openjiuwen.core.common.logging.default.constant import DEFAULT_INNER_LOG_CONFIG
532 custom_log_config = DEFAULT_INNER_LOG_CONFIG.copy()533 custom_log_config = DEFAULT_INNER_LOG_CONFIG.copy()
533- custom_log_config["backup_count"] = int(os.getenv("JIUWEN_LOG_BACKUP_COUNT", 20))534+ # 通过 settings 读取,复用 pydantic 校验和默认值,避免 os.getenv 遇到空字符串抛 ValueError
534- custom_log_config["max_bytes"] = int(os.getenv("JIUWEN_LOG_MAX_BYTES", 20971520))535+ custom_log_config["backup_count"] = settings.jiuwen_log_backup_count
536+ custom_log_config["max_bytes"] = settings.jiuwen_log_max_bytes
535 configure_log_config(custom_log_config)537 configure_log_config(custom_log_config)
536 538 
537 await initialize()539 await initialize()
@@ -26,6 +26,8 @@ from datetime import datetime, timedelta, timezone
26# 从 foundation 导入 Handler(支持 gzip 压缩)26# 从 foundation 导入 Handler(支持 gzip 压缩)
27from openjiuwen_runtime.foundation.log.handler import CompressedRotatingFileHandler27from openjiuwen_runtime.foundation.log.handler import CompressedRotatingFileHandler
28 28 
29+from config import get_settings
30+ 
29# 从 SDK 导入 DefaultLogger 和相关类(不修改 SDK,只继承)31# 从 SDK 导入 DefaultLogger 和相关类(不修改 SDK,只继承)
30from openjiuwen.core.common.logging.default.default_impl import (32from openjiuwen.core.common.logging.default.default_impl import (
31 DefaultLogger,33 DefaultLogger,
@@ -157,10 +159,11 @@ class CleanableDefaultLogger(DefaultLogger):
157 handler.close()159 handler.close()
158 160 
159 # 用 foundation 的 Handler 替换(获得压缩 + 清理能力)161 # 用 foundation 的 Handler 替换(获得压缩 + 清理能力)
160- # backupCount/maxBytes 优先从环境变量读取(与 app.py 中的 configure_log_config 保持一致),162+ # backupCount/maxBytes 从 settings 读取(与 app.py 中的 configure_log_config 保持一致),
161 # 避免旧 handler(SDK 默认值 20)覆盖用户配置163 # 避免旧 handler(SDK 默认值 20)覆盖用户配置
162- env_backup_count = int(os.getenv("JIUWEN_LOG_BACKUP_COUNT", handler.backupCount))164+ _s = get_settings()
163- env_max_bytes = int(os.getenv("JIUWEN_LOG_MAX_BYTES", handler.maxBytes))165+ env_backup_count = _s.jiuwen_log_backup_count
166+ env_max_bytes = _s.jiuwen_log_max_bytes
164 cleanable_handler = CleanableCompressedRotatingFileHandler(167 cleanable_handler = CleanableCompressedRotatingFileHandler(
165 filename=handler.baseFilename,168 filename=handler.baseFilename,
166 max_bytes=env_max_bytes,169 max_bytes=env_max_bytes,
@@ -200,10 +203,11 @@ def setup_sdk_log_cleaner() -> None:
200 背景:configure_log_config() 在 initialize() 之前执行,此时 SDK 用原生203 背景:configure_log_config() 在 initialize() 之前执行,此时 SDK 用原生
201 SafeRotatingFileHandler(不压缩)。如果此时活跃文件已超 maxBytes,轮转会204 SafeRotatingFileHandler(不压缩)。如果此时活跃文件已超 maxBytes,轮转会
202 生成 .1/.2/...(未压缩)。本函数第 3 步把这些残留文件压缩成 .gz,否则205 生成 .1/.2/...(未压缩)。本函数第 3 步把这些残留文件压缩成 .gz,否则
203- 它们不会被清理逻辑扫描到(清理只扫 .gz),会永久残留。206+ 他们不会被清理逻辑扫描到(清理只扫 .gz),会永久残留。
204 """207 """
205- retention_days = int(os.getenv("JIUWEN_LOG_RETENTION_DAYS", 7))208+ _settings = get_settings()
206- max_total_size = int(os.getenv("JIUWEN_LOG_MAX_TOTAL_SIZE", 524288000))209+ retention_days = _settings.jiuwen_log_retention_days
210+ max_total_size = _settings.jiuwen_log_max_total_size
207 211 
208 # 第 1 步:预触发所有懒加载 logger,确保它们被创建并加入 _loggers dict212 # 第 1 步:预触发所有懒加载 logger,确保它们被创建并加入 _loggers dict
209 for log_type in _ALL_SDK_LOG_TYPES:213 for log_type in _ALL_SDK_LOG_TYPES:
@@ -8,11 +8,26 @@ from pathlib import Path
8from typing import Optional8from typing import Optional
9from urllib.parse import quote_plus9from urllib.parse import quote_plus
10 10 
11+from pydantic import field_validator
11from pydantic_settings import BaseSettings, SettingsConfigDict12from pydantic_settings import BaseSettings, SettingsConfigDict
12 13 
13from common.crypto import decrypt_config_value14from common.crypto import decrypt_config_value
14 15 
15 16 
17+def _parse_size_to_bytes(value) -> int:
18+ """将 MB 字符串转为字节 int。
19+ 
20+ 只支持 MB 单位,如 '500 MB'、'20MB';纯数字按字节。
21+ """
22+ if isinstance(value, int):
23+ return value
24+ s = str(value).strip().upper()
25+ if s.endswith("MB"):
26+ num = s[:-2].strip()
27+ return int(float(num) * 1024 * 1024)
28+ return int(s)
29+ 
30+ 
16 31 
17class Settings(BaseSettings):32class Settings(BaseSettings):
18 model_config = SettingsConfigDict(33 model_config = SettingsConfigDict(
@@ -92,19 +107,24 @@ class Settings(BaseSettings):
92 log_rotation_size: str = "20 MB"107 log_rotation_size: str = "20 MB"
93 # 日志保留天数(0=不按天数清理)108 # 日志保留天数(0=不按天数清理)
94 log_retention_days: int = 7109 log_retention_days: int = 7
95- # 日志总空间上限(字节,0=不按空间清理,默认500MB)110+ # 日志总空间上限(MB,0=不按空间清理,默认500MB)
96 log_max_total_size: int = 524288000111 log_max_total_size: int = 524288000
97 112 
98 # ── SDK 日志(openjiuwen SDK 的日志清理配置)────────────────────────────113 # ── SDK 日志(openjiuwen SDK 的日志清理配置)────────────────────────────
99- # SDK 日志单文件大小阈值(字节,达到即触发轮转,默认20MB)114+ # SDK 日志单文件大小阈值(MB,达到即触发轮转,默认20MB)
100 jiuwen_log_max_bytes: int = 20971520115 jiuwen_log_max_bytes: int = 20971520
101 # SDK 日志归档文件保留数量(轮转后最多保留 N 个 .gz,默认20)116 # SDK 日志归档文件保留数量(轮转后最多保留 N 个 .gz,默认20)
102 jiuwen_log_backup_count: int = 20117 jiuwen_log_backup_count: int = 20
103 # SDK 日志归档保留天数(0=不按天数清理,默认7天)118 # SDK 日志归档保留天数(0=不按天数清理,默认7天)
104 jiuwen_log_retention_days: int = 7119 jiuwen_log_retention_days: int = 7
105- # SDK 日志总空间上限(字节,0=不按空间清理,默认500MB)120+ # SDK 日志总空间上限(MB,0=不按空间清理,默认500MB)
106 jiuwen_log_max_total_size: int = 524288000121 jiuwen_log_max_total_size: int = 524288000
107 122 
123+ @field_validator("log_max_total_size", "jiuwen_log_max_bytes", "jiuwen_log_max_total_size", mode="before")
124+ @classmethod
125+ def _parse_size_fields(cls, v):
126+ return _parse_size_to_bytes(v)
127+ 
108 @property128 @property
109 def redis_url(self) -> str:129 def redis_url(self) -> str:
110 if self.redis_password:130 if self.redis_password:
@@ -34,6 +34,9 @@ dependencies = [
34 34 
35 # SQLAlchemy(common.data_store_factory 构建 DB handler)35 # SQLAlchemy(common.data_store_factory 构建 DB handler)
36 "sqlalchemy[asyncio]>=2.0.0",36 "sqlalchemy[asyncio]>=2.0.0",
37+ 
38+ # openjiuwen runtime foundation(SDK 日志清理等基础能力)
39+ "openjiuwen-runtime-foundation>=0.1.0",
37]40]
38 41 
39[project.optional-dependencies]42[project.optional-dependencies]
@@ -48,8 +48,8 @@ ADAPTER_LOG_FILE=logs/versatile_adapter.log
48ADAPTER_LOG_ROTATION_SIZE=100 MB48ADAPTER_LOG_ROTATION_SIZE=100 MB
49# 日志保留天数(0=不按天数清理,默认7天)49# 日志保留天数(0=不按天数清理,默认7天)
50ADAPTER_LOG_RETENTION_DAYS=750ADAPTER_LOG_RETENTION_DAYS=7
51-# 日志总空间上限(字节,0=不按空间清理,默认500MB)51+# 日志总空间上限(MB,0=不按空间清理,默认500MB)
52-ADAPTER_LOG_MAX_TOTAL_SIZE=52428800052+ADAPTER_LOG_MAX_TOTAL_SIZE=500 MB
53 53 
54# ── Redis(会话状态)─────────────────────────────────────────────────────────54# ── Redis(会话状态)─────────────────────────────────────────────────────────
55REDIS_HOST=localhost55REDIS_HOST=localhost
@@ -7,10 +7,24 @@ from functools import lru_cache
7from pathlib import Path7from pathlib import Path
8from typing import Any, Dict, Optional8from typing import Any, Dict, Optional
9 9 
10-from pydantic import Field, Json10+from pydantic import Field, Json, field_validator
11from pydantic_settings import BaseSettings, SettingsConfigDict11from pydantic_settings import BaseSettings, SettingsConfigDict
12 12 
13 13 
14+def _parse_size_to_bytes(value) -> int:
15+ """将 MB 字符串转为字节 int。
16+ 
17+ 只支持 MB 单位,如 '500 MB'、'20MB';纯数字按字节。
18+ """
19+ if isinstance(value, int):
20+ return value
21+ s = str(value).strip().upper()
22+ if s.endswith("MB"):
23+ num = s[:-2].strip()
24+ return int(float(num) * 1024 * 1024)
25+ return int(s)
26+ 
27+ 
14# 调用 Versatile 时默认注入的请求头(JSON 字符串,会被 Json[...] 字段解析)。28# 调用 Versatile 时默认注入的请求头(JSON 字符串,会被 Json[...] 字段解析)。
15#29#
16# 默认值仅放跨环境通用的非鉴权头(Accept / stream),**不含 Cookie**:30# 默认值仅放跨环境通用的非鉴权头(Accept / stream),**不含 Cookie**:
@@ -66,9 +80,14 @@ class Settings(BaseSettings):
66 adapter_log_rotation_size: str = "100 MB"80 adapter_log_rotation_size: str = "100 MB"
67 # 日志保留天数(0=不按天数清理)81 # 日志保留天数(0=不按天数清理)
68 adapter_log_retention_days: int = 782 adapter_log_retention_days: int = 7
69- # 日志总空间上限(字节,0=不按空间清理,默认500MB)83+ # 日志总空间上限(MB,0=不按空间清理,默认500MB)
70 adapter_log_max_total_size: int = 52428800084 adapter_log_max_total_size: int = 524288000
71 85 
86+ @field_validator("adapter_log_max_total_size", mode="before")
87+ @classmethod
88+ def _parse_size_fields(cls, v):
89+ return _parse_size_to_bytes(v)
90+ 
72 # ── Redis ──────────────────────────────────────────────────────────────91 # ── Redis ──────────────────────────────────────────────────────────────
73 redis_host: Optional[str] = None92 redis_host: Optional[str] = None
74 redis_port: Optional[int] = None93 redis_port: Optional[int] = None