国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

記python logging非進程安全踩得坑

nidaye / 718人閱讀

摘要:另外,由于在利用寫文件時只使用了線程可重入鎖,所以只能保證線程安全,多進程情況下會有并發寫入的問題。解決方案重載類將也設為帶時間后綴的形式,取消文件重名操作,僅在需要的時候關閉當前,打開一個新文件名的用文件鎖替換線程鎖代碼如下

背景

有兩個python進程A和B復用同一個logger創建模塊,往同一個文件里寫日志,用的是TimedRotatingFileHandler,并且每天午夜進行文件rollover,保留15天的文件

現象

偶爾會發現某一天的日志里記錄的時間是后一天的,并且只有幾行

原因

雖然官方文檔中說logging handler提供的類都是多線程安全的,但并不是多進程安全的,通過分析源碼發現事實也確實如此。logging模塊利用handler來負責日志文件的rollover,下面以TimedRotatingFileHandler為例來看下它的rollover是如何實現的:

所有打log的函數都會在Handler類中調用handle函數,然后調用emit函數:

    def handle(self, record):
        """
        Conditionally emit the specified logging record.

        Emission depends on filters which may have been added to the handler.
        Wrap the actual emission of the record with acquisition/release of
        the I/O thread lock. Returns whether the filter passed the record for
        emission.
        """
        rv = self.filter(record)
        if rv:
            self.acquire()
            try:
                self.emit(record)
            finally:
                self.release()
        return rv

在TimedRotatingFileHandler的父類BaseRotatingHandler中重載了emit函數:

    def emit(self, record):
        """
        Emit a record.

        Output the record to the file, catering for rollover as described
        in doRollover().
        """
        try:
            if self.shouldRollover(record):
                self.doRollover()
            logging.FileHandler.emit(self, record)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

可以看到其中利用shouldRollover判斷是否需要rollover,利用doRollover來執行rollover

TimedRotatingFileHandler中實現了這兩個函數:

    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
        """
        t = int(time.time())
        if t >= self.rolloverAt:
            return 1
        #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
        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.
        """
        if self.stream:
            self.stream.close()
            self.stream = None
        # get the time that this sequence started at and make it a TimeTuple
        currentTime = int(time.time())
        dstNow = time.localtime(currentTime)[-1]
        t = self.rolloverAt - self.interval
        if self.utc:
            timeTuple = time.gmtime(t)
        else:
            timeTuple = time.localtime(t)
            dstThen = timeTuple[-1]
            if dstNow != dstThen:
                if dstNow:
                    addend = 3600
                else:
                    addend = -3600
                timeTuple = time.localtime(t + addend)
        dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
        if os.path.exists(dfn):
            os.remove(dfn)
        # Issue 18940: A file may not have been created if delay is True.
        if os.path.exists(self.baseFilename):
            os.rename(self.baseFilename, dfn)
        if self.backupCount > 0:
            for s in self.getFilesToDelete():
                os.remove(s)
        if not self.delay:
            self.stream = self._open()
        newRolloverAt = self.computeRollover(currentTime)
        while newRolloverAt <= currentTime:
            newRolloverAt = newRolloverAt + self.interval
        #If DST changes and midnight or weekly rollover, adjust for this.
        if (self.when == "MIDNIGHT" or self.when.startswith("W")) and not self.utc:
            dstAtRollover = time.localtime(newRolloverAt)[-1]
            if dstNow != dstAtRollover:
                if not dstNow:  # DST kicks in before next rollover, so we need to deduct an hour
                    addend = -3600
                else:           # DST bows out before next rollover, so we need to add an hour
                    addend = 3600
                newRolloverAt += addend
        self.rolloverAt = newRolloverAt
    def computeRollover(self, currentTime):
        """
        Work out the rollover time based on the specified time.
        """
        result = currentTime + self.interval
        # If we are rolling over at midnight or weekly, then the interval is already known.
        # What we need to figure out is WHEN the next interval is.  In other words,
        # if you are rolling over at midnight, then your base interval is 1 day,
        # but you want to start that one day clock at midnight, not now.  So, we
        # have to fudge the rolloverAt value in order to trigger the first rollover
        # at the right time.  After that, the regular interval will take care of
        # the rest.  Note that this code doesn"t care about leap seconds. :)
        if self.when == "MIDNIGHT" or self.when.startswith("W"):
            # This could be done with less code, but I wanted it to be clear
            if self.utc:
                t = time.gmtime(currentTime)
            else:
                t = time.localtime(currentTime)
            currentHour = t[3]
            currentMinute = t[4]
            currentSecond = t[5]
            # r is the number of seconds left between now and midnight
            r = _MIDNIGHT - ((currentHour * 60 + currentMinute) * 60 +
                    currentSecond)
            result = currentTime + r
            # If we are rolling over on a certain day, add in the number of days until
            # the next rollover, but offset by 1 since we just calculated the time
            # until the next day starts.  There are three cases:
            # Case 1) The day to rollover is today; in this case, do nothing
            # Case 2) The day to rollover is further in the interval (i.e., today is
            #         day 2 (Wednesday) and rollover is on day 6 (Sunday).  Days to
            #         next rollover is simply 6 - 2 - 1, or 3.
            # Case 3) The day to rollover is behind us in the interval (i.e., today
            #         is day 5 (Saturday) and rollover is on day 3 (Thursday).
            #         Days to rollover is 6 - 5 + 3, or 4.  In this case, it"s the
            #         number of days left in the current week (1) plus the number
            #         of days in the next week until the rollover day (3).
            # The calculations described in 2) and 3) above need to have a day added.
            # This is because the above time calculation takes us to midnight on this
            # day, i.e. the start of the next day.
            if self.when.startswith("W"):
                day = t[6] # 0 is Monday
                if day != self.dayOfWeek:
                    if day < self.dayOfWeek:
                        daysToWait = self.dayOfWeek - day
                    else:
                        daysToWait = 6 - day + self.dayOfWeek + 1
                    newRolloverAt = result + (daysToWait * (60 * 60 * 24))
                    if not self.utc:
                        dstNow = t[-1]
                        dstAtRollover = time.localtime(newRolloverAt)[-1]
                        if dstNow != dstAtRollover:
                            if not dstNow:  # DST kicks in before next rollover, so we need to deduct an hour
                                addend = -3600
                            else:           # DST bows out before next rollover, so we need to add an hour
                                addend = 3600
                            newRolloverAt += addend
                    result = newRolloverAt
        return result

