|
@@ -1,7 +1,5 @@
|
|
|
import logging
|
|
import logging
|
|
|
import sys
|
|
import sys
|
|
|
-import json
|
|
|
|
|
-from datetime import datetime, timezone
|
|
|
|
|
from typing import Optional
|
|
from typing import Optional
|
|
|
|
|
|
|
|
from app.core.config import settings
|
|
from app.core.config import settings
|
|
@@ -11,30 +9,6 @@ try:
|
|
|
except Exception: # pragma: no cover - 运行时兜底
|
|
except Exception: # pragma: no cover - 运行时兜底
|
|
|
fluent_handler = None
|
|
fluent_handler = None
|
|
|
|
|
|
|
|
-class JsonLogFormatter(logging.Formatter):
|
|
|
|
|
- """
|
|
|
|
|
- 将日志格式化为 JSON(每行一条),便于 Fluentd/ELK 检索。
|
|
|
|
|
- """
|
|
|
|
|
-
|
|
|
|
|
- def format(self, record: logging.LogRecord) -> str:
|
|
|
|
|
- log_obj = {
|
|
|
|
|
- "timestamp": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
|
|
|
|
|
- "level": record.levelname,
|
|
|
|
|
- "logger": record.name,
|
|
|
|
|
- "message": record.getMessage(),
|
|
|
|
|
- "module": record.module,
|
|
|
|
|
- "funcName": record.funcName,
|
|
|
|
|
- "lineNo": record.lineno,
|
|
|
|
|
- "app": settings.APP_NAME,
|
|
|
|
|
- "env": settings.APP_ENV
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if record.exc_info:
|
|
|
|
|
- log_obj["exception"] = self.formatException(record.exc_info)
|
|
|
|
|
-
|
|
|
|
|
- return json.dumps(log_obj, ensure_ascii=False)
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
def _build_fluent_handler() -> Optional[logging.Handler]:
|
|
def _build_fluent_handler() -> Optional[logging.Handler]:
|
|
|
"""
|
|
"""
|
|
|
创建 Fluentd Handler。
|
|
创建 Fluentd Handler。
|
|
@@ -48,28 +22,24 @@ def _build_fluent_handler() -> Optional[logging.Handler]:
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
fh = fluent_handler.FluentHandler(
|
|
fh = fluent_handler.FluentHandler(
|
|
|
- tag=settings.FLUENTD_TAG,
|
|
|
|
|
|
|
+ tag=settings.FLUENTD_EFFECTIVE_TAG,
|
|
|
host=settings.FLUENTD_HOST,
|
|
host=settings.FLUENTD_HOST,
|
|
|
port=settings.FLUENTD_PORT,
|
|
port=settings.FLUENTD_PORT,
|
|
|
timeout=settings.FLUENTD_TIMEOUT_SEC
|
|
timeout=settings.FLUENTD_TIMEOUT_SEC
|
|
|
)
|
|
)
|
|
|
- # 给 Fluentd 输出结构化字段(dict),避免 record 退化为 string。
|
|
|
|
|
- # 这里不能使用普通 logging.Formatter,否则会变成纯文本。
|
|
|
|
|
|
|
+ # 输出结构化字段,避免 record 退化为 string。
|
|
|
if hasattr(fluent_handler, "FluentRecordFormatter"):
|
|
if hasattr(fluent_handler, "FluentRecordFormatter"):
|
|
|
fh.setFormatter(fluent_handler.FluentRecordFormatter({
|
|
fh.setFormatter(fluent_handler.FluentRecordFormatter({
|
|
|
- "timestamp": "%(asctime)s",
|
|
|
|
|
"level": "%(levelname)s",
|
|
"level": "%(levelname)s",
|
|
|
"logger": "%(name)s",
|
|
"logger": "%(name)s",
|
|
|
"message": "%(message)s",
|
|
"message": "%(message)s",
|
|
|
- "module": "%(module)s",
|
|
|
|
|
- "funcName": "%(funcName)s",
|
|
|
|
|
- "lineNo": "%(lineno)d",
|
|
|
|
|
|
|
+ "time": "%(asctime)s",
|
|
|
"app": settings.APP_NAME,
|
|
"app": settings.APP_NAME,
|
|
|
- "env": settings.APP_ENV
|
|
|
|
|
|
|
+ "env": settings.APP_ENV,
|
|
|
}))
|
|
}))
|
|
|
return fh
|
|
return fh
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logging.getLogger(__name__).error(f"初始化 Fluentd Handler 失败: {e}")
|
|
|
|
|
|
|
+ logging.getLogger(__name__).exception("初始化 Fluentd Handler 失败: %s", str(e))
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@@ -78,42 +48,38 @@ def setup_logging():
|
|
|
配置日志系统,使其同时输出到文件和控制台。
|
|
配置日志系统,使其同时输出到文件和控制台。
|
|
|
这个函数应该在应用程序启动时只调用一次。
|
|
这个函数应该在应用程序启动时只调用一次。
|
|
|
"""
|
|
"""
|
|
|
- # 获取根日志记录器
|
|
|
|
|
root_logger = logging.getLogger()
|
|
root_logger = logging.getLogger()
|
|
|
- root_logger.setLevel(logging.INFO) # 设置根日志记录器的级别
|
|
|
|
|
|
|
|
|
|
- # 如果已经有处理器,先清空,防止重复添加
|
|
|
|
|
- if root_logger.hasHandlers():
|
|
|
|
|
- root_logger.handlers.clear()
|
|
|
|
|
|
|
+ # 避免重复初始化
|
|
|
|
|
+ if root_logger.handlers:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ root_logger.setLevel(getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO))
|
|
|
|
|
|
|
|
- # 创建 JSON 格式化器(用于控制台与本地文件)
|
|
|
|
|
- formatter = JsonLogFormatter()
|
|
|
|
|
|
|
+ stream_formatter = logging.Formatter("%(asctime)s %(levelname)s [%(name)s] %(message)s")
|
|
|
|
|
+ file_formatter = logging.Formatter("%(asctime)s %(levelname)s [%(name)s] %(message)s")
|
|
|
|
|
|
|
|
- # 1. 创建控制台处理器 (StreamHandler)
|
|
|
|
|
- # - 将日志输出到标准输出(您的终端)
|
|
|
|
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
|
- console_handler.setFormatter(formatter)
|
|
|
|
|
|
|
+ console_handler.setFormatter(stream_formatter)
|
|
|
root_logger.addHandler(console_handler)
|
|
root_logger.addHandler(console_handler)
|
|
|
|
|
|
|
|
- # 2. 创建文件处理器 (FileHandler)
|
|
|
|
|
- # - 将日志写入文件 app.log
|
|
|
|
|
- # - mode='w' 表示每次启动都覆盖旧日志
|
|
|
|
|
- # - encoding='utf-8' 确保正确处理中文字符
|
|
|
|
|
file_handler = logging.FileHandler('app.log', mode='w', encoding='utf-8')
|
|
file_handler = logging.FileHandler('app.log', mode='w', encoding='utf-8')
|
|
|
- file_handler.setFormatter(formatter)
|
|
|
|
|
|
|
+ file_handler.setFormatter(file_formatter)
|
|
|
root_logger.addHandler(file_handler)
|
|
root_logger.addHandler(file_handler)
|
|
|
|
|
|
|
|
- # 3. 可选:创建 Fluentd 处理器 (fluent-logger)
|
|
|
|
|
|
|
+ if not settings.FLUENTD_ENABLED:
|
|
|
|
|
+ root_logger.info("日志系统已成功配置,将输出到控制台和 app.log 文件。")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
fluentd_handler = _build_fluent_handler()
|
|
fluentd_handler = _build_fluent_handler()
|
|
|
if fluentd_handler:
|
|
if fluentd_handler:
|
|
|
root_logger.addHandler(fluentd_handler)
|
|
root_logger.addHandler(fluentd_handler)
|
|
|
- logging.info(
|
|
|
|
|
|
|
+ root_logger.info(
|
|
|
"Fluentd日志输出已启用: host=%s port=%s tag=%s",
|
|
"Fluentd日志输出已启用: host=%s port=%s tag=%s",
|
|
|
- settings.FLUENTD_HOST, settings.FLUENTD_PORT, settings.FLUENTD_TAG
|
|
|
|
|
|
|
+ settings.FLUENTD_HOST, settings.FLUENTD_PORT, settings.FLUENTD_EFFECTIVE_TAG
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- # 配置完成后,可以记录一条消息来确认
|
|
|
|
|
- logging.info("日志系统已成功配置,将同时输出到控制台和 app.log 文件。")
|
|
|
|
|
|
|
+ root_logger.info("日志系统已成功配置,将输出到控制台和 app.log 文件。")
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_logger(name: str) -> logging.Logger:
|
|
def get_logger(name: str) -> logging.Logger:
|