oscar 数据处理软件
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
oscar/mylogger.py

285 lines
11 KiB

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