python日志loguru封装

6 阅读4分钟
import sys
import os
import datetime
import loguru
from pathlib import Path
import configparser
from functools import wraps

# 单例模式装饰器
def singleton_class_decorator(cls):
    _instances = {}

    @wraps(cls)
    def wrapper_class(*args, **kwargs):
        if cls not in _instances:
            _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]

    return wrapper_class


@singleton_class_decorator
class Logger:
    def __init__(self, config_file="log.ini"):
        self.config_file = config_file
        self.logger_add()

    def read_ini(self):
        config = configparser.ConfigParser(inline_comment_prefixes=("#", ";"))
        config.read(self.config_file, encoding="UTF-8")
        return config

    def get_project_path(self, project_path=None):
        if project_path is None:
            project_path = Path(__file__).parent.parent
        return project_path

    # def get_log_path(self):
    #     project_path = self.get_project_path()
    #     project_log_dir = Path(project_path, 'Logs')  # Logs 文件夹路径
    #     project_log_filename = f'{datetime.date.today()}.log'  # 以日期命名日志文件
    #     project_log_path = Path(project_log_dir, project_log_filename)  # 拼接最终日志文件路径
    #     return project_log_path

    def get_log_path(self, level=None):
        project_path = self.get_project_path()
        # 按照日志级别创建不同的目录
        log_dir = (
            Path(project_path, "Logs", level) if level else Path(project_path, "Logs")
        )
        log_dir.mkdir(parents=True, exist_ok=True)  # 确保日志目录存在
        log_filename = f"{datetime.date.today()}.log"  # 日志文件名按日期生成
        return Path(log_dir, log_filename)

    def ensure_log_dir_exists(self, log_path):
        if not os.path.exists(log_path.parent):
            os.makedirs(log_path.parent)

    def logger_add(self):
        # 清空已有的日志处理器
        loguru.logger.remove()

        # 读取配置文件
        config = self.read_ini()

        # 控制台日志配置
        if config.get("StderrLog", "is_open").lower() == "on":
            loguru.logger.add(
                sys.stderr,
                format="[<green>{time:YYYY-MM-DD HH:mm:ss}</green> {level:<8}| "
                "<cyan>{module}</cyan>.<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
                "<level>{message}</level>",
                level=config.get("StderrLog", "level"),
            )

        # 文件日志配置
        if config.get("FileLog", "is_open").lower() == "on":
            # levels = ["DEBUG", "INFO", "SUCCESS", "WARNING", "ERROR", "CRITICAL"]
            levels = ["DEBUG"]
            for level in levels:
                log_path = self.get_log_path(level.lower())
                # print('log_path ', log_path)
                # project_log_path = self.get_log_path()

                # 确保日志目录存在
                # self.ensure_log_dir_exists(project_log_path)
                # print('>>>>>> ', project_log_path)
                loguru.logger.add(
                    sink=log_path,
                    rotation=config.get("FileLog", "rotation"),
                    retention=config.get("FileLog", "retention"),
                    compression="zip",
                    encoding="utf-8",
                    enqueue=True,
                    format="{level:<8} | {time:YYYY-MM-DD HH:mm:ss} | {message}",  # 控制输出格式
                    level=level,
                    serialize=config.getboolean("FileLog", "serialize"),  # 启用 JSON 输出
                )

        # 邮件通知配置(可选)
        if config.get("EmailLog", "is_open").lower() == "on":
            # 假设配置有SMTP邮件通知
            from loguru import logger
            import smtplib
            from email.mime.text import MIMEText
            from email.mime.multipart import MIMEMultipart

            # 获取SMTP配置
            smtp_server = config.get("EmailLog", "smtp_server")
            smtp_port = config.get("EmailLog", "smtp_port")
            smtp_user = config.get("EmailLog", "smtp_user")
            smtp_password = config.get("EmailLog", "smtp_password")
            to_email = config.get("EmailLog", "to_email")

            # 邮件格式配置 没有调测
            def send_email(record):
                msg = MIMEMultipart()
                msg["From"] = smtp_user
                msg["To"] = to_email
                msg["Subject"] = f"Log - {record['level'].name}"

                body = MIMEText(record["message"], "plain", "utf-8")
                msg.attach(body)

                try:
                    with smtplib.SMTP(smtp_server, smtp_port) as server:
                        server.login(smtp_user, smtp_password)
                        server.sendmail(smtp_user, to_email, msg.as_string())
                except Exception as e:
                    logger.error(f"发送邮件失败: {e}")

            # 配置邮件处理器
            loguru.logger.add(send_email, level="ERROR", format="{message}")

    @property
    def get_logger(self):
        return loguru.logger


# 实例化日志类
logger = Logger().get_logger

if __name__ == "__main__":
    logger.debug("调试代码")
    logger.info("输出信息")
    logger.success("输出成功")
    logger.warning("警告信息")
    logger.error("错误信息")
    logger.critical("崩溃信息")
    #
    try:
        1 / 0  # 故意抛出异常
    except ZeroDivisionError as e:
        logger.exception("发生了除零异常")


项目根目录下的:log.ini文件

[StderrLog]
is_open = on
level = DEBUG

[FileLog]
is_open = on
level = INFO
rotation = 500 MB
retention = 10 days
serialize = False ; True False

[EmailLog]
is_open = off
smtp_server = smtp.example.com
smtp_port = 587
smtp_user = your_email@example.com
smtp_password = your_password
to_email = recipient@example.com

