python logging多进程解决方案

1. 用单进程部署日志系统
 
2.用redis加全局日志锁

 

#coding=utf-8
"""
"""
from gevent import monkey; monkey.patch_os() #mokey.pathc_all() 这个千万慎用---会导致redis锁用不了
import gevent 
import logging
try:
    from django.conf import settings
    import sys, os, datetime
    sys.stdout = sys.stderr  # 调试supervisor的时候需要显示print的日志就加这一句
    sys.path.append('/home/sy/workspace/server/lzserver')
    sys.path.append('/home/sy/workspace/server')
    sys.path.append('/home/sy/workspace/server/lzserver/game')
    from game import settings as project_settings
    from django.db.models import Count,Sum
except Exception,e:
    pass

settings.configure(DEBUG=False,  # 要想在supervisor中显示出djanog的print信息,这里要设置为True,还没实践
                   DATABASES=project_settings.DATABASES,
                   INSTALLED_APPS=project_settings.INSTALLED_APPS,
                   CACHES=project_settings.CACHES,
                   DATABASE_ROUTERS=project_settings.DATABASE_ROUTERS,
                   )

try:
    import django
    django.setup()
except Exception,e:
    pass

try:
    from common.defines import BMOBJ
    BMOBJ.initConfigG()
    from common.configs import lbsclientc,gaojic
except Exception,e:
    print  'import error'

import platform
from twisted.internet import reactor
from twisted.internet import threads, task
#
PF_YX = (platform.platform()).split("-")[0]
G_LOG_FILE = "/home/sy/workspace/server/lzserver/game/common/log/test.log"
LOCK_DIR = "/home/sy/workspace/server/lzserver/game/common/log/.lock"

def initlog(LOG_FILE=G_LOG_FILE):
    """
    @des: 线程安全,进程不安全--可以再win和linux运行
    """
    # 生成一个日志对象
    
    logger = logging.getLogger("dadiantest")
    if not logger.handlers:#重复输出日志的问题,这样解决
        # 生成一个Handler。logging支持许多Handler,例如FileHandler, SocketHandler, SMTPHandler等,
        #print logger.manager.loggerDict
    # 我由于要写文件就使用了FileHandler。
        #hdlr = logging.FileHandler(LOG_FILE)
        
        #interval参数默认“1”,如果when=‘h’,那么就是每一小时对日志进行一次分割,即debug.log所在目录会出现 
        hdlr = TimedRotatingFileHandler_MP(LOG_FILE,when='S',interval=1,backupCount=100)  
        #hdlr = logging.handlers.TimedRotatingFileHandler(LOG_FILE,when='S',interval=5,backupCount=100)
        #hdlr = logging.handlers.RotatingFileHandler(LOG_FILE,maxBytes=1024*10,backupCount=1000)
        #hdlr = ConcurrentRotatingFileHandler(LOG_FILE,"a", maxBytes=1024*10,backupCount=1000) 
        #hdlr = MyTimedRotatingFileHandler(LOG_FILE,when='S',interval=1,backupCount=100)
        # 生成一个格式器,用于规范日志的输出格式。如果没有这行代码,那么缺省的
        # 格式就是:"%(message)s"。也就是写日志时,信息是什么日志中就是什么,
        # 没有日期,没有信息级别等信息。logging支持许多种替换值,详细请看
        # Formatter的文档说明。这里有三项:时间,信息级别,日志信息
        #formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        # 将格式器设置到处理器上
        hdlr.setFormatter(formatter)
        # 将处理器加到日志对象上
        logger.addHandler(hdlr)
        # 设置日志信息输出的级别。logging提供多种级别的日志信息,如:NOTSET,
        # DEBUG, INFO, WARNING, ERROR, CRITICAL等。每个级别都对应一个数值。
        # 如果不执行此句,缺省为30(WARNING)。可以执行:logging.getLevelName
        # (logger.getEffectiveLevel())来查看缺省的日志级别。日志对象对于不同
        # 的级别信息提供不同的函数进行输出,如:info(), error(), debug()等。当
        # 写入日志时,小于指定级别的信息将被忽略。因此为了输出想要的日志级别一定
        # 要设置好此参数。这里我设为NOTSET(值为0),也就是想输出所有信息
    #print logger.handlers
    logger.setLevel(logging.DEBUG)
    return logger


