3dca126411
微信优惠券增加美团/京东中间页,领券前获取微信头像授权(主功能) --------- Co-authored-by: guke <guke@autohome.com.cn> Reviewed-on: #75 Co-authored-by: guke <guke@wonderable.ai> Co-committed-by: guke <guke@wonderable.ai>
96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
"""统一日志配置。
|
|
|
|
业务代码用 `logger = logging.getLogger("shagua.xxx")` 即可, 本模块在 main.py 启动时调一次。
|
|
|
|
- 控制台(stdout): 人类可读文本, 给 systemd / 本地看。
|
|
- 文件 `logs/app-server.log`: 单行 JSON, 供阿里云 SLS/Logtail 采集(JSON 模式零正则);
|
|
异常栈作为字段内嵌不换行 → 每条日志一行。
|
|
- 环境变量:
|
|
- LOG_JSON_CONSOLE=1 控制台也输出 JSON
|
|
- LOG_DIR / LOG_FILE 改落盘路径(默认 logs/app-server.log)
|
|
⚠️ admin 子进程(app.admin.main, 独立进程)应设不同 LOG_FILE, 避免与主进程争抢同一轮转文件
|
|
- LOG_SERVICE_NAME JSON 里的 service 字段(默认 app-server)
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
from datetime import datetime
|
|
from logging.handlers import RotatingFileHandler
|
|
from pathlib import Path
|
|
|
|
|
|
class JsonFormatter(logging.Formatter):
|
|
"""把 LogRecord 序列化成单行 JSON(SLS/Logtail 友好)。异常栈内嵌为字段, 整条仍是一行。"""
|
|
def __init__(self, service: str = "app-server"):
|
|
super().__init__()
|
|
self.service = service
|
|
|
|
def format(self, record: logging.LogRecord) -> str:
|
|
dt = datetime.fromtimestamp(record.created)
|
|
data = {
|
|
"time": dt.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(record.msecs):03d}",
|
|
"level": record.levelname,
|
|
"service": self.service,
|
|
"logger": record.name,
|
|
"func": record.funcName,
|
|
"line": record.lineno,
|
|
"message": record.getMessage(),
|
|
}
|
|
if record.exc_info:
|
|
data["exception"] = self.formatException(record.exc_info)
|
|
if record.stack_info:
|
|
data["stack"] = self.formatStack(record.stack_info)
|
|
return json.dumps(data, ensure_ascii=False, default=str)
|
|
|
|
|
|
_CONFIGURED = False
|
|
|
|
|
|
def setup_logging(debug: bool = False) -> None:
|
|
global _CONFIGURED
|
|
if _CONFIGURED:
|
|
return
|
|
|
|
level = logging.DEBUG if debug else logging.INFO
|
|
service = os.getenv("LOG_SERVICE_NAME", "app-server")
|
|
|
|
root = logging.getLogger()
|
|
root.setLevel(level)
|
|
# 清掉 basicConfig/uvicorn 可能预置的 root handler, 避免重复输出
|
|
for h in list(root.handlers):
|
|
root.removeHandler(h)
|
|
|
|
# 控制台: 默认文本(systemd/本地看); LOG_JSON_CONSOLE=1 时输出 JSON
|
|
console = logging.StreamHandler(sys.stdout)
|
|
if os.getenv("LOG_JSON_CONSOLE") == "1":
|
|
console.setFormatter(JsonFormatter(service))
|
|
else:
|
|
console.setFormatter(
|
|
logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
)
|
|
root.addHandler(console)
|
|
|
|
# 文件: 单行 JSON, 供 Logtail 采集(自动轮转, 单文件 10MB, 保留 5 个)
|
|
log_file = os.getenv("LOG_FILE") or str(
|
|
Path(os.getenv("LOG_DIR", "logs")) / "app-server.log"
|
|
)
|
|
Path(log_file).parent.mkdir(parents=True, exist_ok=True)
|
|
file_handler = RotatingFileHandler(
|
|
log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8",
|
|
)
|
|
file_handler.setFormatter(JsonFormatter(service))
|
|
root.addHandler(file_handler)
|
|
|
|
# 第三方库降噪
|
|
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
# uvicorn --reload 的文件监视器: DEBUG 下会把每次"检测到变更"打出来。我们的 root 文件
|
|
# handler 又把这行写回 logs/app-server.log → watchfiles 再次检测 → 自我喂食死循环。
|
|
# 降到 WARNING 同时消除噪音和这个回环(真正的 .py 热重载不受影响)。
|
|
logging.getLogger("watchfiles").setLevel(logging.WARNING)
|
|
|
|
_CONFIGURED = True
|