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