from logging import StreamHandler, FileHandler
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
import fcntl, time, os, codecs, string, re, types, cPickle, struct, shutil
from stat import ST_DEV, ST_INO, ST_MTIME


class StreamHandler_MP(StreamHandler):
    """
    A handler class which writes logging records, appropriately formatted,
    to a stream. Use for multiprocess.
    """
    
    def emit(self, record):
        """
        Emit a record.
            First seek the end of file for multiprocess to log to the same file
        """
        try:
            if hasattr(self.stream, "seek"):
                self.stream.seek(0, os.SEEK_END)
        except IOError, e:
            pass
        
        StreamHandler.emit(self, record)


class FileHandler_MP(FileHandler, StreamHandler_MP):
    """
    A handler class which writes formatted logging records to disk files 
        for multiprocess
    """
    def emit(self, record):
        """
        Emit a record.

        If the stream was not opened because 'delay' was specified in the
        constructor, open it before calling the superclass's emit.
        """
        if self.stream is None:
            self.stream = self._open()
        StreamHandler_MP.emit(self, record)


class RotatingFileHandler_MP(RotatingFileHandler, FileHandler_MP):
    """
    Handler for logging to a set of files, which switches from one file
    to the next when the current file reaches a certain size.
    
    Based on logging.RotatingFileHandler, modified for Multiprocess
    """
    _lock_dir = LOCK_DIR
    if os.path.exists(_lock_dir):
        pass
    else:
        os.mkdir(_lock_dir)

    def doRollover(self):
        """
        Do a rollover, as described in __init__().
        For multiprocess, we use shutil.copy instead of rename.
        """

        self.stream.close()
        if self.backupCount > 0:
            for i in range(self.backupCount - 1, 0, -1):
                sfn = "%s.%d" % (self.baseFilename, i)
                dfn = "%s.%d" % (self.baseFilename, i + 1)
                if os.path.exists(sfn):
                    if os.path.exists(dfn):
                        os.remove(dfn)
                    shutil.copy(sfn, dfn)
            dfn = self.baseFilename + ".1"
            if os.path.exists(dfn):
                os.remove(dfn)
            if os.path.exists(self.baseFilename):
                shutil.copy(self.baseFilename, dfn)
        self.mode = 'w'
        self.stream = self._open()
        
    
    def emit(self, record):
        """
        Emit a record.

        Output the record to the file, catering for rollover as described
        in doRollover().
        
        For multiprocess, we use file lock. Any better method ?
        """
        try:
            if self.shouldRollover(record):
                self.doRollover()
            FileLock = self._lock_dir + '/' + os.path.basename(self.baseFilename) + '.' + record.levelname
            f = open(FileLock, "w+")
            fcntl.flock(f.fileno(), fcntl.LOCK_EX)
            FileHandler_MP.emit(self, record)
            fcntl.flock(f.fileno(), fcntl.LOCK_UN)
            f.close()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)
    
        
