已合并
修改注释 #389
zhaoxi_00622383创建于 7月22日
修改注释 #389
已合并
共 7 个文件变更+71-23
| @@ -58,18 +58,18 @@ LOG_DIR=logs | |||
| 58 | LOG_ROTATION_SIZE=20 MB | 58 | LOG_ROTATION_SIZE=20 MB |
| 59 | # 日志保留天数(0=不按天数清理,默认7天) | 59 | # 日志保留天数(0=不按天数清理,默认7天) |
| 60 | LOG_RETENTION_DAYS=7 | 60 | LOG_RETENTION_DAYS=7 |
| 61 | -# 日志总空间上限(字节,0=不按空间清理,默认500MB) | 61 | +# 日志总空间上限(支持 "500 MB" 格式,0=不按空间清理,默认500MB) |
| 62 | -LOG_MAX_TOTAL_SIZE=524288000 | 62 | +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) |
| 66 | JIUWEN_LOG_BACKUP_COUNT=20 | 66 | JIUWEN_LOG_BACKUP_COUNT=20 |
| 67 | -# SDK 日志单文件大小阈值(字节,达到即触发轮转,默认20MB) | 67 | +# SDK 日志单文件大小阈值(MB,达到即触发轮转,默认20MB) |
| 68 | -JIUWEN_LOG_MAX_BYTES=20971520 | 68 | +JIUWEN_LOG_MAX_BYTES=20 MB |
| 69 | # SDK 日志归档保留天数(0=不按天数清理,默认7天) | 69 | # SDK 日志归档保留天数(0=不按天数清理,默认7天) |
| 70 | JIUWEN_LOG_RETENTION_DAYS=7 | 70 | JIUWEN_LOG_RETENTION_DAYS=7 |
| 71 | -# SDK 日志总空间上限(字节,0=不按空间清理,默认500MB) | 71 | +# SDK 日志总空间上限(MB,0=不按空间清理,默认500MB) |
| 72 | -JIUWEN_LOG_MAX_TOTAL_SIZE=524288000 | 72 | +JIUWEN_LOG_MAX_TOTAL_SIZE=500 MB |
| 73 | 73 | ||
| 74 | # ── LLM(上游保留字段,供其他 Agent 参考;EDPAgent 不读此组)────────────── | 74 | # ── LLM(上游保留字段,供其他 Agent 参考;EDPAgent 不读此组)────────────── |
| 75 | LLM_PROVIDER=llm_OpenAI | 75 | LLM_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 | return | 150 | return |
| @@ -530,8 +531,9 @@ async def lifespan(fastapi_app: FastAPI): | |||
| 530 | from openjiuwen.core.common.logging.log_config import configure_log_config | 531 | from openjiuwen.core.common.logging.log_config import configure_log_config |
| 531 | from openjiuwen.core.common.logging.default.constant import DEFAULT_INNER_LOG_CONFIG | 532 | 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 压缩) |
| 27 | from openjiuwen_runtime.foundation.log.handler import CompressedRotatingFileHandler | 27 | from openjiuwen_runtime.foundation.log.handler import CompressedRotatingFileHandler |
| 28 | 28 | ||
| 29 | +from config import get_settings | ||
| 30 | + | ||
| 29 | # 从 SDK 导入 DefaultLogger 和相关类(不修改 SDK,只继承) | 31 | # 从 SDK 导入 DefaultLogger 和相关类(不修改 SDK,只继承) |
| 30 | from openjiuwen.core.common.logging.default.default_impl import ( | 32 | from 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 dict | 212 | # 第 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 | |||
| 8 | from typing import Optional | 8 | from typing import Optional |
| 9 | from urllib.parse import quote_plus | 9 | from urllib.parse import quote_plus |
| 10 | 10 | ||
| 11 | +from pydantic import field_validator | ||
| 11 | from pydantic_settings import BaseSettings, SettingsConfigDict | 12 | from pydantic_settings import BaseSettings, SettingsConfigDict |
| 12 | 13 | ||
| 13 | from common.crypto import decrypt_config_value | 14 | from 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 | ||
| 17 | class Settings(BaseSettings): | 32 | class 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 = 7 | 109 | log_retention_days: int = 7 |
| 95 | - # 日志总空间上限(字节,0=不按空间清理,默认500MB) | 110 | + # 日志总空间上限(MB,0=不按空间清理,默认500MB) |
| 96 | log_max_total_size: int = 524288000 | 111 | 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 = 20971520 | 115 | jiuwen_log_max_bytes: int = 20971520 |
| 101 | # SDK 日志归档文件保留数量(轮转后最多保留 N 个 .gz,默认20) | 116 | # SDK 日志归档文件保留数量(轮转后最多保留 N 个 .gz,默认20) |
| 102 | jiuwen_log_backup_count: int = 20 | 117 | jiuwen_log_backup_count: int = 20 |
| 103 | # SDK 日志归档保留天数(0=不按天数清理,默认7天) | 118 | # SDK 日志归档保留天数(0=不按天数清理,默认7天) |
| 104 | jiuwen_log_retention_days: int = 7 | 119 | jiuwen_log_retention_days: int = 7 |
| 105 | - # SDK 日志总空间上限(字节,0=不按空间清理,默认500MB) | 120 | + # SDK 日志总空间上限(MB,0=不按空间清理,默认500MB) |
| 106 | jiuwen_log_max_total_size: int = 524288000 | 121 | 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 | @property | 128 | @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 | |||
| 48 | ADAPTER_LOG_ROTATION_SIZE=100 MB | 48 | ADAPTER_LOG_ROTATION_SIZE=100 MB |
| 49 | # 日志保留天数(0=不按天数清理,默认7天) | 49 | # 日志保留天数(0=不按天数清理,默认7天) |
| 50 | ADAPTER_LOG_RETENTION_DAYS=7 | 50 | ADAPTER_LOG_RETENTION_DAYS=7 |
| 51 | -# 日志总空间上限(字节,0=不按空间清理,默认500MB) | 51 | +# 日志总空间上限(MB,0=不按空间清理,默认500MB) |
| 52 | -ADAPTER_LOG_MAX_TOTAL_SIZE=524288000 | 52 | +ADAPTER_LOG_MAX_TOTAL_SIZE=500 MB |
| 53 | 53 | ||
| 54 | # ── Redis(会话状态)───────────────────────────────────────────────────────── | 54 | # ── Redis(会话状态)───────────────────────────────────────────────────────── |
| 55 | REDIS_HOST=localhost | 55 | REDIS_HOST=localhost |
| @@ -7,10 +7,24 @@ from functools import lru_cache | |||
| 7 | from pathlib import Path | 7 | from pathlib import Path |
| 8 | from typing import Any, Dict, Optional | 8 | from typing import Any, Dict, Optional |
| 9 | 9 | ||
| 10 | -from pydantic import Field, Json | 10 | +from pydantic import Field, Json, field_validator |
| 11 | from pydantic_settings import BaseSettings, SettingsConfigDict | 11 | from 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 = 7 | 82 | adapter_log_retention_days: int = 7 |
| 69 | - # 日志总空间上限(字节,0=不按空间清理,默认500MB) | 83 | + # 日志总空间上限(MB,0=不按空间清理,默认500MB) |
| 70 | adapter_log_max_total_size: int = 524288000 | 84 | 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] = None | 92 | redis_host: Optional[str] = None |
| 74 | redis_port: Optional[int] = None | 93 | redis_port: Optional[int] = None |