logger_config.py

下面我会按模块、按函数、按关键设计点对这段代码做一个深入、工程化角度的解析,不仅解释“做了什么”,还解释“为什么要这么做”。


1. 整体功能概览

这段代码的核心目的是:

在多进程场景下,安全地把父进程和子进程的日志写入同一个文件,并且只由主进程负责日志轮转(Log Rotate)

主要特点:

  • 日志写入到固定目录的 bjdt_ai.log
  • 日志文件 自动轮转(20MB 一个,最多 10 个)
  • 主进程:使用 RotatingFileHandler,负责轮转
  • 子进程:使用普通 FileHandler,只写日志,不参与轮转
  • 日志中包含 进程号,方便区分多进程输出
  • 统一设置 上海时区

2. 逐行解析


2.1 import 部分

import logging
from logging.handlers import RotatingFileHandler
import os
from config import settings
import time

2.1.1 各模块作用

模块 作用
logging Python 标准日志库
RotatingFileHandler 支持日志文件自动轮转
os 处理路径、环境变量
settings 项目配置(日志目录等)
time 控制时区

2.2 设置时区

os.environ['TZ'] = 'Asia/Shanghai'  # 时区
time.tzset()

2.2.1 这是在干什么?

  • 显式指定 系统级时区
  • 避免:
    • Docker / 服务器默认 UTC
    • 多台机器日志时间不一致

⚠️ 注意点

  • time.tzset()
    • ✔ Linux / macOS 有效
    • ❌ Windows 上会抛异常(通常在服务端问题不大)

2.2.2 效果

所有日志时间戳:

2025-12-21 21:38:10

都是 北京时间


2.3 日志文件路径

LOG_FILE = os.path.join(settings.LOG_SAVE_DIR, "bjdt_ai.log")

2.3.1 含义

  • 日志统一写到:
settings.LOG_SAVE_DIR/bjdt_ai.log

这样做的好处:

  • 路径集中管理
  • 不写死路径,方便环境切换(dev / prod)

2.4 主进程 Logger:main_logger()

def main_logger():
    logger = logging.getLogger('main')
    logger.setLevel(logging.INFO)

2.4.1 getLogger('main')

  • logger 是 单例
  • 只要名字相同,拿到的是同一个对象
  • "main" 表示主进程专用 logger

2.4.2 防止重复添加 handler

if not logger.handlers:

2.4.2.1 为什么必须有这句?

在以下场景会出问题:

  • 模块被多次 import
  • gunicorn / uvicorn reload
  • 多次调用 main_logger()

如果不判断:

同一条日志 → 被写 N 次

所以这是一个 非常重要的防重复机制


2.4.3 日志轮转(核心设计)

file_handler = RotatingFileHandler(
    LOG_FILE,
    maxBytes=20*1024*1024,  # 20MB
    backupCount=10,         # 保留10个备份
    encoding='utf-8'
)

2.4.3.1 轮转策略解释

参数 说明
maxBytes=20MB 单个日志最大 20MB
backupCount=10 最多保留 10 个旧文件
encoding='utf-8' 防止中文乱码

轮转后文件结构:

bjdt_ai.log
bjdt_ai.log.1
bjdt_ai.log.2
...
bjdt_ai.log.10

2.4.4 日志格式

formatter = logging.Formatter(
    '[%(process)d] %(asctime)s - %(name)s - %(message)s',
    datefmt="%Y-%m-%d %H:%M:%S"
)

2.4.4.1 日志示例

[24567] 2025-12-21 21:40:12 - main - 服务启动成功
字段 含义
%(process)d 进程 ID(多进程关键)
%(asctime)s 时间
%(name)s logger 名
%(message)s 日志内容

2.4.5 返回 logger

return logger

主进程后续直接使用这个 logger 打日志。


2.5 子进程 Logger:child_logger()

def child_logger():
    logger = logging.getLogger("child")
    logger.setLevel(logging.INFO)

2.5.1 与主进程的关键区别

主进程 子进程
RotatingFileHandler FileHandler
负责轮转 不负责轮转
控制文件大小 只写

2.5.2 为什么子进程不能用 RotatingFileHandler

多进程下同时轮转会发生:

  • 文件竞争
  • 文件被删除/重命名时写失败
  • 日志丢失

所以设计思路是:

只有一个进程负责轮转,其余进程只写


2.5.3 子进程 handler

handler = logging.FileHandler(LOG_FILE)
  • 写同一个文件
  • 不进行轮转
  • 当主进程轮转时:
    • 已存在的子进程 → 继续写旧文件
    • 新启动的子进程 → 写新文件

这正是你注释里说的那句话 👇

子进程不需要管理轮转...


2.6 初始化 logger(模块加载即生效)

app_logger = main_logger()
process_logger = child_logger()

2.6.1 含义

  • 模块一被 import:
    • 主进程 logger 就准备好了
    • 子进程 logger 也准备好了

使用方式通常是:

app_logger.info("主进程启动")
process_logger.info("子进程开始处理任务")

3. 整体设计优点总结(工程级)

3.1 优点

  1. 支持多进程
  2. 避免日志重复
  3. 避免多进程轮转冲突
  4. 日志可追踪(进程号)
  5. 统一时区
  6. 配置集中
posted @ 2026-01-05 08:53  做梦当财神  阅读(5)  评论(0)    收藏  举报