# coding=utf-8
'''
单例模式日志  -- 使用一次关闭 handler
这种方法优缺点：
缺点：  输出的format 需要自己定义 ，并过滤
        过滤要看是否以什么开头，或包含什么
优点：  不占用文件资源，占用系统资源小
调用  log.info( )  log.error()
'''

import logging
import logging.handlers
import os
import time
import threading
# from config import LOG_PATH,LOG_LEVEL,LOG_ENABLED,LOG_FORMAT, \
#         LOG_TO_CONSOLE,LOG_TO_FILE

MY_LOGGER_NAME = "DefaultLogger"
LOG_ENABLED = True          # 是否启用日志
LOG_TO_CONSOLE = True       # 是否启用控制台输出日志
LOG_TO_FILE = False         # 是否启用文件输出
LOG_COLOR_ENABLE = True     # 是否启用颜色日志

LOGGER_DIR = "logs"
LOGGER_PATH = os.path.join( os.path.dirname(__file__), LOGGER_DIR )
LOGGER_FILENAME = os.path.join(  LOGGER_PATH, 'logs.log' )

"""
logging.INFO , logging.DEBUG , logging.WARNING , logging.ERROR , 
"""
LOG_LEVEL = logging.INFO  # 日志等级DEBUG  INFO  WARNIG ERROR
# LOG_LEVEL = logging.DEBUG  
# LOG_LEVEL = logging.WARNING  

"""
# LOG_FORMAT = " %(name)s - %(module)s - %(filename)s - %(lineno)d | %(levelname)s : %(message)s"
# LOG_FORMAT = "%(levelname)s - %(asctime)s - process: %(process)d - threadname: %(thread)s " \
#              "- %(filename)s - %(funcName)s - %(lineno)d - %(module)s " \
#              "- %(message)s  "
# LOG_FORMAT = "%(asctime)s -  %(thread)s " \
#              "- %(levelname)s ::: %(message)s "             
# '[%(asctime)s] |%(thread)s| %(levelname)-6s: %(message)s'         
# fm = '%(levelname)：%(levelno)s：%(name)s：%(funcName)s：%(asctime)：%(pathname)：%(filename)：%(module)：%(thread)：%(threadName)'
# 此处日志颜色，修改日志颜色是通过 Filter实现的
"""
LOG_FORMAT = '%(levelname)s\t[%(asctime)s]  %(package)s:%(classname)s:%(funcname)s \t>>  %(message)s'

"""
# 此处日志颜色，修改日志颜色是通过 Filter实现的
"""
LOG_FORMAT_COLOR_DICT = {
    'ERROR'   : "\033[31mERROR\033[0m",
    'INFO'    : "\033[36mINFO\033[0m",
    'DEBUG'   : "\033[1mDEBUG\033[0m",
    'WARN'    : "\033[33mWARN\033[0m",
    'WARNING' : "\033[33mWARNING\033[0m",
    'CRITICAL': "\033[35mCRITICAL\033[0m",
}

"""
# Filter 用法， 以package  class function 过滤 __package__  __class__
# log.error( f"{__package__}::{__class__.__name__}::{sys._getframe().f_code.co_name} >>  ")
# log.error( f"PacakgeName::ClassName::FunctionName::  ")
# LOGGER_FILTER_PACKAGE=[]  为空, 则Filter不起作用
#                         不为空，则只显示定义的报名
# LOGGER_FILTER_CLASS=[]  为空, 则Filter不起作用
#                         不为空，则只显示定义的类或
# LOGGER_FILTER_FUNCNAME=[]  为空, 则Filter不起作用
#                         不为空，则只显示定义的函数
"""
# LOGGER_FILTER_PACKAGE = [ "test_logger" ]   # 包名，文件名去 .py??
LOGGER_FILTER_PACKAGE = [  ]   
LOGGER_FILTER_CLASS = [   ]   # 类名，文件名去 .py??
# LOGGER_FILTER_CLASS = [ "LogTest" ]  
# LOGGER_FILTER_FUNCNAME =  [ "test1","test"  ]    # 函数名
LOGGER_FILTER_FUNCNAME =  [   ]   
LOGGER_FILTER_LEVELNAME = [ ]   # INFO  DEBUG WARNING
    