大致的步驟如下:

判斷當前時間是否大于下一次需要rollover的時間(初始化的最后第一次計算),如果大于則進行rollover

計算rollover之后的文件名dfn,如果文件已經存在則刪除

將當前文件baseFilename重命名為上一步計算出的文件名dfn

重新打開一個baseFilename的stream來寫日志

重新計算下一次需要rollover的時間

在初始化中第一次計算下一次rollover時間時:

   def __init__(self, filename, when="h", interval=1, backupCount=0, encoding=None, delay=False, utc=False):
        BaseRotatingHandler.__init__(self, filename, "a", encoding, delay)
        self.when = when.upper()
        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.interval = 1 # one second
            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.interval = 60 # one minute
            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.interval = 60 * 60 # one hour
            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.interval = 60 * 60 * 24 # one day
            self.suffix = "%Y-%m-%d"
            self.extMatch = r"^d{4}-d{2}-d{2}$"
        elif self.when.startswith("W"):
            self.interval = 60 * 60 * 24 * 7 # one week
            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)
        self.interval = self.interval * interval # multiply by units requested
        if os.path.exists(filename):
            t = os.stat(filename)[ST_MTIME]
        else:
            t = int(time.time())
        self.rolloverAt = self.computeRollover(t)

如果文件已經存在,則以文件的修改時間為基礎計算,如果不存在,則以當前時間為基礎

另外還需要注意的是,在需要rollover的時間點,進程必須是在運行狀態,如果進程退出又進入的話會重新計算下一次需要rollover的時間

基于以上分析不難看出,如果進程A先進行了rollover,進程B又進行rollover,真正的日志文件就會先被重名然后被刪除掉,只有B進程rollover前A進程新寫入的日志內容被重命名保存了下來。另外,由于在利用StreamHandler寫文件時只使用了線程可重入鎖,所以只能保證線程安全,多進程情況下會有并發寫入的問題。

解決方案