class TimedRotatingFileHandler_MP(TimedRotatingFileHandler, FileHandler_MP):
    """
    Handler for logging to a file, rotating the log file at certain timed
    intervals.

    If backupCount is > 0, when rollover is done, no more than backupCount
    files are kept - the oldest ones are deleted.
    """
    _lock_dir = LOCK_DIR
    if os.path.exists(_lock_dir):
        pass
    else:
        os.mkdir(_lock_dir)
    
    def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=0):
        FileHandler_MP.__init__(self, filename, 'a', encoding, delay)
        self.encoding = encoding
        self.when = string.upper(when)
        self.backupCount = backupCount
        self.utc = utc
        # Calculate the real rollover interval, which is just the number of
        # seconds between rollovers.  Also set the filename suffix used when
        # a rollover occurs.  Current 'when' events supported:
        # S - Seconds
        # M - Minutes
        # H - Hours
        # D - Days
        # midnight - roll over at midnight
        # W{0-6} - roll over on a certain day; 0 - Monday
        #
        # Case of the 'when' specifier is not important; lower or upper case
        # will work.
        if self.when == 'S':
            self.suffix = "%Y-%m-%d_%H-%M-%S"
            self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$"
        elif self.when == 'M':
            self.suffix = "%Y-%m-%d_%H-%M"
            self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$"
        elif self.when == 'H':
            self.suffix = "%Y-%m-%d_%H"
            self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}$"
        elif self.when == 'D' or self.when == 'MIDNIGHT':
            self.suffix = "%Y-%m-%d"
            self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
        elif self.when.startswith('W'):
            if len(self.when) != 2:
                raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
            if self.when[1] < '0' or self.when[1] > '6':
                raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
            self.dayOfWeek = int(self.when[1])
            self.suffix = "%Y-%m-%d"
            self.extMatch = r"^\d{4}-\d{2}-\d{2}$"
        else:
            raise ValueError("Invalid rollover interval specified: %s" % self.when)

        self.extMatch = re.compile(self.extMatch)
        
        if interval != 1:
            raise ValueError("Invalid rollover interval, must be 1")


    def shouldRollover(self, record):
        """
        Determine if rollover should occur.

        record is not used, as we are just comparing times, but it is needed so
        the method signatures are the same
        """
        if not os.path.exists(self.baseFilename):
            #print "file don't exist"  
            return 0 
        
        cTime = time.localtime(time.time()) 
        mTime = time.localtime(os.stat(self.baseFilename)[ST_MTIME])
        if self.when == "S" and cTime[5] != mTime[5]:
            #print "cTime:", cTime[5], "mTime:", mTime[5]
            return 1
        elif self.when == 'M' and cTime[4] != mTime[4]:  
            #print "cTime:", cTime[4], "mTime:", mTime[4]
            return 1  
        elif self.when == 'H' and cTime[3] != mTime[3]: 
            #print "cTime:", cTime[3], "mTime:", mTime[3]
            return 1
        elif (self.when == 'MIDNIGHT' or self.when == 'D') and cTime[2] != mTime[2]:
            #print "cTime:", cTime[2], "mTime:", mTime[2]
            return 1 
        elif self.when == 'W' and cTime[1] != mTime[1]:
            #print "cTime:", cTime[1], "mTime:", mTime[1]
            return 1
        else:  
            return 0 
    

    def doRollover(self):
        """
        do a rollover; in this case, a date/time stamp is appended to the filename
        when the rollover happens.  However, you want the file to be named for the
        start of the interval, not the current time.  If there is a backup count,
        then we have to get a list of matching filenames, sort them and remove
        the one with the oldest suffix.
        
        For multiprocess, we use shutil.copy instead of rename.
        """
        if self.stream:
            self.stream.close()
        # get the time that this sequence started at and make it a TimeTuple
        #t = self.rolloverAt - self.interval
        t = int(time.time())
        if self.utc:
            timeTuple = time.gmtime(t)
        else:
            timeTuple = time.localtime(t)
        dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
        if os.path.exists(dfn):
            os.remove(dfn)
        if os.path.exists(self.baseFilename):
            shutil.copy(self.baseFilename, dfn)
            #print "%s -> %s" % (self.baseFilename, dfn)
            #os.rename(self.baseFilename, dfn)
        if self.backupCount > 0:
            # find the oldest log file and delete it
            #s = glob.glob(self.baseFilename + ".20*")
            #if len(s) > self.backupCount:
            #    s.sort()
            #    os.remove(s[0])
            for s in self.getFilesToDelete():
                os.remove(s)
        self.mode = 'w'
        self.stream = self._open()
    
    def emit(self, record):
        """
        Emit a record.

        Output the record to the file, catering for rollover as described
        in doRollover().
        
        For multiprocess, we use file lock. Any better method ?
        """
        try:
            if self.shouldRollover(record):
                self.doRollover()
            FileLock = self._lock_dir + '/' + os.path.basename(self.baseFilename) + '.' + record.levelname
            f = open(FileLock, "w+")
            fcntl.flock(f.fileno(), fcntl.LOCK_EX)
            FileHandler_MP.emit(self, record)
            fcntl.flock(f.fileno(), fcntl.LOCK_UN)
            f.close()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)
    
import os
import time
import errno
 
class FileLockException(Exception):
    pass
 