import sys
import os
import datetime
import loguru
from pathlib import Path
import configparser
from functools import wraps

# 单例模式装饰器
def singleton_class_decorator(cls):
    _instances = {}

    @wraps(cls)
    def wrapper_class(*args, **kwargs):
        if cls not in _instances:
            _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]

    return wrapper_class

@singleton_class_decorator
class Logger:
    def __init__(self, config_file="log.ini"):
        self.config_file = config_file
        self.logger_add()

    def read_ini(self):
        config = configparser.ConfigParser(inline_comment_prefixes=('#', ';'))
        config.read(self.config_file, encoding="UTF-8")
        return config

    def get_project_path(self, project_path=None):
        if project_path is None:
            project_path = Path(__file__).parent.parent
        return project_path

    def get_log_path(self, level=None):
        project_path = self.get_project_path()
        # 按照日志级别创建不同的目录
        log_dir = Path(project_path, 'Logs', level) if level else Path(project_path, 'Logs')
        log_dir.mkdir(parents=True, exist_ok=True)  # 确保日志目录存在
        log_filename = f'{datetime.date.today()}.log'  # 日志文件名按日期生成
        return Path(log_dir, log_filename)

    def ensure_log_dir_exists(self, log_path):
        if not os.path.exists(log_path.parent):
            os.makedirs(log_path.parent)

    def logger_add(self):
        # 清空已有的日志处理器
        loguru.logger.remove()

        # 读取配置文件
        config = self.read_ini()

        # 获取 serialize 配置
        serialize_logs = config.getboolean('FileLog', 'serialize')
        print('serialize ', serialize_logs)

        # 控制台日志配置
        if config.get('StderrLog', 'is_open').lower() == "on":
            loguru.logger.add(
                sys.stderr,
                format="[<green>{time:YYYY-MM-DD HH:mm:ss}</green> {level:<8}| "
                      "<cyan>{module}</cyan>.<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
                      "<level>{message}</level>",
                level=config.get('StderrLog', 'level'),
            )

        # 文件日志配置
        if config.get('FileLog', 'is_open').lower() == "on":
            project_log_path = self.get_log_path()

            # 确保日志目录存在
            self.ensure_log_dir_exists(project_log_path)

            # 根据配置文件中的 serialize 字段决定输出格式
            # if serialize_logs:
            #     loguru.logger.add(
            #         sink=project_log_path,
            #         rotation=config.get('FileLog', 'rotation'),
            #         retention=config.get('FileLog', 'retention'),
            #         compression='zip',
            #         encoding="utf-8",
            #         enqueue=True,
            #         level=config.get('FileLog', 'level'),
            #         serialize=True  # 启用 JSON 格式
            #     )
            # else:
            #     loguru.logger.add(
            #         sink=project_log_path,
            #         rotation=config.get('FileLog', 'rotation'),
            #         retention=config.get('FileLog', 'retention'),
            #         compression='zip',
            #         encoding="utf-8",
            #         enqueue=True,
            #         format="{level:<8} | {time:YYYY-MM-DD HH:mm:ss} | {message}",  # 普通文本格式
            #         level=config.get('FileLog', 'level')
            #     )
            loguru.logger.add(
                sink=project_log_path,
                rotation=config.get("FileLog", "rotation"),
                retention=config.get("FileLog", "retention"),
                compression="zip",
                encoding="utf-8",
                enqueue=True,
                format="{level:<8} | {time:YYYY-MM-DD HH:mm:ss} | {message}",  # 控制输出格式
                level=config.get("FileLog", "level"),
                serialize=config.getboolean("FileLog", "serialize"),  # 启用 JSON 输出
            )

        # 邮件通知配置(可选)
        if config.get('EmailLog', 'is_open').lower() == "on":
            # 假设配置有SMTP邮件通知
            from loguru import logger
            import smtplib
            from email.mime.text import MIMEText
            from email.mime.multipart import MIMEMultipart

            # 获取SMTP配置
            smtp_server = config.get('EmailLog', 'smtp_server')
            smtp_port = config.get('EmailLog', 'smtp_port')
            smtp_user = config.get('EmailLog', 'smtp_user')
            smtp_password = config.get('EmailLog', 'smtp_password')
            to_email = config.get('EmailLog', 'to_email')

            # 邮件格式配置
            def send_email(record):
                msg = MIMEMultipart()
                msg['From'] = smtp_user
                msg['To'] = to_email
                msg['Subject'] = f"Log - {record['level'].name}"

                body = MIMEText(record['message'], 'plain', 'utf-8')
                msg.attach(body)

                try:
                    with smtplib.SMTP(smtp_server, smtp_port) as server:
                        server.login(smtp_user, smtp_password)
                        server.sendmail(smtp_user, to_email, msg.as_string())
                except Exception as e:
                    logger.error(f"发送邮件失败: {e}")

            # 配置邮件处理器
            loguru.logger.add(send_email, level="ERROR", format="{message}")

    @property
    def get_logger(self):
        return loguru.logger


# 实例化日志类
logger = Logger().get_logger

if __name__ == '__main__':
    logger.debug('调试代码')
    logger.info('输出信息')
    logger.success('输出成功')
    logger.warning('警告信息')
    logger.error('错误信息')
    logger.critical('崩溃信息')

    try:
        1 / 0  # 故意抛出异常
    except ZeroDivisionError as e:
        logger.exception('发生了除零异常')