重載TimedRotatingFileHandler類:

將baseFilename也設為帶時間后綴的形式,取消文件重名操作,僅在需要rollover的時候關閉當前stream,打開一個新文件名的stream

用文件鎖替換線程鎖

代碼如下:

import os
import time
import codecs
import fcntl
from logging.handlers import TimedRotatingFileHandler


class MultiProcessSafeHandler(TimedRotatingFileHandler):
    def __init__(self, filename, when="h", interval=1, backup_count=0, encoding=None, utc=False):
        TimedRotatingFileHandler.__init__(self, filename, when, interval, backup_count, encoding, True, utc)
        self.current_file_name = self.get_new_file_name()
        self.lock_file = None

    def get_new_file_name(self):
        return self.baseFilename + "." + time.strftime(self.suffix, time.localtime())

    def should_rollover(self):
        if self.current_file_name != self.get_new_file_name():
            return True
        return False

    def do_rollover(self):
        if self.stream:
            self.stream.close()
            self.stream = None
        self.current_file_name = self.get_new_file_name()
        if self.backupCount > 0:
            for s in self.getFilesToDelete():
                os.remove(s)

    def _open(self):
        if self.encoding is None:
            stream = open(self.current_file_name, self.mode)
        else:
            stream = codecs.open(self.current_file_name, self.mode, self.encoding)
        return stream

    def acquire(self):
        self.lock_file = open(self.baseFilename + ".lock", "w")
        fcntl.lockf(self.lock_file, fcntl.LOCK_EX)

    def release(self):
        if self.lock_file:
            self.lock_file.close()
            self.lock_file = None

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/43879.html

相關文章

  • 老鳥學Javascript - 第一彈

    摘要:順帶一提,跨域可以用解決。本文主要關注一些離散的,即學即用的知識點,和一些在日常編程中容易踩得坑。不做類型轉換,所以如果比較對象的類型不一致,直接返回。當程序員給一個變量賦值為時,通常表示這個變量已經不用了。 原文:http://h01mes.com/veteran-new... 我仍然記得在一個ajax小項目踩到跨域問題(CORS)的坑,最后用Chrome插件解決。由此對Javasc...

    AlexTuan 評論0 收藏0
  • Springcloud-nacos實現配置和注冊中心

    摘要:實現配置和注冊中心最近,阿里開源的比較火,可以和和共用,對升級到非常的方便。只需要添加依賴,使用配置注冊中心地址即可。配置不生效,沒有使用注解刷新配置分清注冊中心和配置中心是兩個概念,需要配置兩個地址學會看源碼,看維基。 Springcloud-nacos實現配置和注冊中心 最近,阿里開源的nacos比較火,可以和springcloud和dubbo共用,對dubbo升級到springc...

    whinc 評論0 收藏0
  • 移動云平臺的基礎架構之旅-云應用篇

    摘要:背景當下后都能在手機鍵盤上敲字如飛,后的都可以坦然的搖微信,移動互聯網可謂炙手可熱。傳統移動開發技術方案難題終端移動平臺太多微信而且不同平臺還有版本差異,對于開發調試簡直是一場噩夢,要想實現統一覆蓋沒有雄厚的資本支持是非常困難的。 背景 當下10后都能在手機鍵盤上敲字如飛,60后的都可以坦然的搖微信,移動互聯網可謂炙手可熱。隨著智能手機的快速發展,移動APP作為登入移動互聯網最便捷的方...

    greatwhole 評論0 收藏0
  • ES6 + Webpack + React + Babel 如何在低版本瀏覽器上愉快的玩耍(上)

    摘要:起因某天,某測試說這個頁面在下白屏,也白。。某前端開發吭哧吭哧。。。一上午的時間就過去了,搞定了。第二天,某測試說又白了。。某前端開發吭哧吭哧。。。誰用的,出來我保證削不屎你。原諒我不禁又黑了一把。 起因 某天,某測試說:這個頁面在 IE8 下白屏,9也白。。 某前端開發: 吭哧吭哧。。。一上午的時間就過去了,搞定了。 第二天,某測試說:IE 又白了。。 某前端開發: 吭哧吭哧。。。誰...

    you_De 評論0 收藏0

發表評論

0條評論

nidaye

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<