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")

浙公网安备 33010602011771号