class PackageFilter(logging.Filter):
    def __init__(self, filter_word:list = []):
        self.filter_word = filter_word
        pass
    def filter(self, record: logging.LogRecord) -> bool:
        if  self.filter_word is not None: 
            return record.package in self.filter_word

class ClassFilter(logging.Filter):
    def __init__(self, filter_word:list = []):
        self.filter_word = filter_word
        pass
    def filter(self, record: logging.LogRecord) -> bool:
        if  self.filter_word is not None: 
            return record.classname in self.filter_word

            pass
        
class FunctionFilter(logging.Filter):
    def __init__(self, filter_word:list = []):
        self.filter_word = filter_word
        pass
    def filter(self, record:logging.LogRecord) -> bool:
        if  self.filter_word is not None: 
            return record.funcname in self.filter_word

class LevelNameFilter(logging.Filter):
    def __init__(self, filter_word:list = []):
        self.filter_word = filter_word
        pass
    def filter(self, record:logging.LogRecord) -> bool:
        if  self.filter_word is not None: 
            return record.levelname in self.filter_word  
                
class ColorFilter(logging.Filter):
    def __init__(self,):
         pass
    def filter(self, record: logging.LogRecord) -> bool:
        record.levelname = LOG_FORMAT_COLOR_DICT.get(record.levelname)
        return True 