class FileLock(object):
    """ A file locking mechanism that has context-manager support so 
        you can use it in a with statement. This should be relatively cross
        compatible as it doesn't rely on msvcrt or fcntl for the locking.
    """
 
    def __init__(self, file_name, timeout=10, delay=.05):
        """ Prepare the file locker. Specify the file to lock and optionally
            the maximum timeout and the delay between each attempt to lock.
        """
        self.is_locked = False
        self.lockfile = os.path.join(os.getcwd(), "%s.lock" % file_name)
        self.file_name = file_name
        self.timeout = timeout
        self.delay = delay
 
 
    def acquire(self):
        """ Acquire the lock, if possible. If the lock is in use, it check again
            every `wait` seconds. It does this until it either gets the lock or
            exceeds `timeout` number of seconds, in which case it throws 
            an exception.
        """
        start_time = time.time()
        while True:
            try:
                #独占式打开文件
                self.fd = os.open(self.lockfile, os.O_CREAT|os.O_EXCL|os.O_RDWR)
                break;
            except OSError as e:
                if e.errno != errno.EEXIST:
                    raise 
                if (time.time() - start_time) >= self.timeout:
                    raise FileLockException("Timeout occured.")
                time.sleep(self.delay)
        self.is_locked = True
 
 
    def release(self):
        """ Get rid of the lock by deleting the lockfile. 
            When working in a `with` statement, this gets automatically 
            called at the end.
        """
        #关闭文件,删除文件
        if self.is_locked:
            os.close(self.fd)
            os.unlink(self.lockfile)
            self.is_locked = False
 
 
    def __enter__(self):
        """ Activated when used in the with statement. 
            Should automatically acquire a lock to be used in the with block.
        """
        if not self.is_locked:
            self.acquire()
        return self
 
 
    def __exit__(self, type, value, traceback):
        """ Activated at the end of the with statement.
            It automatically releases the lock if it isn't locked.
        """
        if self.is_locked:
            self.release()
 
 
    def __del__(self):
        """ Make sure that the FileLock instance doesn't leave a lockfile
            lying around.
        """
        self.release()

dadian = initlog()
from common.defines import MyLock
lock = MyLock("log_lock")
def df():
    for i in xrange(10000):
        dadian.info(i)
             
def df1():
    for i in xrange(10000):
        getl = lock.acquire(blocking_timeout=5)
        try:
            if getl:
                dadian.info(i)
            else:
                print "hahahah"
        except Exception,e:
            pass
        finally:
            lock.release()

def df2():
    lockfile = "/home/sy/workspace/server/lzserver/game/common/log/mylock.lock"
    for i in xrange(10000):
        with FileLock(lockfile):
            dadian.info(i)
        
########################################################
from twisted.internet.defer import Deferred, maybeDeferred, DeferredList
def ts():
    for i in xrange(5):
        d = reactor.callInThread(reactor.callLater, 0.01, df)


from gtwisted.core import reactor as greactor
def gs():
    s =  time.time()
    asa = []
    for i in xrange(5):
        a = greactor.callLater(0.01, df)
        asa.append(a)
    for a in asa:
        a.join()
    e = time.time()
    print e-s

import multiprocessing,time
def ms():
    s = time.time()
    ps = []
    for i in xrange(5):
        p = multiprocessing.Process(target = df1)
        p.start()
        ps.append(p)
        print i
    for p in ps:
        p.join()
    e = time.time()
    print e-s
    
def ms1():
    s = time.time()
    ps = []
    for i in xrange(5):
        p = multiprocessing.Process(target = df2)
        p.start()
        ps.append(p)
        print i
    for p in ps:
        p.join()
    e = time.time()
    print e-s

def ms2():
    s = time.time()
    ps = []
    for i in xrange(5):
        p = multiprocessing.Process(target = df)
        p.start()
        ps.append(p)
        print i
    for p in ps:
        p.join()
    e = time.time()
    print e-s
##############################################################
if __name__=="__main__":
    mode = 3
    if mode==0:#twistd多线程方式
        ts()
        reactor.run()
    elif mode==1:#gevent多线程方式,协程
        gs()
        greactor.run()
    elif mode==2:#多进程--redis锁
        ms()
    elif mode==3:#多进程文件锁
        ms1()
    elif mode==4:#会报错的原始状态
        ms2()

 


分享到: 微信 更多