# 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__ """