class Log(object):
    _instance_lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if not hasattr(Log, "_instance"):
            with Log._instance_lock:
                if not hasattr(Log, "_instance"):
                    Log._instance = object.__new__(cls)
        return Log._instance

    def __init__(self, loggername = "DefaultLog" ):
        # 文件命名   os.path.join()：  将多个路径组合后返回
        self.logger_filepath = LOGGER_FILENAME
        self.loggername = loggername 
        self.level = LOG_LEVEL

        # 日志输出格式
        fm = LOG_FORMAT
        self.formatter = logging.Formatter( fm )
         
        # 生成记录器对象
        self.logger = logging.getLogger( self.loggername )
        self.logger.setLevel(LOG_LEVEL)     
                
        # 日志过滤
        self.__add_filter()

    def __console(self, level, message, extra={} ):
        # 添加 handler       
        self.__add_handler()
        
        # 判断日志级别
        if level == logging.INFO:
            self.logger.info( message, extra=extra)
        elif level == logging.DEBUG:
            self.logger.debug(message,extra=extra)
        elif level == logging.WARNING:
            self.logger.warning(message,extra=extra)
        elif level == logging.ERROR:
            self.logger.error(message,extra=extra)

        # removeHandler在记录日志之后移除句柄，避免日志输出重复问题
        self.__remove_handler()
        # if LOG_TO_FILE and self.file_handler:
        #     self.logger.removeHandler(self.file_handler)
        #     # 关闭打开的文件
        #     self.file_handler.close()
        # if LOG_TO_CONSOLE and self.stream_handler:
        #     self.logger.removeHandler(self.stream_handler)
        #     # 关闭打开的文件
        #     self.stream_handler.close()
        pass
    
    # debug < info< warning< error< critical
    # debug模式
    def debug(self, message, package="Unknown", classname="Unknown", funcname="Unknown" ):
        self.__console(logging.DEBUG, message, extra={"package":package, "classname":classname, "funcname":funcname} )
        # self.__remove_handler()
    # info模式
    def info(self, message, package="Unknown",  classname="Unknown", funcname="Unknown" ):
        self.__console(logging.INFO, message, extra={"package":package, "classname":classname, "funcname":funcname} )
        # self.__remove_handler()
    # warning模式
    def warning(self, message, package="Unknown",  classname="Unknown", funcname="Unknown"):
        self.__console(logging.WARNING, message, extra={"package":package, "classname":classname, "funcname":funcname} )
        # self.__remove_handler()

    # error模式
    def error(self, message, package="Unknown",  classname="Unknown", funcname="Unknown"):
        self.__console(logging.ERROR, message, extra={"package":package, "classname":classname, "funcname":funcname} )
        # self.__remove_handler()

    def __add_filter(self ):
        if len( LOGGER_FILTER_PACKAGE ) > 0 :
            self.logger.addFilter( PackageFilter( filter_word=LOGGER_FILTER_PACKAGE ) )
        if len( LOGGER_FILTER_CLASS ) > 0 :
            self.logger.addFilter( ClassFilter( filter_word=LOGGER_FILTER_CLASS ) )
        if len( LOGGER_FILTER_FUNCNAME ) > 0 :
            self.logger.addFilter( FunctionFilter( filter_word=LOGGER_FILTER_FUNCNAME ) )
        if len(LOGGER_FILTER_LEVELNAME) > 0 :
            self.logger.addFilter( LevelNameFilter( filter_word=LOGGER_FILTER_LEVELNAME ) )

    def __add_handler(self ):
        if LOG_ENABLED and LOG_TO_FILE:
            # 考虑使用 RotatingFileHandler TimedRotatingFileHandler防止日志过大
            # RotatingFileHandler("test", "a", 4096, 2, "utf-8")
            # TimedRotatingFileHandler(filename=LOG_PATH+"thread_", when="D", interval=1, backupCount=7)
            self.file_handler = logging.handlers.TimedRotatingFileHandler(filename=self.logger_filepath, when='D', interval=1, backupCount=30, encoding='utf-8')
            # self.file_handler = logging.FileHandler( self.logger_filepath, encoding='utf-8' )
            self.file_handler.setFormatter( self.formatter )
            # self.file_handler.setLevel(  LOG_LEVEL )
            # if LOG_COLOR_ENABLE:  # 文件日志无需加彩色
            #     self.file_handler.addFilter(  ColorFilter( ) )
            self.logger.addHandler(self.file_handler)

        if LOG_ENABLED and LOG_TO_CONSOLE:
            # 创建一个StreamHandler,用于输出到控制台

            self.stream_handler = logging.StreamHandler()
            self.stream_handler.setFormatter(self.formatter)
            # self.stream_handler.setLevel(  LOG_LEVEL )
            if LOG_COLOR_ENABLE:
                self.stream_handler.addFilter(   ColorFilter( ) )
            self.logger.addHandler(self.stream_handler)    

    def __remove_handler(self ):
        if   LOG_TO_FILE  and self.file_handler:    
            self.logger.removeHandler(self.file_handler)
            if len(self.logger.handlers)>0:
                self.logger.handlers.pop()
            # 关闭打开的文件
            self.file_handler.close()
        if  LOG_TO_CONSOLE  and self.stream_handler:
            self.logger.removeHandler(self.stream_handler)
            if len(self.logger.handlers)>0:
                self.logger.handlers.pop()
            # 关闭控制台
            self.stream_handler.close() 

    def __remove_handler2(self ):
        if LOG_ENABLED and LOG_TO_CONSOLE:
            self.logger.removeHandler(self.stream_handler)
            self.logger.handlers.pop()
            # 关闭控制台
            self.stream_handler.close() 
        if LOG_ENABLED and LOG_TO_FILE:    
            self.logger.removeHandler(self.file_handler)
            self.logger.handlers.pop()
            # 关闭打开的文件
            self.file_handler.close()


log = Log( loggername = "DefaultLog")

"""
filename:       指定日志文件名
filemode:       和file函数意义相同，指定日志文件的打开模式，’w’或’a’
format:         指定输出的格式和内容，format可以输出很多有用信息。显示的条目可以是以下内容：
%(levelname)：  日志级别的名字格式
%(levelno)s：   日志级别的数字表示
%(name)s：      日志名字 loggername
%(funcName)s：  函数名字
%(asctime)：    日志时间，可以使用datefmt去定义时间格式，如上图。
%(pathname)：   脚本的绝对路径
%(filename)：   脚本的名字
%(module)：     模块的名字
%(thread):      thread id
%(threadName)： 线程的名字 
"""

"""
文件名行号  函数名, 要在调用的时候想办法了
# 绝对路径
print( __file__ )
print( sys.argv[0] )

# 文件名
print( os.path.basename(__file__) )
print( os.path.basename(sys.argv[0]) )

self.__class__.__name__
self.__class__.__name__, get_current_function_name()

logger名 __name__
"""