1. 程式人生 > >筆記-python-module-logging.迴圈日誌、多程序日誌

筆記-python-module-logging.迴圈日誌、多程序日誌

筆記-python-module-logging.迴圈日誌、多程序日誌

 

 

1.      logging迴圈日誌

迴圈日誌分為按大小切分和按時間切分,對應實現類如下。

1.1.  RotatingFileHandler

常規檔案回滾,需要指定檔名,encoding,maxBytes

如果maxbytes=0 或backupcount=0情況下不回滾,也就是隻寫到一個檔案中。

新的日誌永遠寫入filename.log,當它滿的時候會將filename.log改名為filename.log.1或其它附加值;

 

def _set_file_handler(self, level=None):
    file_name = os.path.join(LOG_PATH, '{}.log'.format(self.name))
    file_handler = RotatingFileHandler(file_name,
                                       maxBytes=5000,
                                       backupCount=5,
                                       encoding='utf-8'

)
    if not level:
        file_handler.setLevel(self.level)
    else:
        file_handler.setLevel(level)
    formatter = logging.Formatter('%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s'
)
    file_handler.setFormatter(formatter)
    self.file_handler = file_handler
    self.addHandler(file_handler)

 

1.2.    TimeRotatingFileHandler

 

def _set_time_rotating_handler(self, level=None):
    file_name = os.path.join(LOG_PATH, '{}.log'.format(self.name))
    time_handler = logging.handlers.TimedRotatingFileHandler(file_name,
                                                             when='h',
                                                             interval=1,
                                                             backupCount=5)
    if not level:
        time_handler.setLevel(self.level)
    else:
        time_handler.setLevel(level)
    formatter = logging.Formatter('%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s')
    time_handler.setFormatter(formatter)
    time_handler.suffix = "%Y-%m-%d_%H-%M-%S.log"
   
self.addHandler(time_handler)

 

需要注意的就是suffix的書寫

 

1.3.    總結

有幾個坑要注意:

  1. 儘量使用Logger(__name__),一般情況下是不需要日誌傳遞的
  2. 使用getLogger返回的實際是一個日誌樹
  3. logger例項初始化的propagate屬性是true,即向父輩傳遞訊息。而且訊息是直接傳遞給handler。

 

2.      multiprocessing and logging

logging 是執行緒安全的,handler 內部使用了 threading.RLock() 來保證同一時間只有一個執行緒能夠輸出。

但是,在使用 logging.FileHandler 時,多程序同時寫一個日誌檔案是不支援的。

測試時發現多個程序寫同一個檔案是可以的,但部分文件說如果寫長字串時會出問題,測試寫5000個字元的日誌沒問題,更長未確認。

在日誌回滾時一定會出問題。

多程序寫日誌回滾的程式碼如下:

    t = self.rolloverAt - self.interval

    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)

    os.rename(self.baseFilename, dfn)

 

關鍵是在每個程序在過了rotate時間點之後寫第一條日誌時,都會執行這個doRollover,判斷檔案是否存在,如果存在則刪除,改名,往新的.log中寫入,等多個程序都做一遍後,前一週期的日誌完全刪除,本週期的日誌也會有部分被刪除。

術語一點的講,就是在對檔案操作時,沒有對多程序進行一些約束。

 

解決辦法:

  1. 解決檔案重名問題:

既然問題是檔案重名,那麼就不讓檔案有重名,實際就是每個程序各寫一個檔案;有兩種方法:

1)在檔名中加入pid,問題是老的pid不會被自動刪除,無法自維護,不過可以通過定時任務檢查並刪除。

2)另一種方法是使用程序name做為logger名,在建立程序時給出不同的name,這樣不會出現上面的問題;

不讓檔案重名的好處是可以單獨追蹤每個程序的日誌,壞處是日誌不集中。

建議使用這種方式,寫日誌有兩種情況:

1-一般情況下每個程序是負責不同任務的,分開記錄便於查詢;

2-如果確實需要多個程序執行一樣的任務,這時在查詢日誌記錄時是比較麻煩的,不確定在哪個日誌檔案中,但考慮到工程效率,本方法也是可行的。

  1. 使用獨立程序接收並寫日誌,logging提供了sockethandler;
  2. 改寫logging的日誌回滾類程式碼,不太喜歡,每次還得注意環境問題。
  3. 程序鎖,效率問題,方法可行,但考慮各種因素,一般不會用。

 

2.1.    一些問題:windows下多程序異常

報錯:The "freeze_support()" line can be omitted if the program is not going to be

原因及解決方法:

應該是在其它的子程序 裡又開了程序,

把建立程序部分放到 if __name__ == ‘__main__’下即可

例:

if __name__ == '__main__':
    p = Process(target=func, name='my_process')
    p.daemon = True
   
p.start()
    p.join()
    print('process execute complete.')

 

3.      Logging.getLogger

getLogger是模組級的函式,實際是呼叫manager.getLogger()

def getLogger(name=None):

    """

    Return a logger with the specified name, creating it if necessary.

 

    If no name is specified, return the root logger.

    """

    if name:

        return Logger.manager.getLogger(name)

    else:

        return root

繼續:manager.getLogger()

 

  def getLogger(self, name):

        """

        Get a logger with the specified name (channel name), creating it

        if it doesn't yet exist. This name is a dot-separated hierarchical

        name, such as "a", "a.b", "a.b.c" or similar.

 

        If a PlaceHolder existed for the specified name [i.e. the logger

        didn't exist but a child of it did], replace it with the created

        logger and fix up the parent/child references which pointed to the

        placeholder to now point to the logger.

        """

        rv = None

        if not isinstance(name, str):

            raise TypeError('A logger name must be a string')

        _acquireLock()

        try:

            if name in self.loggerDict:

                rv = self.loggerDict[name]

                if isinstance(rv, PlaceHolder):

                    ph = rv

                    rv = (self.loggerClass or _loggerClass)(name)

                    rv.manager = self

                    self.loggerDict[name] = rv

                    self._fixupChildren(ph, rv)

                    self._fixupParents(rv)

            else:

                rv = (self.loggerClass or _loggerClass)(name)

                rv.manager = self

                self.loggerDict[name] = rv

                self._fixupParents(rv)

        finally:

            _releaseLock()

        return rv

 

logging通過manager.loggerDict維護了一個logger列表,實現了相同名稱返回同一個loogger。

如果logger名不存在,則建立一個新的:

rv = (self.loggerClass or _loggerClass)(name)

基本等同於logger(name)

另外一點是logger的繼承關係,是通過下面兩個方法實現的

                    self._fixupChildren(ph, rv)

                    self._fixupParents(rv)