python单例模式和非单例模式日志logger:继承日志模块生成自定义多文件日志
1 继承日志模块生成自定义日志
from __future__ import absolute_import import os import sys import time import datetime import logging import logging.handlers import tempfile DATE_FORMAT = '%Y-%m-%d %H:%M:%S' def create_logfile(): if 'SYAPI_LOG_TEST' in os.environ: value = tempfile.mkdtemp() elif 'SYAPI_LOG' in os.environ: value = os.environ['SYAPI_LOG'] else: value = os.path.join('/var/log','syapi') try: value = os.path.abspath(value) if os.path.exists(value): if not os.path.isdir(value): raise IOError('No such directory: "%s"' % value) if not os.access(value, os.W_OK): raise IOError('Permission denied: "%s"' % value) if not os.path.exists(value): os.makedirs(value) except: print('"%s" is not a valid value for syapi_log.' % value) print('Set the envvar SYAPI_LOG to fix your configuration.') raise return value class JobIdLogger(logging.Logger): def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None): """ Customizing it to set a default value for extra['job_id'] """ rv = logging.LogRecord(name, level, fn, lno, msg, args, exc_info, func) if extra is not None: for key in extra: if (key in ["message", "asctime"]) or (key in rv.__dict__): raise KeyError("Attempt to overwrite %r in LogRecord" % key) rv.__dict__[key] = extra[key] if 'job_id' not in rv.__dict__: rv.__dict__['job_id'] = '' return rv class JobIdLoggerAdapter(logging.LoggerAdapter): """ Accepts an optional keyword argument: 'job_id' You can use this in 2 ways: 1. On class initialization adapter = JobIdLoggerAdapter(logger, {'job_id': job_id}) adapter.debug(msg) 2. On method invocation adapter = JobIdLoggerAdapter(logger, {}) adapter.debug(msg, job_id=id) """ def process(self, msg, kwargs): if 'job_id' in kwargs: if 'extra' not in kwargs: kwargs['extra'] = {} kwargs['extra']['job_id'] = ' [%s]' % kwargs['job_id'] del kwargs['job_id'] elif 'job_id' in self.extra: if 'extra' not in kwargs: kwargs['extra'] = {} kwargs['extra']['job_id'] = ' [%s]' % self.extra['job_id'] return msg, kwargs def setup_logging(flag,job_id): # Set custom logger logging.setLoggerClass(JobIdLogger) formatter = logging.Formatter( fmt="%(asctime)s%(job_id)s [%(levelname)-5s] %(message)s", datefmt=DATE_FORMAT, ) sh_logger = logging.getLogger('sy_stream') sh_logger.setLevel(logging.DEBUG) sh = logging.StreamHandler(sys.stdout) sh.setFormatter(formatter) sh.setLevel(logging.DEBUG) sh_logger.addHandler(sh) log_dir = create_logfile() log_name = str(flag)+"-"+ str(job_id) + ".log" log_path = os.path.join(log_dir, log_name) if log_path is not None: file_logger = logging.getLogger('sy_file') file_logger.setLevel(logging.DEBUG) fh = logging.FileHandler(log_path, "w") fh.setFormatter(formatter) fh.setLevel(logging.DEBUG) file_logger.addHandler(fh) # Useful shortcut for the webapp, which may set job_id return JobIdLoggerAdapter(file_logger,{'job_id':job_id}) else: print('WARNING: log_file config option not found - no log file is being saved') return JobIdLoggerAdapter(sh_logger, {'job_id':job_id}) def init_kubelog_logger(flag,job_id): logger = setup_logging(flag,job_id) return logger # job_id = '%s-%s' % (time.strftime('%Y%m%d-%H%M%S'), os.urandom(2).encode('hex')) # log_obj = init_kubelog_logger("syapi",job_id) log_obj = init_kubelog_logger("syapi","user001") log_obj.info("hello world log start") # create_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # create_time_c = time.time()
2. 利用logging模块的单例模式改造成支持多个日志记录
import logging from logging import handlers from config.log_config import LOG_DIR_FILE from config.log_config import TOPIC_COUNT_LOG_DIR_FILE from config.log_config import HISTORY_TOPIC_COUNT_LOG_DIR_FILE M_size = 1024 * 1024 # 计算M的大小 class LogHandler(object): """ 只需要一个日志对象的,适合单例模式, logging.getLogger()本身就是单例的 多个相互独立的程序,需要很多日志, 还是要生成对象 :param object: :return: """ def __init__(self, file_dir, log_name, debug=True, log_level="INFO", split='time', split_attr='midnight', backup_count=15): """ 初始化日志记录模块 Args: file_name: 日志路径文件名 debug: True(default): 日志写入文件的同时会输出到终端; False则只写入文件 log_level: 日志记录级别, 设定日志的时候使用,DEBUG, INFO,WARNING,ERROR,CRITICAL INFO(default) split: 日期文件的切分规则,默认值为time time: 表示按照实际切分,每天一个文件, 好处是每天一个文件,数据量小比较OK; size: 表示按照大小切分, 每个文件100M, 好处是文件比较小; "": 空字符串表示所有日志写入单个文件中, 应该强烈避免 split_attr: 文件切分属性, 当split='time'时,split_attr值可以为'S'每秒一个文件;’M'每分钟一个文件;'H'每小时一个文件;'D'每天一个文件; 当split='time'时,split_attr值可以为'midnight'在每天0点创建新文件;'W0'-'W6'每周的某天创建一个文件,0表示周一; 当split='size'时,split_attr值为一个元组,元组的第一个元素表示文件大小(单位M),第二个元素表示保留文件的数量 backup_count: 保存的天数, 默认会保存最近15天 Return: 无返回信息 """ self.debug = debug self.log_name = log_name self.file_dir = file_dir self.log_level = log_level self.split = split self.split = split_attr self.backup_count = backup_count def create_logger(self): # logging的初步日志对象, 后面用来add, StreamHandler, FileHandler, 总是单例,加上name就是初始化对象 self.logger = logging.getLogger(name=self.log_name) self.logger.setLevel(logging.DEBUG) # 过滤该level以下的日志消息, 此设置会影响单个handler的设置,如果此级别高于handler则使用此级别 # 日志输出格式 fmt = logging.Formatter('%(asctime)s %(levelname)s [%(filename)s %(lineno)d %(funcName)s] %(message)s') # 日志级别 lvl = eval("logging." + self.log_level) # 输出流的级别和格式保持和文件保持一致 # 如果是测试模式,创建控制台输出流 if self.debug: self.console = logging.StreamHandler() self.console.setLevel(lvl) self.console.setFormatter(fmt) self.logger.addHandler(self.console) # 使用时间自动分割保留15天 if self.split == 'time': # interval 时间间隔,每隔1天分割一次,即按天分日志 file_handler = handlers.TimedRotatingFileHandler(self.file_dir, self.split_attr, interval=1, backupCount=self.backup_count) elif self.split == 'size': self.log_size, self.log_backup_count = self.split_attr file_handler = handlers.RotatingFileHandler(self.file_dir, maxBytes=self.log_size * M_size, backupCount=self.backup_count) else: file_handler = logging.FileHandler(self.file_dir) # 在设置之前的日志模式上加入文件 file_handler.setLevel(lvl) file_handler.setFormatter(fmt) self.logger.addHandler(file_handler) return self.logger logger = LogHandler(LOG_DIR_FILE, log_name="logger").create_logger() topic_logger = LogHandler(TOPIC_COUNT_LOG_DIR_FILE, log_name="topic_logger").create_logger() history_topic_logger = LogHandler(HISTORY_TOPIC_COUNT_LOG_DIR_FILE, log_name="history_topic_logger").create_logger()
对上面的文本的基本改良:
#! /usr/bin/env python # -*- coding: utf-8 -*- # __author__ = "Victor" # Date: 2020/7/14 import os import logging from logging import handlers M_size = 1024 * 1024 # 计算M的大小 class LogHandler(object): """ 只需要一个日志对象的,适合单例模式, logging.getLogger()本身就是单例的 多个相互独立的程序,需要很多日志, 还是要生成对象 :param object: :return: """ def __init__(self, file_dir, log_name, debug=True, log_level="INFO", split='time', split_attr='midnight', backup_count=15): """ 初始化日志记录模块 Args: file_name: 日志路径文件名 debug: True(default): 日志写入文件的同时会输出到终端; False则只写入文件 log_level: 日志记录级别, 设定日志的时候使用,DEBUG, INFO,WARNING,ERROR,CRITICAL INFO(default) split: 日期文件的切分规则,默认值为time time: 表示按照实际切分,每天一个文件, 好处是每天一个文件,数据量小比较OK; size: 表示按照大小切分, 每个文件100M, 好处是文件比较小; "": 空字符串表示所有日志写入单个文件中, 应该强烈避免 split_attr: 文件切分属性, 当split='time'时,split_attr值可以为'S'每秒一个文件;’M'每分钟一个文件;'H'每小时一个文件;'D'每天一个文件; 当split='time'时,split_attr值可以为'midnight'在每天0点创建新文件;'W0'-'W6'每周的某天创建一个文件,0表示周一; 当split='size'时,split_attr值为一个元组,元组的第一个元素表示文件大小(单位M),第二个元素表示保留文件的数量 backup_count: 保存的天数, 默认会保存最近15天 Return: 无返回信息 """ self.debug = debug self.log_name = log_name self.file_dir = file_dir self.log_level = log_level self.split = split self.split = split_attr self.backup_count = backup_count # 日志的上一级文件如果不存在就创建 logs_dir = os.path.dirname(self.file_dir) if not os.path.exists(logs_dir): os.mkdir(logs_dir) def create_logger(self): # logging的初步日志对象, 后面用来add, StreamHandler, FileHandler, 总是单例,加上name就是初始化对象 self.logger = logging.getLogger(name=self.log_name) self.logger.setLevel(logging.DEBUG) # 过滤该level以下的日志消息, 此设置会影响单个handler的设置,如果此级别高于handler则使用此级别 # 日志输出格式 fmt = logging.Formatter('%(asctime)s %(levelname)s [%(filename)s %(lineno)d %(funcName)s] %(message)s') # 日志级别 lvl = eval("logging." + self.log_level) # 输出流的级别和格式保持和文件保持一致 # 如果是测试模式,创建控制台输出流 if self.debug: self.console = logging.StreamHandler() self.console.setLevel(lvl) self.console.setFormatter(fmt) self.logger.addHandler(self.console) # 使用时间自动分割保留15天 if self.split == 'time': # interval 时间间隔,每隔1天分割一次,即按天分日志 file_handler = handlers.TimedRotatingFileHandler(self.file_dir, self.split_attr, interval=1, backupCount=self.backup_count) elif self.split == 'size': self.log_size, self.log_backup_count = self.split_attr file_handler = handlers.RotatingFileHandler(self.file_dir, maxBytes=self.log_size * M_size, backupCount=self.backup_count) else: file_handler = logging.FileHandler(self.file_dir) # 在设置之前的日志模式上加入文件 file_handler.setLevel(lvl) file_handler.setFormatter(fmt) self.logger.addHandler(file_handler) return self.logger # 初始化日志对象,并生成日志文件,在此文件的同级必须存在logs目录 base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) logs_dir = os.path.join(base, "logs") print("logs: ", logs_dir) img_log_file = "%s/%s_%s.log" % (logs_dir, "gnews_run_img", 2) video_log_file = "%s/%s_%s.log" % (logs_dir, "gnews_run_video", 2) # 在别的文件引入这个类, 然后用这个对象即可 img_logger = LogHandler(img_log_file, log_name="gnews_run_img", log_level="INFO").create_logger() video_logger = LogHandler(video_log_file, log_name="gnews_run_video", log_level="INFO").create_logger() img_logger.info("img") video_logger.info("video")