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

  

 

posted @ 2018-05-28 17:05  Adamanter  阅读(932)  评论(0编辑  收藏  举报