common_app_config.py 6.89 KB
Newer Older
qunfeng qiu's avatar
qunfeng qiu committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@Version: 1.0
@Python Version:3.6.6
@Author: ludq1
@Email: ludq1@chinaunicom.cn
@date: 2023/04/07 11:40:00
@Description:
"""

import json
import logging
import os
import traceback
from typing import List, Dict, Union

import yaml

from .base_const import ConstBaseApp


class CommonAppConfig:
    r"""
    应用的通用配置类,通过 CommonAppConfig() 获取
    """
    # 通用配置
    is_debug: bool = False
    log_level_dict = {"debug": logging.DEBUG, "info": logging.INFO, "warn": logging.WARN,
                      "warning": logging.WARNING,
                      "error": logging.ERROR, "fatal": logging.FATAL, "critical": logging.CRITICAL}
    # 初始化字典中log_level使用字符串,初始化后转换为对应的log level int值
    log_level: int = None
    runtime_env: str = None

    _common_logger: logging.Logger = None
    r"""通用的debug使用的logger"""

    # 运行时内存缓存
    runtime_cache_dict: dict = None

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, 'inst'):  # 单例
            self = super().__new__(cls, *args, **kwargs)

            # 通过环境变量初始化,应用要连接的数据库、redis等其他配置
            self.init_app_config()
            cls.inst = self
        return getattr(cls, 'inst')

    def init_app_config(self):
        is_debug_str = os.environ.get("IS_DEBUG") or "false"
        self.is_debug = is_debug_str.lower() == "true"

        if self.is_debug:
            self.log_level = logging.DEBUG
        else:
            log_level_str = os.environ.get("LOG_LEVEL") or "info"
            log_level_int = self.log_level_dict.get(log_level_str.lower())
            if log_level_int is None:
                log_level_int = logging.INFO
            self.log_level = log_level_int

        self.runtime_env = os.environ.get("RUNTIME_ENV") or "NOTSET"

    def refresh_app_config(self, conf_filepath: str = ConstBaseApp.COMMON_APP_CONFIG_DEFAULT_CONF_FILE_PATH):
        if not conf_filepath:
            conf_filepath = ConstBaseApp.COMMON_APP_CONFIG_DEFAULT_CONF_FILE_PATH

        try:
            app_config_dict = self.load_file_as_dict(conf_filepath)
        except BaseException as e:
            if conf_filepath != ConstBaseApp.COMMON_APP_CONFIG_DEFAULT_CONF_FILE_PATH:
                self.common_logger.warning(f"读取配置文件{conf_filepath}时发生异常:{e},不刷新CommonAppConfig")
                self.common_logger.warning(traceback.format_exc())
            return

        # 加载默认值
        self.init_app_config()

        for conf_key in {"runtime_env"}:
            conf_value = app_config_dict.get(conf_key)
            if conf_value is not None:
                setattr(self, conf_key, conf_value)

        is_debug = app_config_dict.get("is_debug")
        if is_debug is None:
            # 如果没有配置属性,则什么也不做
            pass
        else:
            if isinstance(is_debug, bool):
                self.is_debug = is_debug
            else:
                is_debug_str = str(is_debug)
                self.is_debug = is_debug_str.lower() == "true"

        if self.is_debug:
            self.log_level = logging.DEBUG
        else:
            log_level = app_config_dict.get("log_level")
            if log_level is None:
                # 如果没有配置属性,则什么也不做
                pass
            else:
                if isinstance(log_level, int):
                    self.log_level = log_level
                else:
                    log_level_str = str(log_level)
                    log_level_int = self.log_level_dict.get(log_level_str.lower())
                    if log_level_int is None:
                        log_level_int = logging.INFO
                    self.log_level = log_level_int

        return

    def load_file_as_dict(self, filepath: str) -> dict:
        r"""
        判断文件后缀名, 如果是 .yaml 或 .yml 则按 yaml 方式读取,
        文件内容不合规,则抛出异常
        """
        with open(filepath, 'r', encoding="utf-8") as dict_file:
            conf_content = dict_file.read()
            # 无论是 yaml 格式 还是 json 格式,都可以使用 yaml_load 读取,但读取的结果可能是 字典或字符串
            dict_content = self.yaml_load(conf_content)
            if isinstance(dict_content, str):
                raise ValueError(f"{filepath}的内容不是有效的json或yaml字符串")

        return dict_content

    def yaml_load(self, yaml_str: str) -> Union[dict, str]:
        r"""
        无论是 yaml 格式 还是 json 格式,都可以使用 yaml_load 读取,但读取的结果可能是 字典或字符串
        """
        dict_list = self.yaml_load_list(cfg_content=yaml_str)
        if dict_list:
            return dict_list[0]
        else:
            return dict()

    def yaml_load_list(self, path: str = None, cfg_content: str = None) -> List[Union[Dict, str]]:
        """
        将yaml格式文件转换为dict值,该yaml文件可以包含多块yaml数据,每个dict放到list中返回,
        无论是 yaml 格式 还是 json 格式,都可以使用 yaml_load 读取,但读取的结果可能是 字典或字符串

        :param path: 文件路径
        :param cfg_content:
        :return: list -> []
        """
        if path:
            cfg = self.read_file_content(file_path=path)
        else:
            cfg = cfg_content or ""

        yaml_generator = yaml.safe_load_all(cfg)  # 将yaml格式文件转换为dict值,该yaml文件可以包含多块yaml数据
        if yaml_generator is None:
            return list()

        # 转成dict并保存到list中
        yaml_list = list()
        for one_yaml_generator in yaml_generator:
            if one_yaml_generator is None:
                continue
            yaml_list.append(json.loads(json.dumps(one_yaml_generator)))
        return yaml_list

    def read_file_content(self, file_path) -> str:
        """
        读取文件内容

        :param file_path: 文件路径
        :return: str -> file-content
        """
        with open(file_path, 'r') as f:  # 打开一个文件,必须是'rb'模式打开
            return f.read()

    @property
    def common_logger(self):
        r"""
        通用的debug使用的logger
        """
        if self._common_logger is None:
            logger = logging.getLogger("COMMON_LOGGER")
            logger.setLevel(self.log_level)
            if not logger.handlers:
                ch = logging.StreamHandler()
                ch.setLevel(logging.DEBUG)
                formatter = logging.Formatter(
                    '%(asctime)s %(name)s - %(pathname)s - func:%(funcName)s - lineno:%(lineno)s - %(levelname)s - %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S'
                )
                ch.setFormatter(formatter)
                logger.addHandler(ch)
            self._common_logger = logger
        return self._common_logger